Parsear un Font

Parsear Archivos de Fuente TrueType (TTF) — Referencia

Una guía práctica e independiente de implementación para leer el formato binario TTF. Está organizada alrededor del acto de parsear: la columna vertebral del documento es 5. Cómo parsear, que recorre el archivo en orden y enlaza cada paso con el diseño de bytes de esa tabla en 6. Referencia de tablas. Lee el flujo de parseo de arriba a abajo; accede al diseño de una tabla solo cuando necesites los campos exactos.

Contenido

  1. Introducción
  2. Modelo mental
  3. Convenciones
  4. Estructura del archivo
  5. Cómo parsear
  6. Referencia de tablas
  7. Fuentes

↑ Contenido

1. Introducción

El objetivo es tomar un archivo .ttf como un arreglo plano de bytes y convertirlo en datos estructurados — el encabezado del font, los contornos de glifos, las métricas, el mapa de caracteres. Este documento cubre cómo hacerlo: la forma del archivo, el orden de lectura, cómo mover el cursor de lectura a través de los bytes, y el diseño de bytes de cada tabla principal. Los diseños de campos se dan como tablas simples Tipo | Nombre | Notas para que se correspondan directamente con una struct en cualquier lenguaje.

No contiene código de programa — es la referencia de formato a partir de la cual se construye el código.


↑ Contenido

2. Modelo mental

Un archivo TTF no es un documento que se lee de principio a fin. Es un pequeño archivo comprimido (el formato se llama “sfnt” — scalable font). Sus bytes se organizan como:

  1. Una tabla de offsets de 12 bytes (el encabezado sfnt): qué tipo de font es y cuántas tablas contiene.
  2. Un directorio de tablas: un registro de tamaño fijo por tabla, cada uno indicando “la tabla con esta etiqueta vive en este offset de byte y tiene esta longitud.”
  3. Los datos de la tabla, ubicados sólo por los offsets en el directorio.

Núnca asumes dónde está algo. Este tipo de archivos no sigue ninguna convención de orden. Lees el offset y la longitud de una tabla desde el directorio, luego saltas allí.

Ayuda saber para qué sirven las tablas antes de parsearlas. Así es como un carácter se convierte en un glifo dibujado:

código de carácter ──(cmap)──► índice de glifo ──(loca)──► glyf  (el contorno)
                                                └─(hmtx)──► ancho de avance (el espaciado)

head y maxp son la configuración que hace que loca sea interpretable. Esta cadena también explica por qué el orden de parseo en la sección 5 es el que es.


↑ Contenido

3. Convenciones

Endianness

Todo es big-endian (byte más significativo primero). Los dos bytes 0x00 0x09 significan 9, no 2304. No hay campos little-endian en ningún lugar.

Tipos de datos

La especificación usa tipos con nombre. Estos son los que necesitas:

TipoBytesSignificado
uint81byte sin signo
int81byte con signo
uint162short sin signo
int162short con signo
uint243entero de 24 bits sin signo
uint324long sin signo
int324long con signo
Fixed4punto fijo con signo 16.16
FWORD2int16 en unidades de diseño del font
UFWORD2uint16 en unidades de diseño del font
F2Dot142punto fijo con signo 2.14 (escalas/variaciones)
LONGDATETIME8int64 con signo, segundos desde 1904-01-01 00:00 UTC
Tag4cuatro caracteres ASCII uint8, p.ej. glyf
Offset162offset uint16
Offset324offset uint32
Version16Dot164versión mayor/menor empaquetada

El signo importa. La mayoría de los helpers binarios decodifican solo enteros sin signo. Para campos con signo (int16, int64, …), decodifica el valor sin signo del mismo ancho y reinterpreta los bits como complemento a dos. Si te equivocas, un número negativo pequeño (p.ej. un descender de -200) se convierte en un positivo enorme y los límites de ancho/largo de las fuentes suelen ser negativos, así que esto duele.


↑ Contenido

4. Estructura del archivo

La parte superior de todo TTF es un encabezado fijo seguido del directorio. Estas dos son las únicas partes que se leen linealmente; todo lo demás se alcanza por offset.

4.1 Tabla de offsets (encabezado sfnt) — 12 bytes, en el byte 0

TipoNombreNotas
uint32sfntVersion0x00010000 = contornos TrueType; 0x4F54544F (OTTO) = CFF/PostScript; 0x74727565 (true) en Apple. Tu verificación de “¿es un TTF?”.
uint16numTablesnúmero de registros de tabla que siguen
uint16searchRange(mayor potencia de 2 ≤ numTables) × 16
uint16entrySelectorlog2(mayor potencia de 2 ≤ numTables)
uint16rangeShiftnumTables × 16 − searchRange

searchRange, entrySelector y rangeShift son una optimización de búsqueda binaria heredada. Puedes ignorar su significado, pero para un round-trip byte-idéntico debes leerlos y re-emitirlos verbatim (o recomputarlos con las fórmulas anteriores).

4.2 Directorio de tablas — numTables registros de 16 bytes, en el byte 12

TipoNombreNotas
TagtableTag4 bytes ASCII, p.ej. head, glyf
uint32checksumchecksum de la tabla
Offset32offsetdesde el principio del archivo
uint32lengthla longitud real de la tabla, excluyendo bytes de relleno

El registro i comienza en el byte 12 + i × 16.

  • En fonts bien formados los registros están ordenados de forma ascendente por tag, pero los datos de la tabla a los que apuntan pueden aparecer en cualquier orden físico.
  • Los datos de cada tabla están rellenados con bytes cero hasta un límite de 4 bytes. length es la longitud real (sin relleno); el offset de la siguiente tabla tiene en cuenta el relleno.

↑ Contenido

5. Cómo parsear

Este es el núcleo del documento. El diagrama muestra el flujo de parseo completo y las dependencias entre tablas. A continuación, la secuencia exacta a seguir y la estrategia para decodificar cada tabla.

Orden de parseo y dependencias entre tablas

5.1 La secuencia de parseo

Dónde se ubica una tabla (físicamente) es independiente de cuándo la decodificas (lógicamente). El orden de decodificación está forzado por las dependencias de datos:

Sigue esta secuencia. Cada paso enlaza al diseño de bytes de esa tabla en la sección 7; la nota solo indica qué necesita y qué entrega el paso, no la definición completa.

  1. Tabla de offsetsdiseño de bytes →. Leer los primeros 12 bytes; capturar numTables.
  2. Directorio de tablasdiseño de bytes →. Leer numTables × 16 bytes en un mapa tag → (offset, length). Después de esto, cambiar de lectura lineal a búsqueda por offset.
  3. headdiseño de bytes →. Entrega unitsPerEm, indexToLocFormat, el bounding box del font.
  4. maxpdiseño de bytes →. Entrega numGlyphs.
  5. hheadiseño de bytes →. Entrega numberOfHMetrics.
  6. hmtxdiseño de bytes →. Necesita numberOfHMetrics (hhea) y numGlyphs (maxp).
  7. locadiseño de bytes →. Necesita indexToLocFormat (head) y numGlyphs (maxp).
  8. glyfdiseño de bytes →. Necesita loca para delimitar cada glifo.
  9. cmapdiseño de bytes →. Independiente; el mapa de carácter → glifo.
  10. namediseño →, postdiseño →, OS/2diseño →, kerndiseño →. Independientes / opcionales; parsear según se necesite.

Valida sobre la marcha. sfntVersion es un valor conocido; head.magicNumber == 0x5F0F3CF5; cada offset + length se mantiene dentro del archivo; la última entrada de loca es igual a la longitud de la tabla glyf. Estas verificaciones convierten entradas corruptas en errores claros en lugar de lecturas fuera de rango.

5.2 Tablas de diseño fijo vs. variable — la estrategia de decodificación

Cómo decodificas una tabla depende de su forma:

  • Tablas de diseño fijo tienen un conjunto fijo de campos de ancho fijo en un orden fijo, sin arreglos embebidos ni cadenas. El tamaño se conoce de antemano; puedes decodificar una en un solo pase posicional — siempre que el orden de campos de tu struct coincida exactamente con el orden en disco.
  • Tablas de diseño variable contienen un contador, versión u offset que determina cuánto sigue — arreglos dimensionados por otro campo, cadenas, sub-tablas. El tamaño no se conoce hasta que lees; estas necesitan parsing condicional escrito a mano.
TablaClasePor qué
Tabla offsetfijo12 bytes, campos fijos
Registrofijo16 bytes, campos fijos
headfijo54 bytes, todos de ancho fijo
maxpfijo*fijo por versión (0.5 vs 1.0)
hheafijo36 bytes
OS/2fijo*fijo por versión (0–5)
hmtxvariablearreglo dimensionado por hhea.numberOfHMetrics + arreglo cola
locavariablenumGlyphs + 1 entradas; ancho de entrada definido por head
glyfvariablelongitud variable por glifo, dos formatos de glifo
cmapvariablesub-tablas en varios formatos, enlazadas por offset
namevariablearreglo de registros + bloque de almacenamiento de cadenas
postvariable*encabezado fijo; v2.0 agrega un arreglo de nombres de glifos
kernvariableopcional; múltiples formatos de sub-tabla

* = fijo una vez que conoces la versión; trata la palabra de versión como una bifurcación.


↑ Contenido

6. Referencia de tablas

Los diseños de bytes a los que enlaza la secuencia de parseo en 5.1. Consulta solo lo que necesites.

↑ Contenido

6.1 head — encabezado del font (54 bytes, fijo)

El encabezado global del font. Contiene el tamaño de la grilla de diseño (unitsPerEm), el bounding box que engloba todos los glifos del font, marcas de tiempo de creación y modificación, y flags de estilo. El campo más crítico para el parseo es indexToLocFormat: controla si la tabla loca usa offsets de 2 o 4 bytes, por lo que head debe decodificarse antes de poder leer loca.

TipoNombreNotas
uint16majorVersion1
uint16minorVersion0
FixedfontRevisionversión del diseñador; almacenar en bruto si no se interpreta
uint32checksumAdjustmentchecksum de todo el archivo; ver spec
uint32magicNumbersiempre 0x5F0F3CF5 (gancho de validación)
uint16flagscampo de bits
uint16unitsPerEmgrilla de diseño; 16–16384, potencia de 2 para TrueType
LONGDATETIMEcreatedint64, segundos desde 1904
LONGDATETIMEmodifiedint64
int16xMinbounding box del font (¡con signo!)
int16yMin
int16xMax
int16yMax
uint16macStylecampo de bits
uint16lowestRecPPEMtamaño mínimo legible en píxeles
int16fontDirectionHint
int16indexToLocFormat0 = loca corto, 1 = loca largo
int16glyphDataFormat0

↑ Contenido

6.2 maxp — perfil máximo

Establece los requerimientos de memoria del font. Registra los conteos máximos de puntos, contornos y profundidad del stack del intérprete en todos los glifos, para que el rasterizador pueda pre-alocar exactamente lo que necesita. El único campo estrictamente necesario para el parseo es numGlyphs, que dimensiona los arreglos de loca y hmtx. La versión 0.5 (fonts CFF) solo lleva ese campo; la versión 1.0 (TrueType) lleva el conjunto completo.

Versión 0.5 (0x00005000, fonts CFF — 6 bytes):

TipoNombreNotas
uint32version0x00005000
uint16numGlyphsel único campo indispensable

Versión 1.0 agrega (0x00010000, TrueType — 32 bytes en total):

TipoNombreNotas
uint16maxPointsmáx. puntos en un glifo no compuesto
uint16maxContoursmáx. contornos en un glifo no compuesto
uint16maxCompositePointsmáx. puntos en un glifo compuesto
uint16maxCompositeContoursmáx. contornos en un glifo compuesto
uint16maxZones1 = sin twilight zone; 2 = twilight zone en uso
uint16maxTwilightPointsmáx. puntos en la twilight zone
uint16maxStoragemáx. ubicaciones de área de almacenamiento
uint16maxFunctionDefsmáx. definiciones de función
uint16maxInstructionDefsmáx. definiciones de instrucción
uint16maxStackElementsprofundidad máxima del stack
uint16maxSizeOfInstructionsmáx. bytes de instrucciones de glifo
uint16maxComponentElementsmáx. componentes en un glifo compuesto
uint16maxComponentDepthmáx. profundidad de anidamiento de glifos compuestos

↑ Contenido

6.3 hhea — encabezado horizontal (36 bytes, fijo)

Contiene las métricas necesarias para disponer glifos sobre una línea de base horizontal: el ascendente, descendente e interlineado que los renderizadores usan para el espaciado de líneas, y el ancho de avance máximo. Todos los valores están en unidades de diseño del font (FUnits). El campo clave para el parseo es numberOfHMetrics, que le dice al parser de hmtx cuántos pares completos (ancho de avance + LSB) contiene esa tabla.

TipoNombreNotas
uint16majorVersion1
uint16minorVersion0
FWORDascender(int16)
FWORDdescender(int16, usualmente negativo)
FWORDlineGap(int16)
UFWORDadvanceWidthMax(uint16)
FWORDminLeftSideBearing(int16)
FWORDminRightSideBearing(int16)
FWORDxMaxExtent(int16)
int16caretSlopeRisependiente del cursor (rise/run); 1 para vertical
int16caretSlopeRun0 para vertical
int16caretOffsetdesplazamiento para resaltado inclinado; 0 si no es cursiva
[4]int16(reserved)establecer en 0
int16metricDataFormat0
uint16numberOfHMetricsdimensiona la tabla hmtx

↑ Contenido

6.4 hmtx — métricas horizontales (variable)

Almacena el ancho de avance y el left side bearing (LSB) de cada glifo. El ancho de avance es cuánto se mueve el cursor después de dibujar el glifo; el LSB es la distancia desde el origen del glifo hasta el borde izquierdo de su bounding box. La tabla no tiene encabezado — son dos arreglos consecutivos cuyos tamaños provienen de hhea.numberOfHMetrics y maxp.numGlyphs.

Dos arreglos consecutivos:

  1. hMetrics: numberOfHMetrics entradas (de hhea), cada una:

    TipoNombre
    uint16advanceWidth
    int16lsb (left side bearing)
  2. leftSideBearings: numGlyphs − numberOfHMetrics entradas de int16.

El arreglo cola permite que las series monoespaciadas compartan un ancho de avance: los glifos más allá de numberOfHMetrics reutilizan el último ancho de avance y almacenan solo su side bearing.

↑ Contenido

6.5 loca — índice de ubicación (variable)

Mapea cada índice de glifo al offset de bytes de sus datos de contorno dentro de la tabla glyf. Sin loca no puedes saber dónde empieza ningún glifo. Almacena numGlyphs + 1 offsets — la entrada extra marca el fin del último glifo, de modo que la longitud de cada glifo es simplemente loca[i+1] − loca[i]. El ancho de cada entrada (2 o 4 bytes) lo establece head.indexToLocFormat.

numGlyphs + 1 offsets hacia glyf. El + 1 existe para que la longitud de cada glifo sea loca[i+1] − loca[i]; la entrada final marca el fin del último glifo.

  • Corto (indexToLocFormat == 0): numGlyphs+1 × Offset16 (uint16). El valor almacenado es el offset real ÷ 2 — multiplicar por 2 al leer. Esto funciona porque los datos de glifos están alineados a 2 bytes (todo offset real es par) y duplica el alcance de un uint16 a ~128 KB.
  • Largo (indexToLocFormat == 1): numGlyphs+1 × Offset32 (uint32), el offset real directamente.

Si loca[i] == loca[i+1], el glifo i no tiene contorno (p.ej. el espacio). Esto es común — manéjalo.

↑ Contenido

6.6 glyf — datos de glifos (variable, el difícil)

Contiene los datos de contorno reales de cada glifo — los contornos y puntos de control que el rasterizador convierte en píxeles. No tiene encabezado de tabla; son bloques de glifos compactados uno tras otro, localizados completamente por loca. Cada bloque comienza con un encabezado común de 10 bytes que indica si el glifo es simple (contornos propios) o compuesto (referencias a otros glifos), seguido de datos específicos del formato. Es la tabla más compleja de parsear.

Sin encabezado de tabla. Solo bloques de glifos consecutivos, ubicados por loca. Cada bloque comienza con un encabezado común:

TipoNombreNotas
int16numberOfContours≥ 0 → glifo simple; < 0 (usar −1) → glifo compuesto
int16xMinbounding box del glifo
int16yMin
int16xMax
int16yMax

Glifo simple (numberOfContours ≥ 0)

TipoNombreNotas
uint16endPtsOfContours[numberOfContours]último valor + 1 = cantidad total de puntos
uint16instructionLengthbytes de hinting que siguen
uint8instructions[instructionLength]bytecode de hinting TrueType (se puede pasar opacamente)
uint8flags[…]un flag lógico por punto, comprimido (abajo)
xCoordinates[…]codificado por delta, ancho según flags
yCoordinates[…]codificado por delta, ancho según flags

Cantidad total de puntos = endPtsOfContours[last] + 1. La necesitas para saber cuántos flags y coordenadas leer.

Bits de flag (un uint8 por punto, lógicamente):

BitNombreSignificado
0x01ON_CURVE_POINTel punto está en la curva (vs. un punto de control Bézier fuera de curva)
0x02X_SHORT_VECTORel delta x es de 1 byte (si no, 2 bytes o 0)
0x04Y_SHORT_VECTORel delta y es de 1 byte
0x08REPEAT_FLAGel siguiente byte es un contador de repetición; repetir este flag ese número de veces adicionales
0x10X_IS_SAME_OR_POSITIVE_X_SHORT_VECTORdoble significado (abajo)
0x20Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTORdoble significado
0x40OVERLAP_SIMPLElos contornos pueden superponerse
0x80reservedestablecer en 0

El arreglo de flags está comprimido: cuando REPEAT_FLAG (0x08) está activado, el byte siguiente indica cuántos puntos extra comparten ese flag. Leer flags en un bucle hasta acumular pointCount de ellos, expandiendo las repeticiones sobre la marcha.

Decodificación de coordenadas (se muestra x; y es idéntico con 0x04/0x20). Las coordenadas se almacenan como deltas desde el punto anterior (el delta del primer punto es desde 0), y todos los deltas x vienen primero, luego todos los deltas y:

  • X_SHORT_VECTOR (0x02) activado → el delta x es 1 byte (uint8); su signo lo da 0x10: activado ⇒ positivo, desactivado ⇒ negativo.
  • X_SHORT_VECTOR desactivado:
    • 0x10 activado ⇒ delta es 0 (esta x es igual a la x anterior).
    • 0x10 desactivado ⇒ el delta x es un int16 de 2 bytes.

Acumular deltas para obtener coordenadas absolutas.

Glifo compuesto (numberOfContours < 0)

Un bucle de componentes, cada uno referenciando otro glifo:

TipoNombreNotas
uint16flagsflags del componente (abajo)
uint16glyphIndexel id del glifo componente
arg1, arg2int8/uint8 o int16/uint16 según ARG_1_AND_2_ARE_WORDS; si ARGS_ARE_XY_VALUES, offsets de posición con signo, si no, índices de coincidencia de puntos
transform0, 1, 2 o 4 × F2Dot14 según los flags de escala

Bits de flag del componente:

BitNombreSignificado
0x0001ARG_1_AND_2_ARE_WORDSlos args son de 16 bits (si no, de 8 bits)
0x0002ARGS_ARE_XY_VALUESlos args son offsets (si no, índices de puntos)
0x0004ROUND_XY_TO_GRID
0x0008WE_HAVE_A_SCALEsigue un F2Dot14 de escala uniforme
0x0020MORE_COMPONENTSsigue otro componente; continuar el bucle
0x0040WE_HAVE_AN_X_AND_Y_SCALEsiguen dos F2Dot14
0x0080WE_HAVE_A_TWO_BY_TWOsiguen cuatro F2Dot14 (matriz 2×2)
0x0100WE_HAVE_INSTRUCTIONSdespués del último componente: longitud uint16 + bytes de instrucción
0x0200USE_MY_METRICS
0x0400OVERLAP_COMPOUND

Continuar el bucle mientras MORE_COMPONENTS esté activado.

↑ Contenido

6.7 cmap — mapeo de carácter a glifo (variable)

Mapea códigos de carácter Unicode (u otras codificaciones) a índices de glifo. Es el puente entre el texto y los contornos — sin ella no puedes saber qué glifo dibujar para un carácter dado. La tabla contiene múltiples sub-tablas para distintas combinaciones de plataforma/codificación; eliges la más adecuada para tu caso (típicamente la sub-tabla Windows Unicode BMP, formato 4) y usas solo esa.

Encabezado:

TipoNombre
uint16version (0)
uint16numTables

Luego numTables registros de codificación:

TipoNombreNotas
uint16platformID0=Unicode, 1=Mac, 3=Windows
uint16encodingIDespecífico de plataforma
Offset32subtableOffsetdesde el inicio de la tabla cmap

Combinaciones comunes: (3,1) Windows BMP Unicode → usualmente formato 4; (3,10) Windows Unicode completo → formato 12; (0,*) Unicode; (1,0) Mac Roman → formato 0. Elegir la mejor sub-tabla, luego parsear según su palabra de formato:

  • Formato 0 — codificación por byte: format, length, language, luego uint8 glyphIdArray[256].
  • Formato 4 — mapeo por segmentos (BMP, el más usado): format, length, language, segCountX2, searchRange, entrySelector, rangeShift, luego arreglos paralelos endCode[segCount], un pad reservado, startCode[segCount], idDelta[segCount], idRangeOffset[segCount], y un glyphIdArray al final.
  • Formato 6 — tabla recortada: un rango contiguo de códigos.
  • Formato 12 — cobertura segmentada (Unicode completo): format(12), reserved, uint32 length, uint32 language, uint32 numGroups, luego grupos de { uint32 startCharCode; uint32 endCharCode; uint32 startGlyphID }.

↑ Contenido

6.8 name — cadenas legibles por humanos (variable)

Almacena todos los textos legibles del font: nombre de familia, subfamilia, nombre completo, cadena de versión, aviso de copyright, marca registrada y más. Cada cadena se guarda una vez en un bloque de bytes al final de la tabla, y una lista de registros apunta a ese bloque con metadatos de plataforma, codificación, idioma e ID de nombre. Para la mayoría de los casos necesitas los IDs 1 (familia), 2 (subfamilia) y 4 (nombre completo), plataforma 3 (Windows), decodificados como UTF-16BE.

TipoNombreNotas
uint16version0 o 1
uint16countnúmero de registros de nombre
Offset16storageOffsetinicio del almacenamiento de cadenas, desde el inicio de la tabla

Luego count registros de nombre:

TipoNombreNotas
uint16platformID
uint16encodingID
uint16languageID
uint16nameIDqué es la cadena (1=familia, 2=subfamilia, 4=nombre completo, 5=versión, …)
uint16lengthlongitud de la cadena en bytes
Offset16stringOffsetdesde storageOffset

(La versión 1 agrega un langTagCount + registros de etiqueta de idioma antes del almacenamiento.) Luego los bytes de cadena en bruto, codificados por plataforma — usualmente UTF-16BE para la plataforma 3.

↑ Contenido

6.9 post — datos PostScript (variable, dependiente de versión)

Contiene metadatos específicos de PostScript: el ángulo de itálica, si el font es monoespaciado (isFixedPitch), y opcionalmente un mapeo de índice de glifo a nombre de glifo PostScript. El encabezado de 32 bytes siempre está presente; el arreglo de nombres de glifos solo aparece en la versión 2.0. Para la mayoría del trabajo de transformación solo necesitas isFixedPitch e italicAngle.

Encabezado fijo de 32 bytes:

TipoNombre
Version16Dot16version
FixeditalicAngle
FWORDunderlinePosition
FWORDunderlineThickness
uint32isFixedPitch
uint32minMemType42
uint32maxMemType42
uint32minMemType1
uint32maxMemType1
  • 1.0 (0x00010000): solo encabezado (se asumen nombres de glifos Mac estándar).
  • 2.0 (0x00020000): encabezado + uint16 numGlyphs + uint16 glyphNameIndex[numGlyphs] + nombres en cadenas Pascal para índices ≥ 258.
  • 2.5: deprecado. 3.0 (0x00030000): solo encabezado, sin nombres.

↑ Contenido

6.10 OS/2 — métricas OS/2 y Windows (fijo por versión)

La fuente principal de metadatos de clasificación del font para Windows y renderizadores multiplataforma. Contiene la clase de peso (thin → black), la clase de ancho (condensado → expandido), ascendente/descendente/interlineado tipográfico, métricas específicas de Windows, mapas de bits de cobertura Unicode y de páginas de código, la clasificación PANOSE y flags de permisos de incrustación. Esta es la tabla que modifican las transformaciones de negrita y ancho. La palabra de versión determina cuántos campos finales existen. Al leer versiones antiguas, rellenar con ceros hasta el tamaño completo del struct para que los campos faltantes queden en 0.

Versión 0 (78 bytes, todas las versiones):

TipoNombreNotas
uint16version0–5
int16xAvgCharWidthpromedio ponderado del ancho de avance de minúsculas
uint16usWeightClass100–900 (equivale a font-weight de CSS)
uint16usWidthClass1–9, condensado → expandido
uint16fsTypeflags de permisos de incrustación
int16ySubscriptXSize
int16ySubscriptYSize
int16ySubscriptXOffset
int16ySubscriptYOffset
int16ySuperscriptXSize
int16ySuperscriptYSize
int16ySuperscriptXOffset
int16ySuperscriptYOffset
int16yStrikeoutSize
int16yStrikeoutPosition
int16sFamilyClassclasificación IBM de familia tipográfica
uint8[10]panoseclasificación PANOSE de 10 bytes
uint32ulUnicodeRange1bits de cobertura de bloque Unicode 0–31
uint32ulUnicodeRange2bits 32–63
uint32ulUnicodeRange3bits 64–95
uint32ulUnicodeRange4bits 96–127
TagachVendIDidentificador de vendedor de 4 caracteres
uint16fsSelectionflags de estilo (cursiva, negrita, regular, …)
uint16usFirstCharIndexcodepoint Unicode más bajo del font
uint16usLastCharIndexcodepoint Unicode más alto del font
int16sTypoAscenderascendente tipográfico (FUnits)
int16sTypoDescenderdescendente tipográfico (FUnits, usualmente negativo)
int16sTypoLineGapinterlineado tipográfico (FUnits)
uint16usWinAscentmétrica de ascendente Windows
uint16usWinDescentmétrica de descendente Windows (valor positivo)

Versión 1 agrega (86 bytes en total):

TipoNombreNotas
uint32ulCodePageRange1bits de cobertura de página de código 0–31
uint32ulCodePageRange2bits 32–63

Versión 2 / 3 / 4 agrega (96 bytes en total):

TipoNombreNotas
int16sxHeightaltura de la ‘x’ minúscula (FUnits)
int16sCapHeightaltura de la ‘H’ mayúscula (FUnits)
uint16usDefaultCharíndice de glifo para el carácter por defecto
uint16usBreakCharíndice de glifo para el carácter de separación
uint16usMaxContextlongitud máxima del contexto de glifo objetivo

Versión 5 agrega (100 bytes en total):

TipoNombreNotas
uint16usLowerOpticalPointSizetamaño óptico inferior ×20
uint16usUpperOpticalPointSizetamaño óptico superior ×20

↑ Contenido

6.11 kern — kerning (opcional, variable)

Almacena ajustes de espaciado entre pares específicos de glifos. Donde hmtx le da a cada glifo un único ancho de avance, kern permite decir “cuando una ‘A’ es seguida por una ‘V’, acércalas 40 unidades”. Es opcional — muchos fonts modernos lo omiten y usan GPOS para un kerning contextual más potente. Cuando está presente, es una secuencia de sub-tablas que cada una cubre un conjunto de pares.

Opcional y históricamente complicado — Apple y Microsoft definen encabezados incompatibles. Muchos fonts omiten kern (el kerning moderno vive en GPOS). El formato Microsoft (el objetivo para fonts de la era OpenType):

Encabezado de tabla:

TipoNombreNotas
uint16version0
uint16nTablesnúmero de sub-tablas que siguen

Encabezado por sub-tabla:

TipoNombreNotas
uint16versionversión de sub-tabla (0)
uint16lengthlongitud total en bytes (incluyendo este encabezado)
uint16coveragebyte alto = formato (0 o 2); byte bajo = flags (ver abajo)

Flags de coverage (byte bajo): bit 0 = horizontal, bit 1 = mínimo, bit 2 = cross-stream, bit 3 = override.

Formato 0 (pares kern ordenados — el caso común):

TipoNombreNotas
uint16nPairsnúmero de pares kern
uint16searchRange(mayor potencia de 2 ≤ nPairs) × 6
uint16entrySelectorlog2(mayor potencia de 2 ≤ nPairs)
uint16rangeShiftnPairs × 6 − searchRange

Luego nPairs entradas de 6 bytes cada una:

TipoNombreNotas
uint16leftíndice del glifo izquierdo
uint16rightíndice del glifo derecho
int16valueajuste de kern en FUnits

Los pares están ordenados por (left << 16) | right para búsqueda binaria. Iterar sobre las sub-tablas mientras queden bytes.


↑ Contenido

7. Fuentes

Cuando los dos discrepan en casos límite, tratar la especificación de Microsoft como canónica para fonts de la era OpenType.

Páginas por tabla, todas bajo https://learn.microsoft.com/en-us/typography/opentype/spec/:

TablaPáginaTablaPágina
structureotffcmapcmap
headheadnamename
maxpmaxppostpost
hheahheaOS/2os2
hmtxhmtxkernkern
localocaglyfglyf