O PDFium Component grava texto em uma página PDF no nível de glifo com AddCidType2Text, incorporando dados TrueType que você fornece como uma fonte CID Type 2. Ele atribui a cada instância de glifo um CID sequencial, gera o mapa explícito CID-to-GID e o CMap ToUnicode, e mescla glifos na mesma linha de base em um único objeto de texto nativo. A subsetagem opcional mantém o arquivo pequeno, com uma política explícita para o que acontece quando a subsetagem falha
A gravação em nível de glifo é o que você precisa quando o texto já foi moldado (shaped). Árabe, devanágari e qualquer script com formas contextuais produzem uma sequência de identificadores de glifo que não mapeia mais um a um para caracteres, de modo que uma API que recebe uma string e um nome de fonte não consegue expressar o resultado. Entregar glifos e posições diretamente é a única maneira de colocar texto de script complexo corretamente moldado em um PDF
Por que cada instância de glifo recebe seu próprio CID
A otimização tentadora é deduplicar: um CID por identificador de glifo distinto, reutilizado onde quer que esse glifo apareça. Isso produz uma fonte menor e quebra a extração de texto, porque o mesmo glifo pode legitimamente corresponder a conteúdo Unicode diferente em lugares diferentes
Um glifo de ligadura é o caso claro. O mesmo glifo "ffi" pode representar os três caracteres em uma palavra e, após uma decisão de shaping diferente, outra coisa em outro lugar. O mapa ToUnicode é indexado por CID, então um CID compartilhado só pode carregar um mapeamento, e qualquer texto que perca essa disputa se torna inextraível
Assim, cada instância de glifo recebe seu próprio CID, e cada mapeamento é livre para ser várias unidades de código UTF-16. Um glifo sem texto significativo — um elemento decorativo, um glifo apenas de posicionamento — mapeia explicitamente para U+200B, um espaço de largura zero, de modo que todo CID tem um resultado de extração observável, em vez de uma lacuna
uses
PDFium;
var
Pdf: TPdf;
Glyphs: TPdfCidGlyphs;
Options: TPdfCidFontOptions;
Report: TPdfCidFontReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'label.pdf';
Pdf.LoadDocument;
Pdf.PageNumber := 1;
// Uma entrada por glifo moldado: o id do glifo, o texto que ele
// representa e seu avanço e deslocamentos no espaço de texto
SetLength(Glyphs, 3);
Glyphs[0].GlyphID := 402; Glyphs[0].UnicodeText := 'ffi';
Glyphs[0].Advance := 18.4;
Glyphs[1].GlyphID := 71; Glyphs[1].UnicodeText := 'c';
Glyphs[1].Advance := 9.8;
Glyphs[2].GlyphID := 74; Glyphs[2].UnicodeText := 'e';
Glyphs[2].Advance := 9.2;
Options := TPdfCidFontOptions.Default; // subset preferido
Options.VerifyExtraction := True;
if Pdf.AddCidType2Text(LoadFileBytes('NotoSans.ttf'), Glyphs,
11, 72, 700, Options, Report) then
Writeln(Format('%d glyphs, %d unique, font %d -> %d bytes, match=%s',
[Report.GlyphCount, Report.UniqueGlyphCount,
Report.OriginalFontBytes, Report.EmbeddedFontBytes,
BoolToStr(Report.ExtractionMatches, True)]));
finally
Pdf.Free;
end;
end;
Subsetagem, e as verificações que um código de retorno não dá
Fazer o subset de uma fonte apenas para os glifos realmente usados é a diferença entre incorporar 300 KB e incorporar 12 KB, e em um documento com várias fontes isso decide se o arquivo pode ser enviado por e-mail. O caminho de subsetagem da plataforma aceita uma lista de glifos diretamente, o que se encaixa perfeitamente nessa API, já que o chamador já conhece todo identificador de glifo em uso
O que isso não dá é confiança. Uma chamada de subsetagem pode relatar sucesso e retornar uma saída inutilizável, então o componente valida três propriedades antes de aceitar o resultado: a saída precisa ser menor que a original, precisa ser reanalisada como um sfnt válido com uma tabela maxp legível, e precisa reter glifos até o identificador original mais alto solicitado. Qualquer falha significa que o subset é rejeitado
O que acontece a seguir é a política do chamador. Em pcfemSubsetPreferred, o padrão, um subset rejeitado recorre à incorporação da fonte completa, de modo que a página fica correta e apenas maior. Em pcfemSubsetRequired, a operação falha antes de a página ser modificada, que é o que um pipeline com restrição de tamanho quer. pcfemFull pula a subsetagem inteiramente. O relatório diz qual caminho foi seguido por meio de SubsetAttempted, SubsetApplied, UsedFullFontFallback e SubsetErrorCode
Verificação que não pode produzir um falso positivo
Com VerifyExtraction ativado, o componente confirma que o que gravou pode ser lido de volta. O jeito ingênuo de fazer isso é extrair a página inteira e procurar a string esperada, e está errado: uma página que já continha esse texto passa na verificação mesmo quando o novo texto foi gravado incorretamente
Em vez disso, a página de texto é reconstruída e os identificadores de objeto inseridos por essa chamada são lidos individualmente, na ordem de inserção, e concatenados. O resultado é comparado com o texto esperado, e o relatório expõe ambas as strings junto com o booleano, de modo que uma discrepância pode ser diagnosticada, em vez de meramente detectada
Ative isso durante o desenvolvimento e em qualquer pipeline onde a extraibilidade seja uma exigência — arquivos pesquisáveis, conformidade de acessibilidade, mineração de texto posterior. O custo é uma reconstrução de página de texto por chamada, razão pela qual não vem ativado por padrão em loops apertados
Orçamentos e falha atômica
Bytes de fonte, instâncias de glifo e unidades de código Unicode têm cada um um teto antes da alocação, e o formato de fonte, o índice TTC, a faixa de identificador de glifo e os valores de geometria são validados antes de qualquer coisa ser gravada. A geometria precisa ser finita, o que soa óbvio até um motor de shaping entregar a você um avanço NaN vindo de uma fonte malformada
A falha é atômica no nível da página. Se o carregamento da fonte, a gravação de objetos, a geração de conteúdo ou a verificação de extração falharem, todo objeto inserido por essa chamada é removido em ordem reversa e o conteúdo da página é regenerado. Uma chamada malsucedida deixa a página como estava, não com meia sequência de texto nela
Onde isso se encaixa em um pipeline de texto
Vale a pena declarar a divisão de trabalho claramente. Shaping — transformar caracteres em glifos posicionados — não é o trabalho dessa API; pertence a um motor de shaping, e o suporte bidirecional e de script complexo do próprio componente é abordado em tratamento de emoji, CJK e pares substitutos. Essa API é o que você chama depois, com a sequência de glifos que o shaper produziu
Para texto latino comum sem shaping contextual, as APIs de texto mais simples baseadas em string são a ferramenta certa e produzem código menor. Recorra à gravação CID Type 2 quando você tiver saída moldada, quando precisar de controle exato dos identificadores de glifo, ou quando a fonte precisar ser incorporada a partir de bytes que você já tem, em vez de resolvida por nome — a alternativa de resolução de fonte sendo o mecanismo de provedor descrito em controlando a substituição de fontes em PDF
Uma observação de implantação: incorporar uma fonte é uma questão de licenciamento tanto quanto técnica. As fontes variam quanto a se a incorporação é permitida, permitida apenas para visualização, ou permitida para edição. A biblioteca vai incorporar quaisquer bytes que você fornecer a ela, e verificar a licença é responsabilidade sua, não do formato de arquivo
A gravação de texto em nível de glifo, o provisionamento de fontes e a extração de texto compartilham o mesmo modelo de página no Delphi, no C++Builder e no Lazarus; a API completa está descrita na página do PDFium Component para Delphi