O PDFlibPas resolve caracteres que a fonte selecionada não consegue desenhar buscando em uma cadeia de fallback de faces instaladas, cluster por cluster, preservando o shaping e a ordem das runs bidirecionais. Você ativa isso com SetAutomaticFontFallback, estende a cadeia com AddFontFallback, e apenas as fontes de fallback efetivamente usadas na saída são incorporadas ao arquivo
O problema que ele resolve é um que todo gerador de documentos encontra na primeira vez em que o nome de um cliente chega em uma escrita que a fonte do template nunca previu. A falha é silenciosa, e é isso que a torna cara
Por que texto não suportado desaparece em vez de gerar um erro?
Porque o PDF não tem conceito de uma fonte que não consegue desenhar um caractere. Uma fonte simples mapeia códigos de byte para nomes de glifo por meio de um encoding; uma fonte composta mapeia códigos por meio de um CMap para índices de glifo. Peça um glifo que a face não contém e você recebe o índice de glifo zero, .notdef, que a maioria das faces desenha como nada ou como uma caixa vazia. O arquivo é estruturalmente válido, o operador de texto está bem formado, e a página renderiza. Só fica em branco onde o nome deveria estar
Nada na ISO 32000-1 exige que um produtor perceba isso. Um gerador que grava texto sem verificar a cobertura produz um PDF tecnicamente conforme que perdeu conteúdo silenciosamente, e a perda aparece na tela de um cliente semanas depois. É por isso que o recurso de fallback e o relatório de glifos ausentes vêm juntos: resolver o que pode ser resolvido é só metade do trabalho, e relatar o que não pôde ser resolvido é a outra metade
O fallback acontece por cluster, não por code point
Granularidade é o detalhe que separa uma implementação que funciona de uma que só parece funcionar. Texto não é uma sequência de caracteres independentes. Uma sílaba em devanagari, um emoji com modificador de tom de pele, uma letra base com marcas combinantes: cada um é um cluster que precisa ser renderizado por uma única fonte, porque as decisões de shaping dentro dele dependem de tabelas daquela face
O PDFlibPas resolve por clusters, então um cluster coberto por uma face de fallback é desenhado inteiramente por aquela face. Dividir no meio do cluster e desenhar metade com a fonte primária e metade com um fallback produziria um resultado tecnicamente presente mas visivelmente quebrado, o que é discutivelmente pior do que o branco de onde você partiu. A ordem das runs também é preservada, então um fallback dentro de uma run da direita para a esquerda não reordena o texto ao redor; o mesmo mecanismo sustenta o layout vertical descrito em escrita vertical para japonês e chinês
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Ordem de busca: o primeiro match vence, então coloque as faces mais abrangentes por último
Lib.AddFontFallback('Microsoft YaHei'); // Chinês 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 = incorpora a face
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 a cadeia deliberadamente. A resolução usa a primeira face que cobre o cluster, então uma fonte pan-Unicode ampla colocada primeiro vai vencer quase tudo, e suas faces específicas de escrita, cuidadosamente escolhidas, nunca serão consultadas. Coloque as faces específicas primeiro e a coringa por último
Relatar ou abortar: qual falha você quer?
SetMissingGlyphPolicy aceita PDF_MISSING_GLYPH_REPORT, o padrão compatível, ou PDF_MISSING_GLYPH_ABORT. Sob a política de relatório, a operação de texto prossegue, code points não resolvíveis são descartados como antes, e cada um é registrado. Sob a política de abortar, a operação de texto é rejeitada antes que qualquer conteúdo seja gravado, e LastErrorCode é definido como 521
Escolha com base no propósito do documento. Um lote de relatórios internos deve continuar renderizando e registrar as lacunas em log, porque um relatório um pouco incompleto hoje é melhor do que nenhum relatório. Um contrato juridicamente vinculante, uma nota fiscal, ou qualquer coisa com um nome nela, deve abortar, porque um caractere silenciosamente descartado no nome de uma parte é um defeito que você quer descobrir no seu próprio processo, e não em uma disputa. A política de abortar falha antes de gravar, então nenhum content stream pela metade fica para trás
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... construa o 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;
O relatório é deliberadamente legível por máquina e limitado. Cada evento carrega a página, o índice UTF-16 dentro da string, o code point tanto na forma numérica quanto na forma U+XXXX, a fonte que foi selecionada, seu tipo e a operação que encontrou o problema, de modo que um chamado de suporte consegue nomear o caractere exato em vez de descrever um sintoma. O rastreador mantém os 256 eventos mais recentes, o que é suficiente para diagnosticar um documento e pequeno o bastante para que uma execução patológica não transforme diagnóstico em problema de memória
Medição e desenho precisam concordar
A medição de largura usa as mesmas decisões de fallback cientes de cluster que o desenho. Isso parece óbvio e é justamente o que a maioria das camadas de fallback caseiras erra: elas remendam o caminho de desenho, deixam a medição na fonte primária, e toda caixa de texto, alinhamento à direita e coluna de tabela acaba sendo calculada a partir de larguras que não correspondem ao que foi renderizado
Como os dois caminhos compartilham a resolução, uma string medida antes de ser desenhada ocupa a largura na qual foi medida, incluindo as runs de fallback. É isso que torna seguro ativar o fallback globalmente, em vez de apenas nos lugares que você auditou manualmente
Só o que você usou é incorporado
As fontes de fallback são incorporadas de forma preguiçosa: uma face na cadeia que nunca resolveu um cluster não contribui em nada para a saída. Um documento contendo um caractere chinês e 5.000 latinos não carrega uma face CJK completa; ele carrega o que a passagem de subsetting produziu para aquele único glifo, o comportamento descrito em otimização de tamanho de arquivo e subsetting de fonte
Essa preguiça torna uma cadeia ampla barata de configurar. Registre as faces que seu conjunto de documentos pode precisar em cada locale que você atende, e cada PDF individual paga apenas pelo que efetivamente usou. Para documentos que você não gerou, onde as faces ausentes já estão dentro de um arquivo existente, o caminho de reparo é diferente e é abordado em incorporando fontes ausentes em um PDF existente
Vale declarar claramente uma ressalva de implantação: o fallback resolve contra faces instaladas na máquina que executa o código. Um servidor sem fontes CJK instaladas não tem para onde recorrer, e o relatório vai dizer isso já no primeiro documento, em vez de depois da primeira reclamação. Distribua as fontes das quais você depende, e confirme o licenciamento para incorporá-las
O PDFlibPas é uma biblioteca de PDF para Delphi, C++Builder e Lazarus com interfaces DLL e ActiveX correspondentes, de modo que as APIs de fallback e de glifos ausentes também estão disponíveis para chamadores fora do Pascal. A documentação completa está na página da biblioteca PDFlibPas Delphi para PDF