Artigo Técnico

Incorporar Tipos de Letra em Falta em PDFs Existentes para PDF/A no Delphi

A losLab PDF Library pode incorporar os programas de tipo de letra em falta de um PDF já carregado com uma única chamada: EmbedMissingFonts percorre todos os dicionários de tipos de letra no documento, localiza o tipo de letra do sistema instalado correspondente pelo seu nome BaseFont e escreve o programa do tipo de letra de volta no ficheiro. Para equipas que reparam documentos de terceiros que falham na validação PDF/A na incorporação de tipos de letra, esta é a correção que faz desaparecer o erro de preflight 00030

O cenário é deprimentemente comum. Um fluxo de receção de arquivo recebe PDFs de fornecedores, clientes ou de um serviço de digitalização; os documentos são renderizados sem problemas em qualquer computador do edifício; e depois o validador PDF/A rejeita todo o lote com a mesma queixa repetida uma vez por ficheiro: pelo menos um tipo de letra não está incorporado. Ninguém a montante irá regenerar os ficheiros, pelo que o fluxo de trabalho tem de os reparar. Este artigo aborda esse caminho de reparação. É o companheiro do artigo de preflight, que aborda a deteção de violações PDF/A e PDF/UA: esse texto indica quais os documentos que estão corrompidos, este corrige a forma mais frequente como estão corrompidos

Por que razão exige o PDF/A que todos os tipos de letra sejam incorporados?

A norma ISO 19005-1 §6.3.4 exige que cada tipo de letra utilizado por um documento em conformidade traga o seu programa de tipo de letra dentro do ficheiro, porque a promessa do PDF/A é a reprodutibilidade: o documento deve ser renderizado de forma idêntica numa máquina daqui a cinquenta anos que não partilhe quaisquer tipos de letra com a máquina que o produziu. Um tipo de letra não incorporado é uma instrução para procurar a Arial algures no sistema de visualização, e a posição da norma é que "algures no sistema de visualização" não é uma garantia de arquivo. Quaisquer que sejam os glifos, métricas e cobertura que o tipo de letra de substituição tenha, é isso que o leitor obtém, e pode não ser o que o autor viu

O culpado histórico é a convenção Standard 14. O PDF 1.0 prometia que cada visualizador trazia Helvetica, Times, Courier, Symbol e ZapfDingbats, pelo que os geradores aprenderam a referenciar esses tipos de letra por nome e a não incorporar nada, e trinta anos de ferramentas ainda fazem exatamente isso. A losLab PDF Library leva este requisito suficientemente a sério para que, no modo de criação PDF/A, AddStandardFont seja deliberadamente uma instrução sem efeito (no-op): a biblioteca não fornece os programas dos tipos de letra Standard 14, não pode incorporar o que não tem e recusa-se a escrever uma referência não incorporada num documento que reivindique conformidade. Devolve 0 sem selecionar um tipo de letra, pelo que um documento PDF/A deve utilizar AddTrueTypeFont com incorporação e qualquer pedido de Embed=0 é promovido silenciosamente a Embed=1 enquanto o modo PDF/A estiver ativo. Este é o lado do escritor. O problema mais difícil é o lado do leitor: um documento que outra pessoa já escreveu, cheio de dicionários de tipos de letra que não criou

Como é que o EmbedMissingFonts repara um documento carregado?

A losLab PDF Library repara os tipos de letra no local em vez de os reconstruir. Quando um gerador de PDF escreve um tipo de letra TrueType não incorporado, o dicionário FontDescriptor que produz já está completo: FontName, FontBBox, Flags, Ascent, Descent, StemV, todos presentes. A única coisa que o separa de um tipo de letra incorporado é a ausência de uma entrada, a referência de fluxo /FontFile2 que contém o programa real do tipo de letra. Por isso, o EmbedMissingFonts não toca no dicionário do tipo de letra, na codificação, no array de larguras ou em qualquer fluxo de conteúdo que referencie o tipo de letra pelo nome do recurso. Ele lê o programa de tipo de letra correspondente a partir do sistema, comprime-o num novo objeto de fluxo e anexa uma única referência /FontFile2 (ou /FontFile3 para tipos de letra CIDFontType0) ao FontDescriptor que já lá está. Tudo o que as páginas do documento apontam permanece exatamente onde estava, o que torna a operação segura para executar em ficheiros que não controla

A cobertura inclui ambas as arquiteturas de tipos de letra que encontrará na prática: tipos de letra TrueType simples e tipos de letra compostos Type0/CID, do tipo produzido para texto CJK e saída Unicode moderna. A travessia enumera deliberadamente todos os dicionários Font na árvore de objetos do documento, em vez de depender de uma pesquisa de recursos página a página, pelo que os tipos de letra referenciados a partir de anotações ou partilhados entre páginas também são recolhidos. A API é uma chamada única no documento carregado

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;

Um detalhe que vale a pena saber porque explica por que razão a correspondência de nomes funciona melhor do que uma comparação ingénua de strings: a biblioteca normaliza os nomes BaseFont antes de os procurar. Os prefixos de subconjunto (o padrão ABCDEF+ de seis letras maiúsculas e um sinal de mais) são removidos, os sufixos de estilo PostScript como ArialMT resolvem-se para Arial, e os ficheiros TrueType Collection são detetados e descompactados para que um tipo de letra contido num .ttc ainda seja incorporado corretamente

Verificar a reparação com um relatório de preflight

O CreatePreflightReport é a etapa de verificação, e o ciclo é deliberadamente fechado: a mesma auditoria que condenou o ficheiro deve ser a que o liberta. O código de erro 00030 é a conclusão da auditoria profunda de PDF/A que indica "Pelo menos um tipo de letra não está incorporado (FontFile/FontFile2/FontFile3 em falta)", e é reportado em relação ao ficheiro como um todo, pelo que um único tipo de letra esquecido o mantém ativo. Execute o relatório no ficheiro de origem, repare, grave e execute-o novamente na saída

function HasFontEmbeddingViolation(PDF: TPDFlib;
  const FileName: string): Boolean;
var
  Report: string;
begin
  // ComplianceTests = 1 selects the PDF/A checks
  Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
  Result := Pos('00030', Report) > 0;
end;

Para uma visualização por tipo de letra em vez de um veredicto por ficheiro, carregue novamente o documento reparado e enumere: FindFonts seguido de SelectFont e GetFontIsEmbedded reporta o estado de incorporação tipo de letra a tipo de letra, que é a ferramenta certa quando um trabalho em lote necessita de registar exatamente qual o tipo de letra em qual ficheiro não pôde ser reparado. O mesmo padrão de enumeração aparece no artigo sobre a extração de texto, imagens e tipos de letra de PDFs carregados, onde alimenta a extração em vez da reparação

O que acontece quando o tipo de letra não está instalado no sistema?

O EmbedMissingFonts ignora qualquer tipo de letra cujo programa não consiga encontrar e reporta essa omissão através do seu valor de retorno: se a contagem for inferior ao número de tipos de letra não incorporados que contou, a diferença corresponde aos tipos de letra que o sistema não possui. Este é o modo de falha honesto e é melhor do que as alternativas, porque inventar um programa de substituição para um tipo de letra nomeado no documento alteraria a renderização, que é precisamente o que uma reparação de arquivo nunca deve fazer. Para estes casos, a losLab PDF Library disponibiliza o EmbedFontProgramFromFile, que incorpora um .ttf ou .otf fornecido pelo chamador no tipo de letra nomeado, para que um fluxo de trabalho possa distribuir os tipos de letra corporativos que espera encontrar e recorrer a eles deliberadamente

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;

Dois limites merecem ser expostos claramente. Primeiro, os tipos de letra Type1 não são reparados na implementação atual: a sua entrada /FontFile exige a estrutura PFB de três segmentos com chaves de comprimento explícitas, e a biblioteca ignora-os em vez de escrever um fluxo malformado; são raros em documentos modernos, mas aparecem em arquivos antigos. Segundo, incorporar um tipo de letra é um ato de licenciamento. As permissões de incorporação de um tipo de letra TrueType pertencem ao seu fabricante (foundry), e um fluxo de reparação que insira programas de tipos de letra licenciados em documentos que saem da organização deve ter alguém a confirmar se as licenças dos tipos de letra realmente o permitem. A biblioteca fará o que solicitar; se pode faz-lo é uma questão para o seu departamento jurídico, não para o seu compilador

A incorporação é necessária, mas não suficiente

A reparação de tipos de letra resolve o erro 00030, e nada mais. Um documento que falhe no PDF/A devido a encriptação, falta de metadados XMP, um espaço de cores dependente do dispositivo sem um OutputIntent ou mapas ToUnicode ausentes continuará a falhar após a incorporação de todos os tipos de letra, razão pela qual a reparação pertence a um ciclo baseado em preflight, em vez de o substituir. Execute o relatório completo, corrija o que ele indicar e deixe que o relatório lhe diga quando terminar. Existe também uma dimensão de custo: um programa completo de tipo de letra CJK ocupa megabytes, pelo que a incorporação de vários deles pode inflacionar drasticamente um documento pequeno. O contrapeso é o subsetting, abordado no artigo sobre otimização do tamanho de ficheiro PDF e subsetting de tipos de letra, que reduz cada programa incorporado aos glifos que o documento realmente renderiza

Evitar que novos documentos retrocedam

O SetEmbedAllFonts é a metade de prevenção da mesma funcionalidade: uma proteção do lado do escritor que impede o seu próprio código de produzir os documentos que este artigo repara. Com o SetEmbedAllFonts(1) ativo, any subsequente AddTrueTypeFont chamada que solicite Embed=0 é promovida a uma referência incorporada, o que estende a cada documento a garantia que o modo PDF/A já impõe. Afeta tipos de letra adicionados após a chamada, não tipos de letra já presentes num ficheiro carregado, pelo que a divisão do trabalho é clara: SetEmbedAllFonts para os documentos que cria, EmbedMissingFonts para os documentos que herda

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 as metades, a proteção do lado do escritor e o caminho de carregar-reparar-gravar, fazem parte da losLab PDF Library para Delphi, C# e VB.NET, juntamente com o motor de preflight que verifica o resultado; a página do produto contém a referência completa da API de tipos de letra, incluindo as chamadas de incorporação e subsetting por tipo de letra