PDFlibPas resuelve los caracteres que la fuente seleccionada no puede dibujar buscando en una cadena de reserva de tipografías instaladas, clúster a clúster, preservando la composición y el orden de las secuencias bidireccionales. Se activa con SetAutomaticFontFallback, la cadena se amplía con AddFontFallback, y solo las fuentes de reserva realmente usadas en la salida quedan incrustadas en el fichero
El problema que resuelve es el que se encuentra cualquier generador de documentos la primera vez que llega un nombre de cliente en una escritura que la fuente de la plantilla nunca contempló. El fallo es silencioso, y eso es lo que lo hace costoso
¿Por qué desaparece el texto no soportado en lugar de dar un error?
Porque PDF no tiene el concepto de una fuente que no puede dibujar un carácter. Una fuente simple asigna códigos de byte a nombres de glifo mediante una codificación; una fuente compuesta asigna códigos a índices de glifo a través de un CMap. Si se pide un glifo que la tipografía no contiene, se obtiene el índice de glifo cero, .notdef, que la mayoría de las fuentes dibujan como nada o como una caja vacía. El fichero es estructuralmente válido, el operador de texto está bien formado y la página se renderiza. Solo que queda en blanco donde debería estar el nombre
Nada en ISO 32000-1 exige que un productor lo note. Un generador que escribe texto sin comprobar la cobertura produce un PDF técnicamente conforme que ha perdido contenido de forma silenciosa, y la pérdida sale a la luz en la pantalla de un cliente semanas después. Por eso la función de reserva y el informe de glifos faltantes se entregan juntos: resolver lo que se puede resolver es solo la mitad del trabajo, y reportar lo que no se pudo resolver es la otra mitad
La sustitución ocurre por clúster, no por punto de código
La granularidad es el detalle que separa una implementación funcional de una plausible. El texto no es una secuencia de caracteres independientes. Una sílaba devanagari, un emoji con un modificador de tono de piel, una letra base con marcas de combinación: cada uno es un clúster que debe ser dibujado por una sola fuente, porque las decisiones de composición dentro de él dependen de tablas de esa tipografía concreta
PDFlibPas resuelve por clústeres, así que un clúster cubierto por una fuente de reserva se dibuja íntegramente con esa fuente. Dividir a mitad de un clúster y dibujar la mitad con la fuente principal y la otra mitad con una de reserva produciría un resultado técnicamente presente pero visualmente roto, que es discutiblemente peor que el espacio en blanco inicial. El orden de la secuencia también se preserva, de modo que una sustitución dentro de un tramo de derecha a izquierda no reordena el texto circundante; la misma maquinaria sustenta la disposición vertical descrita en escritura vertical para japonés y chino
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Orden de búsqueda: gana la primera coincidencia, así que las fuentes más amplias van al final
Lib.AddFontFallback('Microsoft YaHei'); // Chino simplificado
Lib.AddFontFallback('Meiryo'); // Japonés
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = incrustar la fuente
Lib.SetTextSize(11);
Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
Lib.DrawText(72, 700, 'Delivery status: on time');
Lib.SaveToFile('invoice.pdf');
finally
Lib.Free;
end;
end;
Ordene la cadena de forma deliberada. La resolución toma la primera fuente que cubra el clúster, así que una fuente panunicode amplia colocada primero ganará casi todo y sus tipografías específicas de cada escritura, elegidas con cuidado, nunca llegarán a consultarse. Ponga primero las fuentes específicas y al final la que actúa de red general
Informar o abortar: ¿qué tipo de fallo prefiere?
SetMissingGlyphPolicy acepta PDF_MISSING_GLYPH_REPORT, el valor por defecto compatible, o PDF_MISSING_GLYPH_ABORT. Con la política de informe, la operación de texto continúa, los puntos de código no resolubles se descartan como antes y cada uno queda registrado. Con la política de aborto, la operación de texto se rechaza antes de escribir cualquier contenido y LastErrorCode se fija en 521
Elija según la finalidad del documento. Un lote de informes internos debería seguir renderizando y registrar los huecos, porque un informe ligeramente incompleto hoy es mejor que ningún informe. Un contrato vinculante, una factura o cualquier cosa con un nombre propio deberían abortar, porque un carácter descartado en silencio en el nombre de una parte es un defecto que conviene descubrir en el propio proceso y no en una disputa. La política de aborto falla antes de escribir, así que no queda ningún flujo de contenido a medio formar
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... construir el documento ...
if Lib.DrawText(72, 660, CustomerName) <> 1 then
if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
begin
Report := Lib.GetMissingGlyphReportJSON;
// {"valid":false,"policy":1,"eventCount":1,"events":[
// {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
// "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
// "fontType":"TrueType","operation":"DrawText"}]}
EscalateToOperator(Report);
end;
finally
Lib.Free;
end;
end;
El informe es deliberadamente legible por máquina y acotado. Cada evento lleva la página, el índice UTF-16 dentro de la cadena, el punto de código en forma numérica y en forma U+XXXX, la fuente que se seleccionó, su tipo y la operación en la que ocurrió el problema, así que una incidencia de soporte puede nombrar el carácter exacto en vez de describir un síntoma. El rastreador conserva los 256 eventos más recientes, suficiente para diagnosticar un documento y lo bastante pequeño como para que una ejecución patológica no convierta el diagnóstico en un problema de memoria
Medición y dibujo deben coincidir
La medición del ancho usa las mismas decisiones de sustitución por clúster que el dibujo. Esto suena obvio y es justo lo que la mayoría de las capas de sustitución caseras hacen mal: parchean la ruta de dibujo, dejan la medición sobre la fuente principal, y cada cuadro de texto, alineación a la derecha y columna de tabla acaba calculado a partir de anchos que no coinciden con lo que realmente se renderizó
Como ambas rutas comparten la resolución, una cadena medida antes de dibujarse ocupa el ancho con el que se midió, incluyendo los tramos de sustitución. Eso es lo que hace que la sustitución sea segura de activar de forma global y no solo en los puntos que se auditaron a mano
Solo se incrusta lo que se usó
Las fuentes de reserva se incrustan de forma perezosa: una tipografía de la cadena que nunca resolvió ningún clúster no aporta nada a la salida. Un documento con un carácter chino y 5000 latinos no lleva una fuente CJK completa; lleva lo que el proceso de subconjunto produjo para ese único glifo, comportamiento descrito en la optimización del tamaño de fichero y el subconjunto de fuentes
Esa pereza hace que configurar una cadena amplia salga barato. Registre las tipografías que su conjunto de documentos pueda necesitar en cada configuración regional a la que da servicio, y cada PDF individual solo paga por lo que realmente usó. Para documentos que usted no generó, donde las fuentes faltantes ya están dentro de un fichero existente, la ruta de reparación es distinta y se cubre en cómo incrustar fuentes faltantes en un PDF existente
Vale la pena dejar clara una advertencia de despliegue: la sustitución se resuelve contra las tipografías instaladas en la máquina que ejecuta el código. Un servidor sin fuentes CJK instaladas no tiene nada a lo que recurrir, y el informe se lo dirá desde el primer documento en lugar de tras la primera queja. Distribuya las fuentes de las que dependa y confirme las licencias antes de incrustarlas
PDFlibPas es una biblioteca PDF para Delphi, C++Builder y Lazarus con interfaces DLL y ActiveX equivalentes, así que las API de sustitución e informe de glifos faltantes también están disponibles desde otros lenguajes distintos de Pascal. La documentación completa está en la página de PDFlibPas para Delphi