Compor texto num PDF requer saber quanto espaço horizontal uma cadeia de caracteres vai ocupar antes de a adicionar à página. Isso é necessário para quebra de linha, centralização, alinhamento à direita e cálculos de enquadramento de texto em geral. O PDFium não expõe uma função de medição de texto direta que opere sem uma página ativa, mas fornece os blocos de construção para a construir: criar um objeto de texto temporário, definir o seu conteúdo, consultar os seus limites e destruí-lo sem nunca o adicionar ao conteúdo da página. O PDFiumPas embrulha esse padrão no método auxiliar de classe MeasureText na unidade FPdfMeasure
A sonda de objeto de texto descartável
A medição de texto do PDFium usa quatro chamadas da API. FPDFPageObj_NewTextObj cria um objeto de texto em memória associado ao documento mas não adicionado a nenhuma página. FPDFText_SetText define o conteúdo da cadeia de caracteres. FPDFPageObj_GetBounds devolve a caixa delimitadora do objeto nas coordenadas da página, que inclui a largura do texto renderizado no tamanho de fonte dado. FPDFPageObj_Destroy liberta o objeto sem o ter adicionado à página
O resultado é uma medição que usa as mesmas tabelas de métricas de fonte que o PDFium usaria ao renderizar — incluindo kerning, hinting e qualquer substituição de fonte que possa ocorrer — sem deixar nenhum vestígio na página
// Conceptual sequence — wrapped by MeasureText in FPdfMeasure
var
TextObj: FPDF_PAGEOBJECT;
Left, Bottom, Right, Top: Single;
Width: Single;
begin
TextObj := FPDFPageObj_NewTextObj(Document, 'Helvetica', FontSize);
FPDFText_SetText(TextObj, PWideChar(Text));
FPDFPageObj_GetBounds(TextObj, @Left, @Bottom, @Right, @Top);
Width := Right - Left;
FPDFPageObj_Destroy(TextObj);
// Width is now the rendered width of Text in the given font and size
end;
A API MeasureText
Na prática nunca se escreve essa sequência diretamente. A unidade FPdfMeasure expõe MeasureText e MeasureTextWidth como métodos auxiliares de classe em TPdf. Ambos aceitam um documento, um nome de fonte, um tamanho de fonte e uma cadeia de caracteres, e devolvem a largura em unidades de ponto PDF. MeasureText devolve a caixa delimitadora completa para chamadores que precisam da altura ou das métricas de linha de base; MeasureTextWidth devolve apenas a largura para casos de uso de quebra de linha onde apenas a largura horizontal é necessária
uses
FPdfMeasure;
var
Width: Single;
begin
Width := TPdf.MeasureTextWidth(Document, 'Helvetica', 12.0, 'Hello, world');
// Width is in PDF points (1/72 inch)
end;
Quando a cadeia de caracteres de entrada está vazia ou o objeto de texto não pode ser criado — por exemplo, porque o nome da fonte não pode ser resolvido — o método devolve zero. O código chamador deve tratar zero como um resultado degenerado em vez de uma largura válida
Implementar quebra de linha greedy
Com uma função de medição de largura disponível, a quebra de linha num parágrafo é um algoritmo greedy direto. Acumule palavras numa linha até que adicionar a próxima palavra exceda a largura da coluna, depois emita a linha atual e comece uma nova. A largura de cada palavra candidata pode ser medida individualmente, ou a linha acumulada pode ser medida como um todo — medir a linha acumulada é tipicamente mais preciso porque captura o kerning entre palavras
function WrapParagraph(
Doc: FPDF_DOCUMENT;
const FontName: string;
FontSize, ColumnWidth: Single;
const Text: string): TArray<string>;
var
Words: TArray<string>;
Line, Candidate: string;
Word: string;
Lines: TList<string>;
begin
Lines := TList<string>.Create;
try
Words := Text.Split([' ']);
Line := '';
for Word in Words do
begin
if Line = '' then
Candidate := Word
else
Candidate := Line + ' ' + Word;
if TPdf.MeasureTextWidth(Doc, FontName, FontSize, Candidate) <= ColumnWidth then
Line := Candidate
else
begin
if Line <> '' then
Lines.Add(Line);
Line := Word;
end;
end;
if Line <> '' then
Lines.Add(Line);
Result := Lines.ToArray;
finally
Lines.Free;
end;
end;
O algoritmo funciona corretamente para entradas degeneradas: uma única palavra mais larga que ColumnWidth é emitida como a sua própria linha em vez de ser descartada. Uma entrada vazia produz um array vazio. Palavras com espaços iniciais ou finais devem ser aparadas antes de passar para a função de medição se a precisão de enquadramento for importante
Medição versus a tabela de métricas de fonte integrada
Uma alternativa à sonda descartável é usar tabelas de métricas de fonte integradas. Para as 14 fontes padrão do PDF (Helvetica, Times, Courier e as suas variantes), as métricas de glifo são publicadas pela Adobe e podem ser codificadas diretamente. Para fontes embebidas ou substituídas, as métricas variam de acordo com o ficheiro de fonte carregado, de modo que as tabelas integradas dão resultados errados a menos que a fonte exata seja conhecida com antecedência
A abordagem de sonda descartável usa as mesmas métricas que o PDFium usa durante a renderização, o que a torna precisa independentemente da fonte. O custo são quatro chamadas de API por medição, que é insignificante para uso interativo e quebra de linha de parágrafo, mas pode adicionar-se se medições de largura individuais forem chamadas para cada caractere num texto longo. Para esse caso, uma cache de largura por fonte-e-tamanho ou uma medição de linha acumulada em vez de por-palavra reduz o número de chamadas
Integrar com composição de página
A medição de texto é o passo de pré-processamento para qualquer operação de composição de texto no PDFiumPas. Um fluxo de trabalho típico: mede a largura de cada palavra com MeasureTextWidth, usa o algoritmo de quebra greedy para dividir o parágrafo em linhas, e depois adiciona cada linha como um objeto de texto à página nas coordenadas calculadas. A função WrapParagraph acima produz as linhas; o chamador avança o cursor y por FontSize * LineSpacing para cada linha ao escrever os objetos de texto
A renderização dessas páginas compostas em bitmaps para visualização é abordada em renderização de PDF em segundo plano com futuros canceláveis e renderização progressiva cancelável. Todas as funcionalidades de composição e renderização estão disponíveis no Componente PDFiumPas para Delphi e Lazarus