Artículo técnico

Incrustar fuentes faltantes en archivos PDF existentes para PDF/A en Delphi

losLab PDF Library puede incrustar los programas de fuentes faltantes de un PDF ya cargado con una sola llamada: EmbedMissingFonts recorre cada diccionario de fuentes en el documento, localiza la fuente del sistema instalada correspondiente por su nombre BaseFont y escribe el programa de la fuente de regreso en el archivo. Para los equipos que reparan documentos de terceros que fallan en la validación de PDF/A debido a la incrustación de fuentes, esta es la solución que hace desaparecer el error de comprobación preliminar 00030

El escenario es lamentablemente común. Un flujo de ingesta de archivos recibe archivos PDF de proveedores, clientes o una oficina de escaneo; los documentos se representan bien en cada escritorio 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 los pasos previos regenerará los archivos, por lo que el flujo tiene que repararlos. Este artículo cubre esa ruta de reparación. Es el complemento del artículo sobre comprobación preliminar, que cubre la detección de infracciones de PDF/A y PDF/UA: ese texto le indica qué documentos están dañados, y este repara la forma más frecuente en que se dañan

¿Por qué PDF/A requiere que se incrusten todas las fuentes?

La norma ISO 19005-1 §6.3.4 requiere que cada fuente utilizada 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 representarse de manera 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 posición de la norma es que "en algún lugar del sistema de visualización" no es una garantía de archivo. Cualesquiera que sean los glifos, las métricas y la cobertura que tenga la fuente sustituta, eso es lo que obtiene el lector, y podría no ser lo que vio el autor

El culpable histórico es la convención Standard 14. PDF 1.0 prometía que cada visor incluía Helvetica, Times, Courier, Symbol y ZapfDingbats, por lo que los generadores aprendieron a hacer referencia a esas fuentes por su nombre y a no incrustar nada, y treinta años de herramientas siguen haciendo exactamente eso. losLab PDF Library se toma este requisito con la suficiente seriedad como para que en el modo de creación de PDF/A AddStandardFont sea deliberadamente una operación sin efecto: la biblioteca no distribuye los programas de fuentes de Standard 14, no puede incrustar lo que no tiene y se niega a escribir una referencia no incrustada en un documento que reclama conformidad. Devuelve 0 sin seleccionar una fuente, por lo que un documento PDF/A debe usar AddTrueTypeFont con incrustación en su lugar, y cualquier solicitud de 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 fuentes 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 de flujo /FontFile2 que contiene el programa de fuente real. Así que EmbedMissingFonts no toca el diccionario de fuentes, la codificación, la matriz de anchos ni ningún flujo de contenido que haga referencia a la fuente por su nombre de recurso. Lee el programa de fuente correspondiente del sistema, lo comprime en un nuevo objeto de flujo y añade una única referencia /FontFile2 (o /FontFile3 para fuentes CIDFontType0) al FontDescriptor que ya está allí. Todo a lo que apuntan las páginas del documento permanece exactamente donde estaba, lo que hace que la operación sea segura de ejecutar en archivos que usted no controla

La cobertura incluye ambas arquitecturas de fuentes que encontrará en la práctica: fuentes TrueType simples y fuentes compuestas Type0/CID, del tipo producido para texto CJK y salidas Unicode modernas. El recorrido enumera deliberadamente cada diccionario de fuentes en el árbol de objetos del documento en lugar de depender de un recorrido de recursos página por página, por lo que las fuentes referenciadas desde anotaciones o compartidas entre páginas también se recopilan. La API es una sola llamada en 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');

    // Walks every Font dictionary; returns how many fonts
    // gained a font program. Fonts whose program cannot be
    // found on the system are skipped, not failed.
    Repaired := PDF.EmbedMissingFonts;
    Writeln(Format('%d font program(s) embedded', [Repaired]));

    PDF.SaveToFile('supplier-invoice-repaired.pdf');
  finally
    PDF.Free;
  end;
end;

Verificación de la reparación con un informe de comprobación preliminar

CreatePreflightReport es el paso de verificación y el ciclo se cierra deliberadamente: la misma auditoría que condenó al archivo debería ser la que lo limpie. El código de error 00030 es el hallazgo de auditoría profunda de PDF/A que dice "Al menos una fuente no está incrustada (falta FontFile/FontFile2/FontFile3)" y se informa para el archivo en su totalidad, por lo que una sola fuente pasada por alto lo mantiene activo. Ejecute el informe en el archivo de origen, repárelo, guárdelo y ejecútelo de nuevo en el de salida

Para obtener una vista por fuente en lugar de un veredicto por archivo, cargue el documento reparado nuevamente y enumere: FindFonts seguido de SelectFont y GetFontIsEmbedded informa el estado de incrustación fuente por fuente, que es la herramienta adecuada cuando un trabajo por lotes necesita registrar exactamente qué tipo de letra en qué archivo no se pudo reparar. El mismo patrón de enumeración aparece en el artículo sobre extracción de texto, imágenes y fuentes de archivos PDF cargados, donde alimenta la extracción en lugar de la reparación

¿Qué sucede cuando la fuente no está instalada en el sistema?

EmbedMissingFonts omite cualquier fuente cuyo programa no pueda encontrar e informa la omisión a través de su valor de retorno: si el recuento resulta menor que el número de fuentes no incrustadas que contó, la diferencia son las 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 la representación, que es precisamente lo que una reparación de archivo nunca debe hacer. Para estos casos, losLab PDF Library proporciona EmbedFontProgramFromFile, que incrusta un archivo .ttf u .otf suministrado por el llamador en la fuente nombrada, de modo que un flujo de trabajo pueda distribuir las fuentes corporativas que espera encontrar y recurrir a ellas de manera 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
        // Try the installed system font first, then fall back
        // to a font file shipped alongside the application
        if PDF.EmbedFontProgram(PDF.FontName) = 0 then
          PDF.EmbedFontProgramFromFile(PDF.FontName,
            'fonts\CorporateSans.ttf');
  end;
end;

Dos límites merecen ser expuestos claramente. 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 mal formado; son raras en documentos modernos pero 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 flujo de reparación que introduce programas de fuentes licenciados en documentos que salen de la organización debe hacer que alguien confirme que las licencias de las fuentes realmente lo permiten. La biblioteca hará lo que usted le pida; si se le permite pedirlo es una pregunta para su departamento legal, no para su compilador

La incrustación es necesaria, no suficiente

Reparar las fuentes borra el error 00030 y nada más. Un documento que falla en PDF/A debido a cifrado, metadatos XMP faltantes, un espacio de color dependiente del dispositivo sin un OutputIntent o mapas ToUnicode ausentes seguirá fallando después de incrustar cada fuente, razón por la cual la reparación pertenece a un ciclo impulsado por la comprobación preliminar en lugar de reemplazarlo. Ejecute el informe completo, corrija lo que nombre y deje que el informe le indique cuándo ha terminado. También hay una dimensión de costo: un programa completo de fuentes CJK ocupa megabytes, por lo que incrustar varios de ellos puede inflar drásticamente un documento pequeño. El contrapeso es la creación de subconjuntos, cubierta en el artículo sobre la optimización del tamaño del archivo PDF y la creación de subconjuntos de fuentes, que reduce cada programa incrustado a los glifos que el documento realmente representa

Evitar que los nuevos documentos sufran regresiones

SetEmbedAllFonts es la mitad preventiva de la misma característica: una protección del lado del escritor que evita 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 cada documento 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, por lo que la división del trabajo es clara: SetEmbedAllFonts para los documentos que crea, EmbedMissingFonts para los documentos que hereda

PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file

Ambas mitades, la protección del lado del escritor y la ruta de cargar-reparar-guardar, forman parte de losLab PDF Library para Delphi, C# y VB.NET, junto con el motor de comprobación preliminar 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 creación de subconjuntos por fuente