Artigo Técnico

Renderizar Páginas de PDF para Bitmap no Delphi com o HotPDF

O HotPDF renderiza uma página de PDF carregada num TBitmap do Delphi através de uma única chamada: RenderLoadedPageToBitmap(PageIndex, DPI). A função interpreta o fluxo de conteúdo da página e devolve um bitmap RGB de 24 bits pertencente ao chamador na resolução que escolher, que é exatamente o que uma barra de miniaturas, uma pré-visualização de impressão ou um fluxo de exportação de PDF para imagem necessita. Este artigo descreve a API e, em seguida, aborda o elemento que distingue um renderizador utilizável de um brinquedo: desenhar texto a partir dos próprios programas de tipos de letra incorporados, em vez de recorrer a tipos de letra do sistema semelhantes

Por que razão a renderização de 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, selecionam tipos de letra, definem cores e posicionam glifos, executado com base no modelo gráfico definido na norma ISO 32000-1 §8. Nada no ficheiro indica o aspeto de qualquer píxel. Para produzir un bitmap, deve executar esse programa — manter uma matriz de transformação atual, uma pilha de estados gráficos para q/Q, um caminho de corte, espaços de cores de preenchimento e traço — e rasterizar o resultado. É por isso que "apenas mostrar a página 3 como uma imagem" consiste num intérprete de fluxo de conteúdo, e não numa conversão de formato de ficheiro

O renderizador do HotPDF, introduzido na v2.253.0, está estruturado em seis unidades dissociadas que refletem 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 liga os operadores de caminho PDF ao GDI, uma camada de métricas de tipos de letra que lê arrays /Widths para avanços corretos, e o intérprete que processa os operadores e direciona os outros cinco. Os Image XObjects passam pela mesma pilha de descodificação que a biblioteca utiliza para a extração, pelo que todos os filtros de imagem que o HotPDF consegue descodificar para extração — incluindo imagens JPEG 2000 comprimidas com JPXDecode — também aparecem na saída renderizada

Renderizar 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 utilizador PDF para um píxel. Devolve nil em caso de falha (índice fora do intervalo, recursos em falta) em vez de gerar uma exceção, permitindo que um visualizador ignore uma página corrompida e continue. O chamador possui o bitmap devolvido e deve libertá-lo

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;

Por que razão as miniaturas de PDF renderizadas mostram glifos incorretos?

Glifos incorretos ou aproximados na saída de PDF renderizada significam quase sempre que o renderizador está a substituir um tipo de letra do sistema em vez de utilizar o tipo de letra incorporado no ficheiro. O primeiro renderizador do HotPDF fazia exatamente isso: removia o prefixo de subconjunto do /BaseFont (transformando ABCDEF+Arial em Arial), solicitava ao GDI um tipo de letra do sistema com esse nome e desenhava o texto com o mesmo. Para um documento que utiliza Arial ou Times New Roman com codificação padrão, o resultado parece semelhante. No entanto, trata-se de uma aproximação e falha em situações bem definidas

Os tipos de letra incorporados em subconjuntos são o pior cenário. Um tipo de letra de subconjunto pode conter apenas os quarenta glifos que um documento realmente utiliza, com códigos de carateres atribuídos numa ordem privada para esse ficheiro — o código 1 pode ser "T", o código 2 "h" e assim sucessivamente. Um tipo de letra do sistema nada sabe sobre essa atribuição privada, pelo que o texto desaparece ou surge como carateres totalmente incorretos. As codificações personalizadas, tipos de letra de símbolos, tipos de letra de códigos de barras e qualquer face não instalada na máquina de renderização falham da mesma forma. Um renderizador que se limite à substituição por tipos de letra do sistema produz miniaturas que são reconhecíveis como a página — até que a página utilize os tipos de letra que tornaram a incorporação necessária em primeiro lugar

Renderização de glifos incorporados: desenhar a partir do próprio programa do tipo de letra

O HotPDF eliminou essa lacuna ao longo de cinco versões (v2.268.0 a v2.272.0), analisando os programas de tipos de letra incorporados e reproduzindo os contornos dos seus glifos como caminhos vetoriais GDI preenchidos. O texto numa página renderizada provém agora dos mesmos dados de contorno que um visualizador em conformidade utiliza, o que significa que os tipos de letra de subconjunto, codificações personalizadas e faces não instaladas são renderizados com as suas formas exatas. A cobertura foi desenvolvida por variante de tipo de letra:

Para tipos de letra Type0/CIDFontType2 com um programa TrueType incorporado (FontFile2), o renderizador analisa as tabelas glyf e loca diretamente: as curvas quadráticas são convertidas em curvas Bézier cúbicas que o GDI compreende, os pontos na curva implícitos entre pontos fora da curva consecutivos são reconstruídos e os glifos compostos são reproduzidos recursivamente. Tanto os esquemas Identity como os de fluxo explícito CIDToGIDMap são suportados, e os avanços de CID respeitam as entradas de largura /W e /DW, para que o texto Identity-H de dois bytes avance corretamente

Os programas CFF (FontFile3, sejam CIDFontType0C, Type1C ou um encapsulamento OpenType) obtêm um intérprete de charstring Type 2 completo: linhas, curvas, a família flex, máscaras de dicas (hints) e chamadas de subrotinas locais/globais com o desvio de subrotina correto. Os programas CFF baseados em CID mapeiam códigos de carateres através do conjunto de carateres (charset) do tipo de letra, o que é relevante para tipos de letra de subconjunto cuja ordem de glifos difere da ordem CID, e a seleção de dicionário de tipos de letra por glifo através de FDArray/FDSelect é respeitada. Os tipos de letra TrueType simples (não CID) resolvem códigos de um byte através da tabela cmap do próprio tipo de letra incorporado com uma cadeia de subtabelas robusta — formatos Unicode 4 e 12 primeiro, depois subtabelas de símbolos com o espelho de utilização privada F000, depois formatos Macintosh legados — enquanto os tipos de letra Type1 simples se resolvem através da codificação integrada do programa CFF

Dois refinamentos completam o cenário. Primeiro, os dicionários de /Encoding de tipos de letra simples são resolvidos de acordo com a prioridade que a norma ISO 32000-1 §9.6.6 prescreve: os arrays /Differences sobrepõem-se à codificação base, que por sua vez se sobrepõe ao próprio mapa do programa do tipo de letra — o caminho de que dependem as ferramentas derivadas de TeX e PostScript, com nomes de glifos a resolverem-se através da Adobe Glyph List, do conjunto de carateres CFF ou do cmap TrueType. Segundo, os tipos de letra Type3, cujos glifos são eles próprios pequenos fluxos de conteúdo, são reproduzidos no renderizador com a composição da matriz do tipo de letra, tamanho do tipo de letra e matriz de texto; os /Widths do espaço do glifo são interpretados através da /FontMatrix como a norma ISO 32000-1 §9.6.5 exige, e os procedimentos de glifos que declaram uma caixa delimitadora (bounding box) d1 são recortados para a mesma, para que um glifo de código de barras malformado não possa pintar fora da sua célula. Quando um código não pode ser mapeado — um programa corrompido, um caráter não mapeado — o renderizador recorre ao desenho com o tipo de letra do sistema para esse glifo, em vez de ignorar o bloco de texto

Como tornar rápidas as renderizações repetidas?

A resposta que o HotPDF disponibiliza é uma cache de páginas utilizadas mais recentemente: RenderLoadedPageToBitmapCached mantém até RenderCacheCapacity páginas renderizadas (padrão 8) indexadas pelo índice da página e DPI, e um acerto na cache devolve uma cópia nova pertencente ao chamador sem manipular o fluxo de conteúdo — tipicamente milhares de vezes mais rápido do que voltar a interpretar a página. Esse padrão adequa-se perfeitamente a visualizadores: um utilizador que alterne entre duas páginas, ou um evento de redimensionamento que solicite a mesma página com o mesmo DPI, acerta na cache de 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 quanto ao consumo de memória antes de aumentar a capacidade. Uma página US Letter a 300 DPI tem 2550×3300 píxeis, cerca de 25 MB como um bitmap de 24 bits, pelo que oito páginas em cache na resolução de exportação ocupam cerca de 200 MB. No DPI de miniaturas, as mesmas oito entradas custam muito menos de um megabyte. Defina o tamanho de RenderCacheCapacity para o DPI em que realmente faz a cache e chame InvalidateRenderedPageCache após qualquer edição no local — a cache é indexada apenas por página e DPI, não conseguindo detetar que o conteúdo subjacente foi alterado. Carregar um novo documento limpa a cache automaticamente

Uma segunda cache funciona por baixo da cache de páginas: os Image XObjects descodificados são mantidos num armazenamento com limite de bytes definido por ImageCacheMaxBytes (padrão 32 MB) com expulsão dos utilizados há mais tempo (LRU). Um logótipo ou imagem de cabeçalho repetido em todas as páginas é descodificado uma vez por carregamento de documento, em vez de uma vez por operador Do, o que reduz para cerca de metade o tempo de renderização em páginas com imagens partilhadas e acelera a exportação de TIFFs multipágina na mesma medida. Chamar InvalidateRenderedPageCache também limpa esta cache

O que ainda é renderizado de forma aproximada

O renderizador tem como alvo o subconjunto comum de documentos PDF, e convém 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 — os espaços de cores do dispositivo, paletas Indexadas e pesquisas de cores de funções Tipo 0 amostradas são suportados, mas um ficheiro de produção de impressão que dependa de intenções de renderização ICC não será colorimetricamente exato. Os padrões de sombreado (shading patterns, sh) e modos de mistura (blend modes) para além do alpha simples estão igualmente fora do âmbito, e a recursividade de Form XObject é limitada em profundidade para evitar ciclos. Para faturas, relatórios, contratos e formulários — páginas constituídas por 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 o seu fluxo de processamento gera documentos com o HotPDF ou consome PDFs empresariais típicos, o RenderLoadedPageToBitmap garante a consistência das formas exatas dos glifos incorporados, avanços de CID corretos e geometria de página correta. As aproximações ocorrem nos limites do modelo gráfico que os documentos empresariais raramente visitam

O RenderLoadedPageToBitmap, a sua variante em cache e o fluxo de renderização de glifos incorporados aqui descritos são fornecidos como parte do HotPDF Component para Delphi e C++Builder — uma biblioteca VCL nativa sem dependências de DLLs externas, abrangendo a criação, edição, extração de texto e renderização de páginas de PDF num único pacote