¿Cómo usar pares de kerning extraídos de un archivo TTF para mostrar glifos correctamente como Path2D en Java?

Nov 09 2020

Esta pregunta trata sobre la recuperación de información de fuentes de glifos en Java y está relacionada con una pregunta publicada aquí. Para obtener más detalles, consulte la pregunta y las respuestas.

Allí se sugirió usar la biblioteca Apache FOP para recuperar los pares de kerning directamente desde el archivo Truetype ya que Java no proporciona esta información. Luego porté la biblioteca a Windows y recuperé los pares de kerning usando este código:

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

Finalmente, la biblioteca funciona, pero los pares de kerning devueltos no funcionan con los glifos recuperados en Path2D.Float usando la función a continuación y el fragmento de código que se muestra justo después:

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

Las longitudes de los glifos se recuperan en la lente de matriz :

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

Para que quede claro, muestro los glifos usando el relleno de Graphics2D y traduzco usando las longitudes arriba agregadas a los desplazamientos de kerning devueltos por la biblioteca Apache FOP como se sugiere, pero el resultado es horrible. El tamaño de fuente es estándar 1000, como se sugiere en esa discusión, e interchar resulta en 75. Todo esto parece correcto pero mis pares de kerning manuales se ven mucho mejor que usar los pares de kerning del archivo TTF.

¿Hay alguien con conocimientos en esta biblioteca o Truetype Fonts que pueda decir cómo se supone que debemos usar estos pares de kerning?

¿Es necesario acceder a los glifos directamente desde el archivo TTF en lugar de utilizar la gestión de fuentes Java como se muestra arriba? Si es así, ¿cómo?

Respuestas

JackLondon Nov 10 2020 at 21:31

GNU Classpath contiene un ejemplo, gnu.classpath.examples.awt. HintingDemo.java , que puede ayudar a resolver este problema. Este ejemplo le permite visualizar glifos. Lee la fuente e interpreta el idioma en busca de sugerencias. Puede elegir mostrar con pistas o sin ellas (los glifos con pistas son buenos para tamaños de letra pequeños, pero no se recomiendan en tamaños grandes). Si no está acostumbrado a las sugerencias Truetype, comprenderá con esta demostración que alinean las rutas dentro de los límites de números enteros. El programa no es muy sofisticado pero tiene todas las herramientas necesarias para leer los glifos e interpretar las pistas con la ventaja de visualizar los resultados.

No necesita el paquete completo para compilar y ejecutar esta demostración. Si está utilizando Eclipse, es fácil crear un proyecto para él. Primero cree los paquetes gnu.classpath.examples.awt e importe HintingDemo.java en él. Luego, solo importa todas sus dependencias, archivo por archivo o paquetes completos a la vez. Por ejemplo, puede importar el paquete completo gnu.java.awt.font y borrar OpenTypeFontPeer.java (la demostración no lo necesita y genera un error si lo deja).

Esto proporciona una forma independiente de leer y mostrar glifos directamente desde el archivo de fuente. Curiosamente, no utiliza ninguna información de kerning. Esto debe agregarse con la biblioteca Apache FOP . Si leer el archivo dos veces es un problema, necesitará una solución alternativa, ya sea profundizando en GNU Classpath para obtener la misma información, o tratando de hacer que Apache FOP "hable" con GNU Classpath. En este momento no puedo decir lo difícil que es esto. Lo estoy usando solo como herramientas para copiar la información y usarlo en otro lugar, no como una forma de leer realmente archivos de fuentes en un programa real. Las fuentes son muy compactas pero no son la forma más eficiente de mostrar texto, especialmente cuando hay una interpretación del idioma de la fuente como en el caso de las fuentes Type 1 y Truetype. Deshacerse de esta interpretación parece una buena idea si desea alta calidad y velocidad.

JackLondon Nov 11 2020 at 07:48

¡Problema resuelto!

Recordando que para abrir el archivo y obtener los pares de kerning se necesita este código, usando la biblioteca 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();

El siguiente fragmento de código para vectorizar los glifos es correcto ahora:

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

Observe que ahora el tamaño de fuente es 2048, que es el unitPerEm para esta fuente en particular. Este valor viene dado por la etiqueta HEAD en el archivo de fuente como se explica aquí .

Tenga en cuenta que los anchos no pueden ser dados por la matriz lensy el código comentado anteriormente. Tiene que leerse del archivo. El uso int width = getCharWidthRaw(prev)de Apache FOP , donde prevestá el carácter anterior, widthes el ancho sin procesar del carácter tal como está escrito en el archivo. Este valor debe agregarse al valor del par de kerning que se puede obtener en el mapa kerning.

El mapa se usa de esta manera: kerning.get(prev)que devuelve otro mapa que contiene los caracteres y valores de kerning que se agregarán. Si el carácter que se mostrará a continuación se encuentra en este mapa, se agrega el valor correspondiente width. Si no se encuentra, o si nullse devuelve, no hay valor de kerning para este par.

Aquí hay un texto para mostrar que el kerning ahora funciona.