O HotPDF renderiza uma página PDF carregada em um TBitmap do Delphi por meio de uma única chamada: RenderLoadedPageToBitmap(PageIndex, DPI). A função interpreta o fluxo de conteúdo da página (content stream) e retorna um bitmap RGB de 24 bits pertencente ao chamador na resolução que você escolher, que é exatamente o que uma barra de miniaturas, uma visualização de impressão ou um pipeline de exportação de PDF para imagem necessita. Este artigo aborda a API e depois a parte que separa um renderizador utilizável de um brinquedo: desenhar texto a partir dos próprios programas de fontes incorporadas, em vez de fontes de sistema semelhantes
Por que renderizar uma página PDF é mais difícil do que desenhar uma imagem?
Uma página PDF não é uma imagem. É um programa: um fluxo de operadores que constroem caminhos (paths), selecionam fontes, definem cores e posicionam glifos, executados contra o modelo gráfico definido na ISO 32000-1 §8. Nada no arquivo diz como deve ser a aparência de qualquer pixel. Para produzir um bitmap, você deve executar esse programa — manter uma matriz de transformação atual, uma pilha de estados gráficos para q/Q, a caminho de recorte (clipping path), espaços de cores de preenchimento e contorno — e rasterizar o resultado. É por isso que "apenas exibir a página 3 como uma imagem" é um interpretador de fluxo de conteúdo, e não uma conversão de formato de arquivo
O renderizador do HotPDF, introduzido na v2.253.0, é construído como sei unidades desacopladas que espelham esse modelo: um núcleo de matriz afim para a álgebra de transformação PDF [a b c d e f], uma pilha de estados gráficos, um resolvedor de espaço de cores (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), um construtor de caminhos que conecta os operadores de caminho do PDF ao GDI, uma camada de métricas de fontes que lê as matrizes /Widths para avanços corretos, e o interpretador que distribui operadores e direciona os outros cinco. Objetos de imagem (image XObjects) passam pela mesma pilha de decodificação que a biblioteca usa para extração, de modo que cada filtro de imagem que o HotPDF consegue decodificar para extração — incluindo imagens JPEG 2000 compactadas por JPXDecode — também apareça na saída renderizada
Renderizando uma página carregada para um TBitmap
O RenderLoadedPageToBitmap recebe um índice de página baseado em zero e um valor de DPI, onde 72 DPI mapeia uma unidade de espaço do usuário do PDF para um pixel. Retorna nil em caso de falha (índice fora de faixa, recursos ausentes) em vez de gerar uma exceção, de modo que um visualizador possa pular uma página corrompida e continuar. O chamador possui o bitmap retornado e deve liberá-lo (free)
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('report.pdf') > 0 then
begin
Bmp := Pdf.RenderLoadedPageToBitmap(0, 144); // page 1 at 144 DPI
if Bmp <> nil then
try
Image1.Picture.Assign(Bmp);
finally
Bmp.Free; // caller owns the bitmap
end;
end;
finally
Pdf.Free;
end;
end;
O argumento DPI realiza o trabalho de escala para cada cenário comum. Uma barra de miniaturas renderiza a 36 ou 48 DPI, obtendo bitmaps pequenos e rápidos; uma visualização na tela a 96 ou 144 DPI corresponde à densidade típica do monitor; uma rota de exportação a 300 DPI produz imagens com qualidade de impressão. A rotação de página obtida a partir da entrada /Rotate e a inversão de origem do /MediaBox (o PDF coloca a origem no canto inferior esquerdo e o GDI no canto superior esquerdo) são tratadas dentro da matriz de página para dispositivo, de modo que uma página Carta (US Letter) a 72 DPI retorne exatamente como 612×792 pixels na orientação correta
Por que as miniaturas de PDF renderizadas mostram glifos errados?
Por que as miniaturas de PDF renderizadas mostram glifos errados?
Glifos errados ou aproximados na saída renderizada do PDF quase sempre significam que o renderizador está substituindo por uma fonte do sistema em vez de usar a fonte incorporada no arquivo. O primeiro renderizador do HotPDF fazia exatamente isso: removia o prefixo de subconjunto do /BaseFont (transformando ABCDEF+Arial em Arial), solicitava ao GDI uma fonte do sistema com esse nome e desenhava o texto com ela. Para um documento que usa Arial ou Times New Roman com codificação padrão, o resultando parece próximo. No entanto, é uma aproximação, e falha de maneiras bem definidas
Fontes de subconjunto incorporadas são o pior caso. Uma fonte de subconjunto pode carregar apenas os quarenta glifos que um documento realmente usa, com códigos de caracteres atribuídos em uma ordem privada daquele arquivo — o código 1 pode ser "T", o código 2 "h" e assim por diante. Uma fonte do sistema não sabe nada sobre essa atribuição privada, portanto o texto simplesmente desaparece ou sai como caracteres totalmente errados. Codificações personalizadas, fontes de símbolos, fontes de código de barras e qualquer face não instalada na máquina de renderização falham da mesma forma. Um renderizador que para na substituição da fonte do sistema produz miniaturas que são reconhecíveis como a página — até que a página use as fontes que tornaram a incorporação necessária em primeiro lugar
Renderização de glifos incorporados: desenhando a partir do próprio programa de fonte
O HotPDF fechou essa lacuna ao longo de cinco versões (v2.268.0 a v2.272.0), analisando os programas de fontes incorporadas e reproduzindo seus contornos de glifos como caminhos vetoriais GDI preenchidos. O texto em uma página renderizada agora provém dos mesmos dados de contorno que um visualizador em conformidade utiliza, o que significa que fontes de subconjuntos, codificações personalizadas e faces não instaladas sejam renderizadas com suas formas exatas. A cobertura foi desenvolvida por tipo de fonte:
Para fontes Type0/CIDFontType2 com um programa TrueType incorporado (FontFile2), o renderizador analisa as tabelas glyf e loca diretamente: contornos quadráticos são convertidos para as curvas Bézier cúbicas que o GDI compreende, pontos na curva (on-curve points) implícitos entre pontos fora da curva (off-curve points) consecutivos são reconstruídos e glifos compostos são reproduzidos recursivamente. Ambas as disposições Identity e de fluxo explícito CIDToGIDMap são suportadas, e os avanços de CID respeitam as entradas de largura /W e /DW, de modo que o texto Identity-H de dois bytes avance corretamente
Programas CFF (FontFile3, seja CIDFontType0C, Type1C ou um encapsulador OpenType) recebem um interpretador completo de charstring do Tipo 2: linhas, curvas, a família flex, máscaras de dica (hint masks) e chamadas de sub-rotinas locais/globais com o viés de sub-rotina correto. Programas CFF chaveados por CID mapeiam códigos de caracteres através do charset da fonte, o que é importante para fontes de subconjunto cuja ordem de glifo difere da ordem CID, e a seleção de dicionário de fontes (font-DICT) por glifo através de FDArray/FDSelect é respeitada. Fontes TrueType simples (não CID) resolvem códigos de um byte através da própria tabela cmap da fonte incorporada com uma cadeia robusta de subtabelas — formatos Unicode 4 e 12 primeiro, depois subtabelas de símbolos com o espelho de uso privado F000, depois formatos legados Macintosh — enquanto fontes Type1 simples se resolvem através da codificação integrada do programa CFF
Dois refinamentos completam o quadro. Primeiro, dicionários /Encoding de fontes simples são resolvidos conforme a prioridade que a ISO 32000-1 §9.6.6 prescreve: as matrizes /Differences anulam a codificação base, que por sua vez anula o próprio mapa do programa de fonte — o caminho do qual dependem as cadeias de ferramentas derivadas de TeX e PostScript, com nomes de glifos se resolvendo através da Adobe Glyph List, do charset CFF ou do cmap TrueType. Segundo, as fontes Type3, cujos glifos são eles mesmos pequenos fluxos de conteúdo, são reproduzidas através do renderizador com a matriz de fonte, tamanho da fonte e matriz de texto compostos; as larguras no espaço do glifo (glyph-space /Widths) são interpretadas através da /FontMatrix conforme exige a ISO 32000-1 §9.6.5, e os procedimentos de glifo que declaram uma caixa delimitadora (bounding box) d1 são recortados a ela, de modo que um glifo de código de barras malformado não possa desenhar fora de sua célula. Quando um código não pode ser mapeado — um programa corrompido, um caractere não mapeado — o renderizador recorre ao desenho com fonte do sistema para esse glifo, em vez de descartar o trecho de texto
Como tornar rápidas renderizações repetidas?
A resposta que o HotPDF fornece é um cache de página do tipo mais recentemente utilizado (MRU): o RenderLoadedPageToBitmapCached mantém até RenderCacheCapacity páginas renderizadas (padrão 8) identificadas pelo índice da página e DPI, e um acerto no cache (cache hit) retorna uma cópia nova de propriedade do chamador sem tocar no fluxo de conteúdo — normalmente milhares de vezes mais rápido do que interpretar a página novamente. Esse padrão se encaixa perfeitamente em visualizadores: um usuário alternando entre duas páginas, ou um evento de redimensionamento que solicita novamente a mesma página com o mesmo DPI, atinge o cache todas as vezes
// Thumbnail strip: first pass renders, scrolling back hits the cache
for I := 0 to ThumbCount - 1 do
begin
Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 48);
if Bmp <> nil then
try
ThumbList.AddThumbnail(I, Bmp);
finally
Bmp.Free;
end;
end;
// After editing a loaded page in place:
Pdf.InvalidateRenderedPageCache; // next render reflects the change
Seja realista sobre a conta de memória antes de aumentar a capacidade. Uma página Carta (US Letter) a 300 DPI tem 2550×3300 pixels, cerca de 25 MB como um bitmap de 24 bits, portanto oito páginas em cache na resolução de exportação ocupam cerca de 200 MB. No DPI de miniatura, as mesmas oito entradas custam bem menos de um megabyte. Dimensione o RenderCacheCapacity para o DPI em que você realmente faz o cache e chame InvalidateRenderedPageCache após qualquer edição no próprio local — o cache é indexado apenas pela página e DPI, e não pode detectar que o conteúdo subjacente foi alterado. O carregamento de um novo documento o limpa automaticamente
Um segundo cache funciona abaixo do cache de página: objetos de imagem decodificados (image XObjects) são mantidos em um armazenamento limitado por bytes sob ImageCacheMaxBytes (padrão 32 MB) com despejo do tipo mais antigo (LRU). Um logotipo ou imagem de cabeçalho repetida em cada página é decodificada uma única vez por carregamento de documento, em vez de uma vez por operador Do, o que reduz pela metade o tempo de renderização para páginas com imagens compartilhadas e acelera a exportação de TIFF de várias páginas na mesma medida. O InvalidateRenderedPageCache também limpa este cache
O que ainda renderiza aproximadamente
O renderizador tem como alvo o subconjunto comum de documentos PDF, e vale a pena saber onde estão os limites. Os espaços de cores CalRGB, Lab e baseados em ICC são aproximados em vez de geridos por cores — espaços de cores de dispositivo, paletas indexadas e pesquisas de cores de função do Tipo 0 amostradas são tratados, mas um arquivo de produção de impressão que dependa de intenções de renderização ICC não será colorimetricamente exato. Padrões de sombreamento (sh) e modos de mesclagem (blend modes) além do alfa simples também estão fora do escopo, e a recursão de objetos de formulário (Form XObject) é limitada em profundidade como proteção de ciclo. Para faturas, relatórios, contratos e formulários — páginas feitas de texto, caminhos e imagens —, a saída é fiel; para uma prova de design cheia de gradientes e grupos de transparência, trate o bitmap como uma pré-visualização, e não como uma prova
A leitura prática: se seu pipeline gera documentos com HotPDF ou consome PDFs comerciais típicos, o RenderLoadedPageToBitmap os reproduz com as formas exatas dos glifos incorporados, avanços CID corretos e geometria de página correta. Les aproximações vivem nos cantos do modelo gráfico que os documentos comerciais raramente visitam
O RenderLoadedPageToBitmap, sua variante em cache e o pipeline de renderização de glifos incorporados descritos aqui são fornecidos como parte do HotPDF Component para Delphi e C++Builder — uma biblioteca nativa VCL sem dependências de DLLs externas, abrangendo criação, edição de PDF, extração de texto e renderização de página em um único pacote