8

I am currently drawing text on Canvas while using external (non-standard) font, loaded from TTF file. I want to enable kerning for the text I am displaying.

What I want to know is if there is a possibility to read kerning pairs from typeface using Android API.

Alex Semeniuk
  • 1,865
  • 19
  • 28
  • [This older question](http://stackoverflow.com/q/1640659/2564301) (but with answers as recent as last year) suggest the kern tables are not exposed into Java. Is "manually" parsing the raw file data a viable option? (Noting that there are several different table formats, all equally atrocious...) – Jongware Jan 27 '16 at 20:31
  • thanks, Jongware. I do not mind parsing *.ttf files providing there is some information on where to look for kerning pairs (I'm usually a bit afraid of atrocious formats). – Alex Semeniuk Jan 29 '16 at 10:43
  • That would be in the [OpenType specifications](https://www.microsoft.com/typography/otspec/default.htm), to be exact in [The Kerning Table](https://www.microsoft.com/typography/otspec/kern.htm). Definitely not for the faint of heart; and if you are *really* unlucky, the font you used is a Type 1 type and so you'd need to parse the `GPOS` subtable, which is yet magnitudes more difficult... (This may well be the very reason the programmers of Canvas' `drawText` simply did not bother ...) – Jongware Jan 29 '16 at 10:55
  • Thanks. I'll experiment with this when I have some spare time. – Alex Semeniuk Jan 29 '16 at 11:08
  • 1
    There is no Android API to get the kerning table or parse a TTF file. However, I have slimmed down and ported [apache fop](https://github.com/apache/fop) and it should be able to accomplish what you want. I can't find a TTF that has kerning. Will you please link to a font using kerning? – Jared Rummler Feb 16 '16 at 00:25
  • Actually, the Android utilises the values from kerning table. Methods like `Paint.measureText()` or (more accurately) `Paint.getTextWidths()` adjust their results with regards to the kerning table. However, Google think they are too smart and completely ignore these values if first and second characters are from different languages. What a shame... – Alex Semeniuk Feb 24 '16 at 10:49

2 Answers2

9

What I want to know is if there is a possibility to read kerning pairs from typeface using Android API.

There is no public API to read kerning pairs from a TTF file. However, I pulled the relevant code from Apache FOP and you can read the kerning pairs using this library.

Example usage:

TTFFile file = TTFFile.open(getAssets().open("fonts/font.ttf"));
Map<Integer, Map<Integer, Integer>> kerning = file.getKerning();

You can also retrieve other metadata. Example:

TTFFile ttfFile = TTFFile.open(new File("/system/fonts/Roboto-Regular.ttf"));

String name = ttfFile.getFullName();             // "Roboto Regular"
String family = ttfFile.getSubFamilyName();      // "Regular"
int fontWeight = ttfFile.getWeightClass();       // 400
String copyright = ttfFile.getCopyrightNotice(); // "Font data copyright Google 2014"

I want to enable kerning for the text I am displaying.

See:

How to adjust text kerning in Android TextView?

setLetterSpacing(float)

Community
  • 1
  • 1
Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
  • Thanks, I'll give it a try and get back to you. As for `setSpacing()` method, it is not quite the same since spacing is set to all characters of the string while kerning is adjusted only for specific character pairs. – Alex Semeniuk Feb 16 '16 at 09:37
  • 1
    Have you tried out the library? I can upload it to maven if needed/wanted. I spent a couple hours on it so hopefully that time was useful to your project :-) – Jared Rummler Feb 18 '16 at 17:59
  • So... the key of the first map is the code of the first character in the pair? So `kerning.get(firstChar)` should return a map of all charcters which associated with `firstChar` in a pair. And thus `kerning.get(firstChar).get(secondChar)` should return the offset for the pair consisting of `firstChar` followed by `secondChar`. Right? The what does the actual value mean? For instance, `-40`. Is this the percentage of width by which I should shift the second character (in case of negative - to the left)? Please explain. – Alex Semeniuk Feb 22 '16 at 10:07
  • And yeah, such an amazing library should be able to be easily distributed via Maven/Gradle (or at least as a pre-built JAR file). – Alex Semeniuk Feb 22 '16 at 10:51
  • 1
    Kinda figured this out myself. If we take the value from kerning table mutliply it by the font size (or text size) and then divide by 1000 we get the actual distance the second character should be shifted away from the first one (for negative values - shifted towards the first character). – Alex Semeniuk Feb 24 '16 at 10:46
0

I was willing to use the parser described above using standard Java on Windows. If anyone wants to do it, one needs to use Rectangle instead of Rect. This is just a minor conversion. I also eliminated the directory jaredrummler because it was a bit too long (I kept the copyright comments in the beginning of the files, though). But there are two TTFFile classes in this parser. 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();

Only works if you import the correct class file:

import com.fontreader.truetype.TTFFile;

Finally, the code works but the kerning pairs returned don't work with the paths you convert using:

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();
    } 
}

And lengths recovered by lens in the following code:

double interchar = fontsize * 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], tx[i], 0f);
    lens[i] = glyphs[i].getBounds2D().getWidth() + interchar;
}

Just to be clear, I display the glyphs using fill in Graphics2D and I translate using the lengths above added to the kerning displacements returned by the library Apache FOP as suggested above, but the result is horrible. The fontsize is standard 1000 as suggested in this discussion and interchar results in 75, after multiplying by the font size. 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 trained with this library to be able to tell how we are supposed to use these kerning pairs?

Sorry for diverting slightly from the original question but this might complete the information since once one reads the kerning pairs how one uses them correctly on either Windows or Android?

  • Welcome to Stack Overflow. This is a useful contribution to help fill out this thread. As Stack Overflow isn't setup as a traditional discussion board, but rather a question and answer site, please try to separate out any follow-up questions from your answer. If it's a minor point, you might just add it as a comment under your answer. If it's something you really want to dive into, though, I'd recommend asking a new question and then linking back to this thread for more details. This will also help ensure that reviewers don't mistake your contribution as (just) a question. – Jeremy Caney Nov 09 '20 at 22:16
  • My post helps users to convert Apache FOP for Windows. There are two slight problems to do it. The first is to convert Rect to Rectangle. The second point is that there are two TTFFile classes, which is quite confusing. I point out the correct one. The post also shows a code fragment to extract the kerning pairs from a TTF file using the library on Windows. The second part of the post shows how to obtain the glyphs directly from Java, but the information doesn't match with the kerning pairs. This seems to be a known problem in Java Fonts Management. It's going to be posted as a question. – Jack London Nov 09 '20 at 22:58