2
votes

This question is about recovering glyph font information in Java and it is related to a question posted here. For more details please check the question and answers.

It was suggested there to use Apache FOP library to recover the kerning pairs directly from the Truetype file since Java doesn't supply this information. I then ported the library to Windows and recovered the kerning pairs using this code:

TTFFile file;
File ttf = new File("C:\\Windows\\Fonts\\calibri.ttf" );
try { file = TTFFile.open(ttf); }
catch (IOException e) {e.printStackTrace(); }
Map<Integer, Map<Integer, Integer>> kerning = file.getKerning();

Finally, the library works but the kerning pairs returned don't work with the glyphs retrieved in a Path2D.Float using the function below and the code fragment shown right after:

void vectorize(Path2D.Float path, String s) {
    PathIterator pIter;
    FontRenderContext frc = new FontRenderContext(null,true,true);
    GlyphVector gv;
    Shape glyph;
    gv = font.createGlyphVector(frc, s);
    glyph = gv.getGlyphOutline(0);
    pIter = glyph.getPathIterator(null);
    while (!pIter.isDone()) {
        switch(pIter.currentSegment(points)) {
        case PathIterator.SEG_MOVETO:
            path.moveTo(points[0], points[1]);
            break;
        case PathIterator.SEG_LINETO :
            path.lineTo(points[0], points[1]);
            break;
        case PathIterator.SEG_QUADTO :
            path.quadTo(points[0], points[1], points[2], points[3]);
            break;
        case PathIterator.SEG_CUBICTO :
            path.curveTo(points[0], points[1], points[2], points[3], points[4], points[5]);
            break;
        case PathIterator.SEG_CLOSE :
            path.closePath();
        }
        pIter.next();
    } 
}

The glyph lengths are retrieved into the array lens:

Font font = new Font("Calibri", Font.PLAIN, 1000);
double interchar = 1000. * 0.075;
int size = '}' - ' ' + 1;
Path2D.Float[] glyphs = new Path2D.Float[size];
double[] lens = new double[size];
String chars[] = new String[size];
int i; char c; 
char[] s = { '0' };
for (i = 0, c = ' '; c <= '}'; c++, i++) { s[0] = c; chars[i] = new String(s); }
for (i = 0; i < size; i++) {
    vectorize(glyphs[i] = new Path2D.Float(), chars[i]); // function shown above
    lens[i] = glyphs[i].getBounds2D().getWidth() + interchar;
}

Just to be clear, I display the glyphs using fill from Graphics2D and I translate using the lengths above added to the kerning displacements returned by the Apache FOP library as suggested, but the result is horrible. The font size is standard 1000, as suggested in that discussion, and interchar results in 75. All this seems correct but my manual kerning pairs look far much better than using the kerning pairs from the TTF file.

Is there anyone knowledgeable in this library or Truetype Fonts to be able to tell how we are supposed to use these kerning pairs?

Is it necessary to access the glyphs directly from the TTF file instead of using Java font management as shown above? If yes, how?

2

2 Answers

1
votes

Problem solved!

Recalling that to open the file and to obtain the kerning pairs one needs this code, using the library Apache FOP:

TTFFile file;
File ttf = new File("C:\\Windows\\Fonts\\calibri.ttf" );
try { file = TTFFile.open(ttf); }
catch (IOException e) {e.printStackTrace(); }
Map<Integer, Map<Integer, Integer>> kerning = file.getKerning();

The following piece of code to vectorize the glyphs is correct now:

Font font = new Font("Calibri", Font.PLAIN, 2048);
int size = '}' - ' ' + 1;
Path2D.Float[] glyphs = new Path2D.Float[size];
//double[] lens = new double[size];
String chars[] = new String[size];
int i; char c; 
char[] s = { '0' };
for (i = 0, c = ' '; c <= '}'; c++, i++) { s[0] = c; chars[i] = new String(s); }
for (i = 0; i < size; i++) {
    vectorize(glyphs[i] = new Path2D.Float(), chars[i]);
    //lens[i] = glyphs[i].getBounds2D().getWidth();
}

Notice that now the font size is 2048 which is the unitsPerEm for this particular font. This value is given by the HEAD tag in the font file as explained here.

Notice that the widths cannot be given by the array lens and code commented out above. It has to be read from the file. Using int width = getCharWidthRaw(prev) from Apache FOP, where prev is the previous character, width is the raw width of the character as written in the file. This value has to be added to the kerning pair value that can be obtained in the map kerning.

The map is used this way: kerning.get(prev) which returns another map containing the characters and kerning values to be added. If the character to be shown next is found in this map, the corresponding value is added to width. If not found, or if null is returned, there is no kerning value for this pair.

Here it is a text to show the kerning now works.

Text with correct kerning and correct character widths

0
votes

GNU Classpath contains an example, gnu.classpath.examples.awt.HintingDemo.java, that may help to solve this problem. This example allows you to visualize glyphs. It reads the font and interprets the language for hints given in it. You can choose to show with hints or without them (hinted glyphs are good for small font sizes but not recommended in large sizes). If you are not used to Truetype hints you will understand with this demo that they align the paths within integer boundaries. The program isn't very fancy but it has all the necessary tools to read the glyphs and interpret the hints with the advantage of visualizing the results.

You don't need the whole package to compile and run this demo. If you are using Eclipse it is easy to create a project for it. First create the packages gnu.classpath.examples.awt and import HintingDemo.java in it. Then you just import all its dependencies, file by file or whole packages at a time. For example, you can import the whole package gnu.java.awt.font and erase OpenTypeFontPeer.java (the demo doesn't need it and it causes an error if you leave it).

This gives a standalone way to read and display glyphs directly from the font file. Interestingly, it doesn't use any kerning information. This has to be added with Apache FOP library. If reading the file twice is a problem you will need a workaround, either going deeply into GNU Classpath to get the same information, or trying to make Apache FOP "to talk" with GNU Classpath. At this time I am unable to say how difficult this is. I am using it only as tools to copy the information and using it elsewhere, not as a way to really read font files in an actual program. Fonts are very compact but are not the most efficient way to display text, especially where there is interpretation of the font language as in the case of Type 1 and Truetype fonts. Getting rid of this interpretation looks like a good idea if you are willing high quality and speed.