Rendering accented TrueType font characters with java doesn't look right on some systems

I had a problem converting characters for a TrueType font (like Arial) to java.awt.Shape for further manual rendering (like EPS, but that doesn't matter).

I broke a program that does this in small processes to find out where the problem came from, and it seems to me that the problem is related to the process of loading the glyph from the font.

I use the following code snippet to download a font (Arial, from the msttcore package) and convert the Γ– character to Shape , which I can use later:

 Font font = new Font("Arial", Font.PLAIN, 24); AttributedString attributedString = new AttributedString("Γ–"); attributedString.addAttribute(TextAttribute.FONT, font, 0, "Γ–".length()); FontRenderContext fontRenderContext = new FontRenderContext(null, false, false); TextLayout layout = new TextLayout(attributedString.getIterator(), fontRenderContext); Shape shape = layout.getOutline(null); 

I also tried using the following code snippet, but it gives me the same results:

 Font font = new Font("Arial", Font.PLAIN, 24); FontRenderContext fontRenderContext = new FontRenderContext(null, false, false); GlyphVector glyphVector = font.createGlyphVector(fontRenderContext, text); Shape shape = glyphVector.getOutline(); 

After that, I use shape.getPathIterator(null) and shape.getPathIterator(null) over the segments that it gives me to print the coordinates of the point. I do this on three different systems:

  • Mac OS X Mountain Lion (10.8), where I will take care of using the Font () constructor so that I can point to the right arial.ttf file to avoid my fragment using the built-in Arial font.
  • Amazon AWS Distribution Based on Fedora
  • Ubuntu-based Amazon AWS distribution distribution

When creating java.awt.Shape on my Mac, the generated EPS file looks right. When creating java.awt.Shape on Linux machines, it seems that some of the coordinates of the points are different from those that were generated on my Mac.

  • The coordinates of part O "..." are different, but only to the extent that it only looks like roundish errors and is not noticeable to my eye:

  • Part Β¨ , however, looks very strange, and the coordinates of the points differ much more than rounding errors.

See the following figure:

Both glyphs on the same picture

In green, this is the path from Shape generated on my Mac; in red, the path from Shape generated on a Fedora-like computer.

Since both overlap well on part O , it looks a bit darker than green. But you can see that part Β¨ is very different. It is not even centered for the red road ...

Summary of my experiments:

  • The Java version / distribution does not affect this issue.
  • This seems like a common problem with all accented characters (for now: Γ–Γ„Γ…Γœ).
  • I have the same issue with Times New Roman font.

I think I have tried a lot already, and I don’t understand why this is happening, and we will be grateful for any advice.


Here is the smallest complete code snippet I can give you that shows the problem:

 package Experiments; import java.awt.Font; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.PathIterator; import java.io.File; import java.text.AttributedString; public class MyClass { public static void main(String[] args) throws Exception { Font font = new Font("Arial", Font.PLAIN, 24); AttributedString attributedString = new AttributedString("Γ–"); attributedString.addAttribute(TextAttribute.FONT, font, 0, "Γ–".length()); FontRenderContext fontRenderContext = new FontRenderContext(null, false, false); TextLayout layout = new TextLayout(attributedString.getIterator(), fontRenderContext); Shape shape = layout.getOutline(null); PathIterator it = shape.getPathIterator(null); double[] points = new double[6]; double x = 0, y = 0; while (!it.isDone()) { double x1 = points[0], y1 = points[1]; double x2 = points[2], y2 = points[3]; double x3 = points[4], y3 = points[5]; switch (it.currentSegment(points)) { case PathIterator.SEG_CLOSE: System.out.println("close"); break; case PathIterator.SEG_QUADTO: // Convert to cubic curve x3 = x2; y3 = y2; x2 = x1 + 1 / 3f * (x2 - x1); y2 = y1 + 1 / 3f * (y2 - y1); x1 = x + 2 / 3f * (x1 - x); y1 = y + 2 / 3f * (y1 - y); case PathIterator.SEG_CUBICTO: System.out.println("curve: " + x1 + "," + y1 + "," + x2 + "," + y2 + "," + x3 + "," + y3); x = x3; y = y3; break; case PathIterator.SEG_LINETO: System.out.println("lineto: " + x1 + "," + y1); x = x1; y = y1; break; case PathIterator.SEG_MOVETO: System.out.println("moveto: " + x1 + "," + y1); x = x1; y = y1; break; } it.next(); } } } 

The output on my Mac is:

 moveto: 0.0,0.0 curve: 0.7734375230502337,-5.57812516624108,0.7734374884748831,-5.57812491687946,0.0,0.0 curve: 0.7734375230502337,-8.429687751224265,1.925781272817403,-13.451171899039764,3.45703125,-15.064453125 curve: 4.988281295634806,-16.677734423079528,6.96484378608875,-17.484375,9.38671875,-17.484375 curve: 10.97265629726462,-17.484375,12.402343768975697,-17.105468738707714,13.67578125,-16.34765625 curve: 14.949218787951395,-15.589843727415428,15.91992188495351,-14.533203104801942,16.587890625,-13.177734375 curve: 17.25585939490702,-11.822265584603883,17.58984375,-10.28515622438863,17.58984375,-8.56640625 curve: 17.58984375,-6.8242186980787665,17.23828123952262,-5.265624979510903,16.53515625,-3.890625 curve: 15.832031229045242,-2.5156249590218067,14.835937480791472,-1.4746093644644134,13.546875,-0.767578125 curve: 12.257812461582944,-0.06054685392882675,10.867187477764674,0.29296875,9.375,0.29296875 curve: 7.757812451804057,0.29296875,6.3124999810243025,-0.09765626164153218,5.0390625,-0.87890625 curve: 3.765624962048605,-1.6601562732830644,2.800781240221113,-2.7265625201398507,2.14453125,-4.078125 close moveto: 1.16015625,-6.10546875 curve: 2.7226562965661287,-7.589843794237822,2.7226562267169356,-8.343750000349246,1.16015625,-8.3671875 curve: 2.7226562965661287,-6.2734374376013875,4.060546891589183,-4.630859357246663,5.173828125,-3.439453125 curve: 6.287109408178367,-2.248046839493327,7.683593775029294,-1.65234375,9.36328125,-1.65234375 curve: 11.074218800989911,-1.65234375,12.482421891472768,-2.2539062679279596,13.587890625,-3.45703125 curve: 14.693359407945536,-4.660156285855919,15.24609375,-6.367187532945536,15.24609375,-8.578125 curve: 15.24609375,-9.976562541676685,15.009765617956873,-11.197265640541445,14.537109375,-12.240234375 curve: 14.064453110913746,-13.283203156082891,13.373046861437615,-14.091796883556526,12.462890625,-14.666015625 curve: 11.55273434787523,-15.240234392113052,10.531249983119778,-15.52734375,9.3984375,-15.52734375 curve: 7.789062452036887,-15.52734375,6.404296857712325,-14.974609358527232,5.244140625,-13.869140625 close moveto: 3.50390625,-12.2109375 lineto: 6.046875,-18.234375 lineto: 6.046875,-20.63671875 lineto: 8.25,-20.63671875 close moveto: 8.25,-18.234375 lineto: 10.41796875,-18.234375 lineto: 10.41796875,-20.63671875 lineto: 12.62109375,-20.63671875 close 

On a Fedora-like computer:

 moveto: 0.0,0.0 curve: 0.7708333563059568,-5.583333499729633,0.7708333218470216,-5.583333250135183,0.0,0.0 curve: 0.7708333563059568,-8.427083584479988,1.921875022817403,-13.447916690725833,3.453125,-15.0625 curve: 4.984375045634806,-16.677083381451666,6.963541702833027,-17.484375,9.390625,-17.484375 curve: 10.973958380520344,-17.484375,12.403645852347836,-17.106770822079852,13.6796875,-16.3515625 curve: 14.955729204695672,-15.596354144159704,15.92708334326744,-14.539062479743734,16.59375,-13.1796875 curve: 17.26041668653488,-11.820312459487468,17.59375,-10.28124997438863,17.59375,-8.5625 curve: 17.59375,-6.822916614823043,17.24218748952262,-5.265624979510903,16.5390625,-3.890625 curve: 15.835937479045242,-2.5156249590218067,14.838541647419333,-1.4739583227783442,13.546875,-0.765625 curve: 12.255208294838667,-0.05729164555668831,10.864583311136812,0.296875,9.375,0.296875 curve: 7.760416618548334,0.296875,6.315104147652164,-0.09375001164153218,5.0390625,-0.875 curve: 3.763020795304328,-1.6562500232830644,2.796874990221113,-2.723958353511989,2.140625,-4.078125 close moveto: 1.15625,-6.109375 curve: 2.7187500465661287,-7.5885417107492685,2.7187499767169356,-8.343750000465661,1.15625,-8.375 curve: 2.7187500465661287,-6.2812499376013875,4.057291683275253,-4.638020815560594,5.171875,-3.4453125 curve: 6.286458366550505,-2.2526041311211884,7.682291691657156,-1.65625,9.359375,-1.65625 curve: 11.078125051222742,-1.65625,12.489583349786699,-2.2578125179279596,13.59375,-3.4609375 curve: 14.697916699573398,-4.664062535855919,15.25,-6.369791699573398,15.25,-8.578125 curve: 15.25,-9.973958374932408,15.013020826270804,-11.195312515599653,14.5390625,-12.2421875 curve: 14.065104152541608,-13.289062531199306,13.372395819751546,-14.098958341870457,12.4609375,-14.671875 curve: 11.549479139503092,-15.244791683740914,10.531249983236194,-15.53125,9.40625,-15.53125 curve: 7.791666618548334,-15.53125,6.4036458160262555,-14.979166650213301,5.2421875,-13.875 close moveto: 3.5,-12.21875 lineto: 6.625,-18.0 lineto: 6.625,-20.390625 lineto: 8.828125,-20.390625 lineto: 8.828125,-18.0 close moveto: 6.625,-18.0 lineto: 11.0,-18.0 lineto: 11.0,-20.390625 lineto: 13.203125,-20.390625 lineto: 13.203125,-18.0 close 

You already have my thanks if you read before :)

+4
source share
2 answers

Why are you worried with Arial? The "free" version of msttcorefonts is a waiver of software that has not been patched for many years. It is optimized for a font rendering system that no longer exists (it is now well known that Microsoft used the wrong metadata values ​​in these files to circumvent errors in their bleeding system).

Use a modern font designed for cross-platform use and licensing, which actually allows you to redistribute and make derivatives (see the DejaVu or Google font library). Your shapes are derived.

Old java on OSX was supported by Apple and probably uses an Apple-specific font engine. Old java on other systems (like Oracle downloads) uses a proprietary font engine. OpenJDK uses freetype (Oracle did not dare to change the official jdk for fear of breaking applications by relying on its old engine warts. Shame freetype is well supported).

Expecting all of them to produce exactly the same formation results, this exercise is futile. And the more complex and old the font, the more likely they will diverge (the old fonts include the old metadata of the new fonts, and depending on the font mechanism, it will try to do something with this old data or not. Including the Apple and Windows metadata that will read or ignored depending on the font engine used).

Smart font formats are great ...

+2
source
Answer

nim led us along the way, so I accepted it.

I will describe only what we found in our research.

With a little testing of the black box, we realized that the problem disappears when using large font sizes. Then, using very large font sizes (25,000 pixels), we got the top points of the glyphs receiving negative coordinates, which look suspiciously like side effects of an integer overflow.

I did not try to find the source code of the font engine, but when testing black I can suspect that the font mechanism used in our java version somewhere uses an integer when reading glyphs from a font that gets quite large error rounding when using small sizes font and overflow errors when using large font sizes.

To fix our problem and change our production code as little as possible, we decided to always load a font with a font size of 100 pixels, and then scale the form using AffineTransform to the desired size.

We did not change the font, as suggested by nim answer, because we try to make the correction as minimal as possible, but we keep it in mind that there are much better alternatives to msttcore fonts (Google Fonts lib is one (By the way, we also had errors rounding off with Google fonts, albeit slightly less than msttcore fonts)).

0
source

Source: https://habr.com/ru/post/1498636/


All Articles