losLab PDF Library puede incrustar los programas de fuente ausentes de un PDF ya cargado con una sola llamada: EmbedMissingFonts recorre cada diccionario de fuente del documento, localiza la fuente de sistema instalada que coincide por su nombre BaseFont y escribe el programa de fuente de vuelta en el archivo. Para los equipos que reparan documentos de terceros que no superan la validación PDF/A por la incrustación de fuentes, esta es la corrección que hace desaparecer el error de preflight 00030
El escenario es deprimentemente común. Un pipeline de ingesta de archivo recibe PDF de proveedores, clientes o una oficina de escaneo; los documentos se renderizan bien en todos los escritorios del edificio; y entonces el validador PDF/A rechaza todo el lote con la misma queja repetida una vez por archivo: al menos una fuente no está incrustada. Nadie aguas arriba va a regenerar los archivos, así que el pipeline tiene que repararlos. Este artículo cubre esa ruta de reparación. Es el complemento de el artículo sobre preflight, que cubre la detección de infracciones PDF/A y PDF/UA: aquella pieza dice qué documentos están rotos, esta corrige la forma más frecuente en que lo están
¿Por qué PDF/A exige que todas las fuentes estén incrustadas?
ISO 19005-1 §6.3.4 exige que cada fuente usada por un documento conforme lleve su programa de fuente dentro del archivo, porque toda la promesa de PDF/A es la reproducibilidad: el documento debe renderizarse de forma idéntica en una máquina dentro de cincuenta años que no comparta ninguna fuente con la máquina que lo produjo. Una fuente no incrustada es una instrucción para ir a buscar Arial en algún lugar del sistema de visualización, y la postura de la norma es que "algún lugar del sistema de visualización" no es una garantía de archivo. Sean cuales sean los glifos, las métricas y la cobertura de la fuente sustituta, eso es lo que recibe el lector, y puede no ser lo que vio el autor
El culpable histórico es la convención de las Standard 14. PDF 1.0 prometía que todos los visores incluían Helvetica, Times, Courier, Symbol y ZapfDingbats, así que los generadores aprendieron a referenciar esas fuentes por nombre y a no incrustar nada, y treinta años de herramientas siguen haciendo exactamente eso. losLab PDF Library se toma el requisito lo bastante en serio como para que, en modo de creación PDF/A, AddStandardFont sea deliberadamente una operación nula: la biblioteca no incluye los programas de fuente de las Standard 14, no puede incrustar lo que no tiene y se niega a escribir una referencia no incrustada en un documento que declara conformidad. Devuelve 0 sin seleccionar ninguna fuente, así que un documento PDF/A debe usar AddTrueTypeFont con incrustación, y cualquier petición Embed=0 se promociona en silencio a Embed=1 mientras el modo PDF/A está activo. Ese es el lado del escritor. El problema más difícil es el lado del lector: un documento que otra persona ya escribió, lleno de diccionarios de fuente que no creaste tú
¿Cómo repara EmbedMissingFonts un documento cargado?
losLab PDF Library repara las fuentes en el sitio en lugar de reconstruirlas. Cuando un generador de PDF escribe una fuente TrueType no incrustada, el diccionario FontDescriptor que produce ya está completo: FontName, FontBBox, Flags, Ascent, Descent, StemV, todos presentes. Lo único que lo separa de una fuente incrustada es la ausencia de una entrada, la referencia al stream /FontFile2 que contiene el programa de fuente real. Así que EmbedMissingFonts no toca el diccionario de fuente, la codificación, el array de anchos ni ningún flujo de contenido que referencie la fuente por nombre de recurso. Lee el programa de fuente correspondiente del sistema, lo comprime en un nuevo objeto stream y añade una única referencia /FontFile2 (o /FontFile3 para fuentes CIDFontType0) al FontDescriptor que ya está ahí. Todo aquello a lo que apuntan las páginas del documento se queda exactamente donde estaba, que es lo que hace que la operación sea segura de ejecutar en archivos que no controlas
La cobertura incluye las dos arquitecturas de fuente que se encuentran en la práctica: fuentes TrueType simples y fuentes compuestas Type0/CID, del tipo producido para texto CJK y salida Unicode moderna. El recorrido enumera deliberadamente cada diccionario Font del árbol de objetos del documento en lugar de depender de un recorrido de recursos página por página, así que las fuentes referenciadas desde anotaciones o compartidas entre páginas también se recogen. La API es una sola llamada sobre el documento cargado
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// Recorre cada diccionario Font; devuelve cuántas fuentes
// obtuvieron un programa de fuente. Las fuentes cuyo programa
// no se encuentra en el sistema se omiten, no fallan.
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
Un detalle que merece la pena conocer porque explica por qué la coincidencia de nombres funciona mejor que una comparación de cadenas ingenua: la biblioteca normaliza los nombres BaseFont antes de buscarlos. Los prefijos de subconjunto (el patrón ABCDEF+ de seis letras mayúsculas y un signo más) se eliminan, los sufijos de estilo PostScript como ArialMT se resuelven a Arial, y los archivos TrueType Collection se detectan y desempaquetan para que una cara que vive dentro de un .ttc también se incruste correctamente
Verificar la reparación con un informe de preflight
CreatePreflightReport es el paso de verificación, y el bucle se cierra a propósito: la misma auditoría que condenó el archivo debería ser la que lo apruebe. El código de error 00030 es el hallazgo de auditoría profunda PDF/A que dice "At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)", y se notifica contra el archivo en su conjunto, así que una sola fuente pasada por alto lo mantiene vivo. Ejecuta el informe sobre el archivo de origen, repara, guarda y vuelve a ejecutarlo sobre la salida
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 selecciona las comprobaciones PDF/A
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
Para una vista por fuente en lugar de un veredicto por archivo, carga de nuevo el documento reparado y enumera: FindFonts seguido de SelectFont y GetFontIsEmbedded informa del estado de incrustación fuente por fuente, que es la herramienta adecuada cuando un trabajo por lotes necesita registrar exactamente qué cara de qué archivo no pudo repararse. El mismo patrón de enumeración aparece en el artículo sobre extraer texto, imágenes y fuentes de PDF cargados, donde alimenta la extracción en lugar de la reparación
¿Qué ocurre cuando la fuente no está instalada en el sistema?
EmbedMissingFonts omite cualquier fuente cuyo programa no pueda encontrar, y notifica la omisión mediante su valor de retorno: si el recuento vuelve más bajo que el número de fuentes no incrustadas que contaste, la diferencia son fuentes que el sistema no tiene. Este es el modo de fallo honesto, y es mejor que las alternativas, porque inventar un programa sustituto para una fuente nombrada en el documento cambiaría el renderizado, que es precisamente lo que una reparación de archivo nunca debe hacer. Para estos casos losLab PDF Library proporciona EmbedFontProgramFromFile, que incrusta un .ttf o .otf suministrado por quien llama en la fuente nombrada, de modo que un pipeline puede llevar consigo las fuentes corporativas que espera encontrar y recurrir a ellas de forma deliberada
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// Probar primero la fuente de sistema instalada y luego recurrir
// a un archivo de fuente distribuido junto con la aplicación
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
Dos límites merecen enunciarse con claridad. Primero, las fuentes Type1 no se reparan en la implementación actual: su entrada /FontFile requiere la estructura PFB de tres segmentos con claves de longitud explícitas, y la biblioteca las omite en lugar de escribir un stream malformado; son raras en documentos modernos pero sí aparecen en archivos antiguos. Segundo, incrustar una fuente es un acto de licencia. Los permisos de incrustación de una fuente TrueType pertenecen a su fundición, y un pipeline de reparación que mete programas de fuente con licencia en documentos que salen de la organización debería tener a alguien que confirme que las licencias de fuente realmente lo permiten. La biblioteca hará lo que le pidas; si puedes pedirlo es una cuestión para tu departamento legal, no para tu compilador
Incrustar es necesario, no suficiente
Reparar las fuentes elimina el error 00030, y nada más. Un documento que no supera PDF/A por cifrado, por metadatos XMP ausentes, por un espacio de color dependiente del dispositivo sin OutputIntent o por mapas ToUnicode ausentes seguirá fallando después de incrustar todas las fuentes, y por eso la reparación pertenece dentro de un bucle dirigido por el preflight en lugar de sustituirlo. Ejecuta el informe completo, corrige lo que nombra y deja que el informe te diga cuándo has terminado. Hay también una dimensión de coste: un programa de fuente CJK completo alcanza los megabytes, así que incrustar varios puede inflar drásticamente un documento pequeño. El contrapeso es la subdivisión, cubierta en el artículo sobre optimización del tamaño de archivo PDF y subconjuntos de fuentes, que recorta cada programa incrustado a los glifos que el documento realmente renderiza
Evitar que los documentos nuevos retrocedan
SetEmbedAllFonts es la mitad preventiva de la misma función: una salvaguarda del lado del escritor que impide que tu propio código produzca los documentos que este artículo repara. Con SetEmbedAllFonts(1) activo, cualquier llamada posterior a AddTrueTypeFont que solicite Embed=0 se promociona a una referencia incrustada, lo que extiende a todos los documentos la garantía que el modo PDF/A ya impone. Afecta a las fuentes añadidas después de la llamada, no a las fuentes que ya están en un archivo cargado, así que la división del trabajo es limpia: SetEmbedAllFonts para los documentos que creas, EmbedMissingFonts para los documentos que heredas
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// A partir de aquí, AddTrueTypeFont(Name, 0) se comporta
// como AddTrueTypeFont(Name, 1): ninguna referencia
// no incrustada puede llegar al archivo de salida
Ambas mitades, la salvaguarda del lado del escritor y la ruta cargar-reparar-guardar, forman parte de losLab PDF Library para Delphi, C# y VB.NET, junto con el motor de preflight que verifica el resultado; la página del producto lleva la referencia completa de la API de fuentes, incluidas las llamadas de incrustación y subdivisión por fuente