PDFlibPas resuelve los caracteres que la fuente seleccionada no puede dibujar buscando en una cadena de fuentes de respaldo entre las caras instaladas, clúster por clúster, preservando el shaping y el orden de las corridas bidireccionales. Se activa con SetAutomaticFontFallback, se extiende la cadena con AddFontFallback, y solo las fuentes de respaldo realmente usadas en la salida quedan incrustadas en el archivo
El problema que resuelve es uno que todo generador de documentos enfrenta la primera vez que llega el nombre de un cliente en una escritura que la fuente de la plantilla nunca anticipó. La falla es silenciosa, y eso es lo que la vuelve costosa
¿Por qué el texto no soportado desaparece en lugar de generar un error?
Porque PDF no tiene el concepto de una fuente que no puede dibujar un carácter. Una fuente simple mapea códigos de byte a nombres de glifo mediante una codificación; una fuente compuesta mapea códigos a través de un CMap hacia índices de glifo. Pida un glifo que la cara no contiene y obtendrá el índice de glifo cero, .notdef, que la mayoría de las caras dibujan como nada o como un recuadro vacío. El archivo es estructuralmente válido, el operador de texto está bien formado, y la página se renderiza. Simplemente 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 verificar la cobertura produce un PDF técnicamente conforme que ha perdido contenido en silencio, y la pérdida sale a la superficie en la pantalla de un cliente semanas después. Por esto la función de respaldo y el reporte de glifos faltantes vienen 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
El respaldo ocurre por clúster, no por punto de código
La granularidad es el detalle que separa una implementación funcional de una que solo parece funcionar. 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 combinadas: cada uno es un clúster que debe renderizarse con una sola fuente, porque las decisiones de shaping dentro de él dependen de tablas de esa cara
PDFlibPas resuelve por clústeres, así que un clúster cubierto por una cara de respaldo se dibuja por completo con esa cara. Dividir a mitad de un clúster y dibujar la mitad con la fuente primaria y la otra mitad con una de respaldo produciría un resultado técnicamente presente pero visiblemente roto, que se podría argumentar es peor que el espacio en blanco con el que empezó. El orden de las corridas también se preserva, así que un respaldo dentro de una corrida 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, ponga las caras más amplias 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 cara
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 cara que cubre el clúster, así que una fuente panunicode amplia colocada primero ganará casi todo y sus caras específicas de escritura, cuidadosamente elegidas, nunca serán consultadas. Coloque las caras específicas primero y la de captura general al final
¿Reportar o abortar: qué tipo de falla prefiere?
SetMissingGlyphPolicy acepta PDF_MISSING_GLYPH_REPORT, el valor predeterminado compatible, o PDF_MISSING_GLYPH_ABORT. Bajo la política de reporte, la operación de texto continúa, los puntos de código sin resolver se descartan como antes, y cada uno queda registrado. Bajo 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 para qué es el documento. Un lote de reportes internos debería seguir renderizando y registrar los vacíos, porque un reporte ligeramente incompleto hoy es mejor que ningún reporte en absoluto. Un contrato legalmente vinculante, una factura, o cualquier cosa con un nombre encima, debería abortar, porque un carácter descartado en silencio en el nombre de una parte es un defecto que usted quiere descubrir en su propio proceso y no en una disputa. La política de aborto falla antes de escribir, así que no queda ningún stream 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 reporte 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 fue seleccionada, su tipo y la operación que encontró el problema, de modo que un ticket de soporte puede nombrar el carácter exacto en lugar de describir un síntoma. El rastreador conserva los 256 eventos más recientes, lo cual alcanza para diagnosticar un documento y es suficientemente pequeño para que una ejecución patológica no convierta el diagnóstico en un problema de memoria
La medición y el dibujo deben coincidir
La medición de ancho usa las mismas decisiones de respaldo por clúster que el dibujo. Esto suena obvio y es justo lo que la mayoría de las capas de respaldo caseras hacen mal: parchan la ruta de dibujo, dejan la medición en la fuente primaria, y cada cuadro de texto, alineación derecha y columna de tabla termina calculado a partir de anchos que no coinciden con lo que se renderizó
Como ambas rutas comparten la resolución, una cadena medida antes de dibujarse ocupa el ancho con el que fue medida, incluyendo las corridas de respaldo. Eso es lo que hace seguro habilitar el respaldo globalmente, en lugar de solo en los lugares que usted auditó a mano
Solo lo que usted usó queda incrustado
Las fuentes de respaldo se incrustan de forma perezosa: una cara de la cadena que nunca resolvió un clúster no aporta nada a la salida. Un documento que contiene un carácter chino y 5,000 latinos no lleva una cara CJK completa; lleva lo que el proceso de subconjunto produjo para ese único glifo, que es el comportamiento descrito en optimización de tamaño de archivo y subconjunto de fuentes
Esa pereza hace que una cadena amplia sea barata de configurar. Registre las caras que su conjunto de documentos podría necesitar en cada locale que atienda, y cada PDF individual paga solo por lo que realmente usó. Para documentos que usted no generó, donde las caras faltantes ya están dentro de un archivo existente, la ruta de reparación es distinta y se cubre en incrustación de fuentes faltantes en un PDF existente
Vale la pena declarar una advertencia de despliegue con claridad: el respaldo se resuelve contra las caras instaladas en la máquina que ejecuta el código. Un servidor sin fuentes CJK instaladas no tiene nada a qué recurrir, y el reporte se lo dirá desde el primer documento en lugar de después de la primera queja. Distribuya las fuentes de las que depende, y confirme las licencias para incrustarlas
PDFlibPas es una biblioteca de PDF para Delphi, C++Builder y Lazarus con interfaces DLL y ActiveX equivalentes, así que las API de respaldo y de glifos faltantes también están disponibles desde llamadores que no son Pascal. La documentación completa está en la página de la biblioteca PDFlibPas Delphi