losLab PDF Library puede incrustar los programas de fuente faltantes 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 pasan 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 ingestión para archivado recibe PDF de proveedores, clientes o una oficina de digitalización; los documentos se renderizan bien en todos los escritorios del edificio; y luego 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 en el origen 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 de preflight, que cubre la detección de infracciones PDF/A y PDF/UA: aquella pieza le 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 toda 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 de ir a buscar Arial en algún lugar del sistema de visualización, y la postura del estándar es que "en algún lugar del sistema de visualización" no es una garantía de archivado. Cualesquiera que 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 prometió que todo visor incluye Helvetica, Times, Courier, Symbol y ZapfDingbats, así que los generadores aprendieron a referenciar esas fuentes por nombre y 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 el 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 en su lugar, y cualquier solicitud Embed=0 se promueve silenciosamente 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 alguien más ya escribió, lleno de diccionarios de fuente que usted no creó
¿Cómo repara EmbedMissingFonts un documento cargado?
losLab PDF Library repara las fuentes en su lugar en vez 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 flujo /FontFile2 que contiene el programa de fuente real. Así que EmbedMissingFonts no toca el diccionario de fuente, la codificación, el arreglo de anchos ni ningún flujo de contenido que referencie la fuente por nombre de recurso. Lee el programa de fuente coincidente desde el sistema, lo comprime en un nuevo objeto de flujo y agrega 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 sobre archivos que usted no controla
La cobertura incluye las dos arquitecturas de fuente que encontrará en la práctica: fuentes TrueType simples y fuentes compuestas Type0/CID, del tipo que se produce 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, de modo que también se detectan las fuentes referenciadas desde anotaciones o compartidas entre páginas. 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
// recibieron 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 vale la pena conocer porque explica por qué la coincidencia de nombres funciona mejor que una comparación ingenua de cadenas: 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 al estilo PostScript como ArialMT se resuelven a Arial, y los archivos TrueType Collection se detectan y desempaquetan, de modo que una tipografía que vive dentro de un .ttc también se incrusta correctamente
Verificar la reparación con un reporte de preflight
CreatePreflightReport es el paso de verificación, y el ciclo se cierra deliberadamente: la misma auditoría que condenó el archivo debe ser la que lo apruebe. El código de error 00030 es el hallazgo de la auditoría profunda PDF/A que dice "At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)", y se reporta contra el archivo en su conjunto, así que una sola fuente pasada por alto lo mantiene vivo. Ejecute el reporte sobre el archivo de origen, repare, guarde y vuelva 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, vuelva a cargar el documento reparado y enumere: FindFonts seguido de SelectFont y GetFontIsEmbedded reporta el estado de incrustación fuente por fuente, que es la herramienta adecuada cuando un trabajo por lotes necesita registrar exactamente qué tipografía de qué archivo no pudo repararse. El mismo patrón de enumeración aparece en el artículo sobre extracción de 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 puede encontrar, y reporta la omisión mediante su valor de retorno: si el conteo resulta menor que el número de fuentes no incrustadas que usted contó, la diferencia son fuentes que el sistema no tiene. Este es el modo de falla 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 con fines de archivado nunca debe hacer. Para estos casos losLab PDF Library ofrece EmbedFontProgramFromFile, que incrusta un .ttf o .otf suministrado por quien llama en la fuente nombrada, de modo que un pipeline puede incluir 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
// Pruebe primero la fuente de sistema instalada y luego
// recurra a un archivo de fuente incluido 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 flujo malformado; son raras en documentos modernos, pero sí aparecen en repositorios antiguos. Segundo, incrustar una fuente es un acto de licenciamiento. 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 las fuentes realmente lo permiten. La biblioteca hará lo que usted le pida; si usted puede pedirlo es una pregunta para su departamento legal, no para su compilador
La incrustación es necesaria, no suficiente
Reparar las fuentes elimina el error 00030, y nada más. Un documento que no pasa PDF/A por cifrado, por metadatos XMP faltantes, por un espacio de color dependiente del dispositivo sin un 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 ciclo dirigido por preflight en lugar de reemplazarlo. Ejecute el reporte completo, corrija lo que nombra y deje que el reporte le diga cuándo ha terminado. También hay una dimensión de costo: un programa de fuente CJK completo llega a los megabytes, así que incrustar varios puede inflar drásticamente un documento pequeño. El contrapeso es el subconjunto, cubierto 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 protección del lado del escritor que impide que su 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 promueve 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 agregadas 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 usted crea, EmbedMissingFonts para los documentos que usted hereda
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 protección 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 contiene la referencia completa de la API de fuentes, incluidas las llamadas de incrustación y subconjunto por fuente