Cada caráter visível num PDF contém uma referência à fonte que o desenhou, e o PDFium VCL permite-lhe seguir essa referência de volta para o objeto de fonte e ler o que ele sabe. A unidade de acesso é o caráter, não o documento: escolhe um caráter pelo seu índice no texto da página e solicita o nome da família, o nome base, a espessura (weight), o ângulo itálico e se o tipo de letra subjacente está realmente contido dentro do ficheiro. Essa última propriedade é a que a maioria das análises realmente procura, porque uma fonte incorporada viaja com o documento e uma não incorporada é apenas uma promessa de que a máquina do leitor terá o mesmo tipo de letra instalado.
O componente expõe estas propriedades através dos mesmos objetos TPdf e TPdfView que utiliza para renderização e extração de texto. Não existe um objeto "tabela de fontes" separado para abrir. Após o texto de uma página ter sido analisado, as propriedades da fonte ficam associadas ao índice do caráter, e pode lê-las um glifo de cada vez. Esse design adequa-se à forma como o PDF armazena as informações em primeiro lugar: uma única página pode alternar de fonte dezenas de vezes, e a única resposta honesta para "qual é a fonte deste documento" é "depende de qual caráter se refere".
Ler a fonte por trás de um caráter
A operação útil mais simples é obter o índice de um caráter e extrair tudo o que o PDFium pode dizer sobre a sua fonte. Todas as propriedades de fonte no TPdf e TPdfView são indexadas pela posição do caráter, pelo que o índice se aplica a todas elas. A página também tem de ser a página atual para que o índice seja resolvido em relação ao texto correto, o que é importante quando se avança além da primeira página.
procedure DescribeFontAt(Pdf: TPdf; CharIndex: Integer);
var
Report: TStringList;
PtSize: Single;
begin
Report := TStringList.Create;
try
PtSize := Pdf.FontSize[CharIndex];
Report.Add('Character : ' + Pdf.Character[CharIndex]);
Report.Add('Family : ' + Pdf.FontFamilyName[CharIndex]);
Report.Add('Base name : ' + Pdf.FontBaseName[CharIndex]);
Report.Add('Weight : ' + IntToStr(Pdf.FontWeight[CharIndex]));
Report.Add('Italic : ' + IntToStr(Pdf.FontItalicAngle[CharIndex]) + ' deg');
Report.Add('Size : ' + FormatFloat('0.0', PtSize) + ' pt');
Report.Add('Ascent : ' + FormatFloat('0.0', Pdf.FontAscent[CharIndex, PtSize]));
Report.Add('Descent : ' + FormatFloat('0.0', Pdf.FontDescent[CharIndex, PtSize]));
Report.Add('Embedded : ' + BoolToStr(Pdf.FontIsEmbedded[CharIndex], True));
ShowMessage(Report.Text);
finally
Report.Free;
end;
end;
Algumas das assinaturas de métodos podem surpreender quem vem de outras bibliotecas. O FontAscent e o FontDescent recebem dois argumentos, o índice do caráter e o tamanho em pontos, porque o PDFium reporta essas métricas em unidades de espaço de glifo que só se tornam píxeis após serem dimensionadas pelo tamanho em que o texto foi definido. Passe o valor que já leu de FontSize[CharIndex] e obterá o ascent (ascendente) e o descent (descendente) nos mesmos pontos que o resto do layout. O descent é retornado como um valor negativo, pois mede abaixo da linha de base. O nome da família e o nome base são strings separadas de propósito: o nome base é a entrada /BaseFont original do PDF, contendo frequentemente um prefixo de subconjunto como ABCDEF+, enquanto o nome da família é o nome limpo que o renderizador resolve.
Transformar um clique num índice de caráter
Num visualizador, raramente sabe o índice de antemão. O utilizador clica num glifo e é necessário traduzir a coordenada de píxeis para o caráter correspondente. O método CharacterIndexAtPos faz exatamente isso, recebendo a posição do rato e uma tolerância, e retornando o índice do caráter mais próximo, ou um valor negativo quando o clique ocorre num espaço em branco ou numa página vazia.
procedure TfrmMain.PdfViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
if not PdfView.Active then
Exit;
// 4 px of slack in each direction so a near-miss still hits the glyph.
Index := PdfView.CharacterIndexAtPos(X, Y, 4.0, 4.0);
if Index < 0 then
Exit; // clicked between glyphs; leave the panel alone
PdfView.CurrentCharIndex := Index;
DescribeFontAt(PdfView.Pdf, Index);
end;
Vale a pena afinar a tolerância. Se for demasiado apertada, os utilizadores sentirão que têm de clicar na haste exata de uma letra; se for demasiado folgada, um clique numa margem pode selecionar um caráter distante que nada tem a ver com o que pretendiam. Três a cinco píxeis do dispositivo é um ponto de partida razoável para visualização no ecrã. O índice retornado corresponde ao texto analisado da página atual, o mesmo espaço de índices esperado por todas as propriedades de fonte, pelo que pode passá-lo diretamente para a rotina acima. Armazená-lo em CurrentCharIndex é opcional mas conveniente: a visualização mantém esse valor como a sua referência de glifo focado, o que é prático se outras partes da interface de utilizador quiserem ler a seleção sem terem de a recalcular.
A incorporação é a propriedade que importa
Para a maior parte do trabalho real, a única questão que vale a pena responder é se cada fonte está incorporada. Um documento cujas fontes estão todas integradas nele é renderizado da mesma forma no RIP de uma gráfica, no portátil de um colega e num servidor sem qualquer interface gráfica. Um documento que depende de uma fonte Helvetica não incorporada está a arriscar que todas essas máquinas possuam um tipo de letra correspondente. Quando isso falha, o leitor substitui por algo semelhante, as métricas alteram-se e um formulário cuidadosamente desenhado sofre um refluxo de texto suficiente para desconfigurar o layout. Percorrer o texto da página e agrupar as fontes pelo estado de incorporação fornece essa resposta de forma simples.
procedure ReportNonEmbeddedFonts(Pdf: TPdf);
var
Embedded, External: TStringList;
I: Integer;
Name: string;
begin
Embedded := TStringList.Create;
External := TStringList.Create;
try
Embedded.Sorted := True;
Embedded.Duplicates := dupIgnore;
External.Sorted := True;
External.Duplicates := dupIgnore;
for I := 0 to Pdf.CharacterCount - 1 do
begin
Name := Pdf.FontBaseName[I];
if Name = '' then
Continue; // generated spaces and the like have no font
if Pdf.FontIsEmbedded[I] then
Embedded.Add(Name)
else
External.Add(Name);
end;
if External.Count > 0 then
ShowMessage(IntToStr(External.Count) +
' non-embedded font(s):' + sLineBreak + External.Text)
else
ShowMessage('All ' + IntToStr(Embedded.Count) +
' font(s) on this page are embedded.');
finally
Embedded.Free;
External.Free;
end;
end;
Dois detalhes garantem a precisão desta análise. Primeiro, o CharacterCount é por página, pelo que uma auditoria a todo o documento significa definir o Pdf.PageNumber para cada página sucessivamente e executar o ciclo novamente, fundindo os resultados. Segundo, a camada de texto contém caracteres gerados, como os espaços que um leitor infere entre as palavras, e estes não têm nenhum objeto de fonte associado; a verificação de nome base vazio ignora-os em vez de registar um elemento fantasma. O nome base é a chave correta para a eliminação de duplicados aqui, porque o prefixo de subconjunto que contém distingue dois subconjuntos diferentes da mesma família, o que é geralmente o que deseja saber.
Extrair o tipo de letra incorporado
Quando uma fonte está incorporada, pode ler os seus bytes diretamente. A propriedade FontData retorna o programa de fonte binário, os mesmos dados TrueType ou CFF que o PDF contém, o que é suficiente para gravar um ficheiro de fonte autónomo ou para identificar a fonte em comparação com uma biblioteca conhecida. Esta propriedade retorna uma matriz vazia quando a fonte não está incorporada, pelo que a verificação de incorporação e a verificação de comprimento protegem a gravação.
procedure SaveEmbeddedFont(Pdf: TPdf; CharIndex: Integer;
const OutputFile: string);
var
Data: TBytes;
Stream: TFileStream;
begin
if not Pdf.FontIsEmbedded[CharIndex] then
begin
ShowMessage('That glyph''s font is not embedded; nothing to extract.');
Exit;
end;
Data := Pdf.FontData[CharIndex];
if Length(Data) = 0 then
Exit;
Stream := TFileStream.Create(OutputFile, fmCreate);
try
Stream.WriteBuffer(Data[0], Length(Data));
finally
Stream.Free;
end;
ShowMessage('Wrote ' + IntToStr(Length(Data)) + ' bytes.');
end;
Os bytes representam o subconjunto incorporado, não a fonte comercial original, pelo que o que obtém de volta geralmente cobre apenas os glifos que o documento realmente utilizou. Isto é perfeitamente adequado para análise forense e verificação, mas inadequado para reutilização; um subconjunto de Times New Roman que contenha trinta glifos não é uma fonte que possa instalar e utilizar para escrever. Trate a extração como um meio de inspecionar o que foi enviado e não como uma ferramenta de recuperação de fontes. Se precisar do nome base correspondente para nomear o ficheiro de saída, leia FontBaseName[CharIndex] juntamente com os dados e remova a etiqueta de subconjunto inicial se pretender apenas a família de fontes limpa.
Compreender o valor da espessura
A propriedade FontWeight retorna a classe de espessura numérica, a mesma escala de 100 a 900 que o CSS utiliza, onde 400 é regular e 700 é negrito. O PDFium retorna o que quer que a fonte declare, o que nem sempre é uma centena exata; um tipo de letra pode anunciar 350 ou 650, e considerar qualquer valor igual ou superior a 600 como \"negrito suficiente\" funciona melhor do que testar exatamente por 700. O ângulo itálico é um sinal complementar: um valor diferente de zero, geralmente negativo, significa que a fonte é oblíqua ou itálica verdadeira, e zero significa vertical. Em conjunto, permitem identificar uma sequência de negrito-itálico em comparação com uma regular sem renderizar nada, o que é o tipo de verificação que uma análise prévia (preflight) ou uma auditoria de acessibilidade pretende realizar em lote.
Nenhuma destas leituras requer um bitmap renderizado. Elas provêm da camada de texto analisada, pelo que um documento aberto na página correta é tudo o que necessita, o que torna a inspeção de fontes rápida e económica para executar em todo um arquivo. Se estiver a combinar isto com a extração de texto, os mesmos índices de caracteres alinham-se com o texto que extrai, de modo que a fonte de um glifo e o seu valor Unicode são duas leituras para o mesmo índice. O artigo complementar sobre a extração de texto de documentos PDF com o PDFium VCL aborda esse aspeto da camada de texto com mais detalhe.
As propriedades de fonte apresentadas aqui fazem parte do Componente PDFium Delphi VCL.