Articolo tecnico

Renderizzare pagine PDF in Bitmap in Delphi con HotPDF

HotPDF renderizza una pagina PDF caricata in un TBitmap di Delphi attraverso una singola chiamata: RenderLoadedPageToBitmap(PageIndex, DPI). La funzione interpreta il flusso di contenuti della pagina e restituisce una bitmap RGB a 24 bit di proprietà del chiamante alla risoluzione scelta, che è esattamente ciò di cui ha bisogno una striscia di miniature, un'anteprima di stampa o una pipeline di esportazione da PDF a immagine. Questo articolo illustra l'API, per poi passare alla parte che distingue un renderer utilizzabile da un giocattolo: disegnare il testo a partire dai programmi dei font incorporati stessi anziché da font di sistema simili

Perché renderizzare una pagina PDF è più difficile che disegnare un'immagine?

Una pagina PDF non è un'immagine. È un programma: un flusso di operatori che creano percorsi, selezionano font, impostano colori e posizionano glifi, eseguiti in base al modello grafico definito in ISO 32000-1 §8. Niente nel file indica l'aspetto di un singolo pixel. Per produrre una bitmap è necessario eseguire quel programma — mantenere una matrice di trasformazione corrente, uno stack di stati grafici per q/Q, un percorso di ritaglio, spazi colore per riempimento e tratto — e rasterizzare il risultato. Ecco perché "mostrare semplicemente la pagina 3 come immagine" richiede un interprete del flusso di contenuti, non una conversione del formato di file

Il renderer di HotPDF, introdotto nella v2.253.0, è strutturato in sei unità disaccoppiate che rispecchiano questo modello: un core di matrice affine per l'algebra di trasformazione PDF [a b c d e f], uno stack di stati grafici, un risolutore di spazio colore (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), un builder di percorsi che collega gli operatori di percorso PDF a GDI, un livello di metriche dei font che legge gli array /Widths per avanzamenti corretti, e l'interprete che smista gli operatori e guida gli altri cinque. Gli XObject immagine passano attraverso lo stesso stack di decodifica che la libreria utilizza per l'estrazione, quindi qualsiasi filtro immagine che HotPDF può decodificare per l'estrazione — comprese le immagini JPEG 2000 compresse con JPXDecode — appare anche nell'output renderizzato

Renderizzare una pagina caricata in un TBitmap

RenderLoadedPageToBitmap accetta un indice di pagina a base zero e un valore DPI, dove 72 DPI mappano un'unità dello spazio utente PDF in un pixel. Restituisce nil in caso di errore (indice fuori intervallo, risorse mancanti) anziché sollevare un'eccezione, in modo che un visualizzatore possa saltare una pagina danneggiata e continuare. Il chiamante possiede la bitmap restituita e deve liberarla

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;

Il parametro DPI esegue il ridimensionamento per ogni scenario comune. Una striscia di miniature viene renderizzata a 36 o 48 DPI ottenendo bitmap piccole e veloci; un'anteprima sullo schermo a 96 o 144 DPI corrisponde alla tipica densità del display; un percorso di esportazione a 300 DPI produce immagini di qualità di stampa. La rotazione della pagina indicata nella voce /Rotate e l'inversione dell'origine di /MediaBox (PDF posiziona l'origine in basso a sinistra, GDI in alto a sinistra) sono gestite all'interno della matrice pagina-dispositivo, in modo che una pagina US Letter a 72 DPI venga restituita esattamente come 612×792 pixel con il corretto orientamento

Perché le miniature PDF renderizzate mostrano glifi errati?

Glifi errati o approssimativi nell'output PDF renderizzato indicano quasi sempre che il renderer sta sostituendo un font di sistema invece di utilizzare il font incorporato nel file. Il primo renderer di HotPDF faceva esattamente questo: rimuoveva il prefisso del subset da /BaseFont (trasformando ABCDEF+Arial in Arial), richiedeva a GDI un font di sistema con quel nome e disegnava il testo utilizzandolo. Per un documento che utilizza Arial o Times New Roman con codifica standard, il risultato appare simile. Ma si tratta di un'approssimazione che fallisce in scenari ben definiti

I font incorporati come subset rappresentano il caso peggiore. Un font subset può contenere solo i quaranta glifi effettivamente utilizzati da un documento, con codici di caratteri assegnati in un ordine privato per quel file — il codice 1 potrebbe essere "T", il codice 2 "h" e così via. Un font di sistema non sa nulla di tale assegnazione privata, quindi il testo scompare o viene visualizzato con caratteri completamente errati. Le codifiche personalizzate, i font di simboli, i font di codici a barre e qualsiasi carattere non installato sulla macchina di rendering falliscono allo stesso modo. Un renderer che si limita alla sostituzione con font di sistema produce miniature che richiamano la pagina — finché la pagina non utilizza i font che hanno reso necessario l'incorporamento in primo luogo

Rendering dei glifi incorporati: disegnare a partire dal programma del font stesso

HotPDF ha colmato questo divario nel corso di cinque versioni (dalla v2.268.0 alla v2.272.0) analizzando i programmi dei font incorporati e riproducendo i contorni dei loro glifi come percorsi vettoriali GDI riempiti. Il testo in una pagina renderizzata proviene ora dagli stessi dati di contorno utilizzati da un visualizzatore conforme, il che significa che i font subset, le codifiche personalizzate e i caratteri non installati vengono renderizzati con le loro forme esatte. La copertura è stata sviluppata in base alla variante del font:

Per i font Type0/CIDFontType2 con un programma TrueType incorporato (FontFile2), il renderer analizza direttamente le tabelle glyf e loca: i contorni quadratici vengono convertiti nelle curve di Bézier cubiche interpretate da GDI, i punti su curva impliciti tra punti fuori curva consecutivi vengono ricostruiti e i glifi compositi vengono riprodotti ricorsivamente. Sono supportati sia i layout Identity sia i flussi espliciti CIDToGIDMap, e gli avanzamenti CID rispettano le voci di larghezza /W e /DW, garantendo il corretto avanzamento del testo a due byte Identity-H

I programmi CFF (FontFile3, sia CIDFontType0C, Type1C, o un wrapper OpenType) beneficiano di un interprete charstring Type 2 completo: linee, curve, la famiglia flex, maschere di hint e chiamate di subroutine locali/globali con la corretta distorsione (bias) della subroutine. I programmi CFF basati su CID mappano i codici dei caratteri attraverso il set di caratteri del font (charset), aspetto importante per i font subset il cui ordine dei glifi differisce dall'ordine CID, e viene rispettata la selezione font-DICT per glifo tramite FDArray/FDSelect. I font TrueType semplici (non CID) risolvono i codici a un byte tramite la tabella cmap interna del font incorporato con una robusta catena di sottotabelle — formati Unicode 4 e 12 per primi, poi sottotabelle dei simboli con specchio ad uso privato F000, infine formati Macintosh legacy — mentre i font Type1 semplici risolvono tramite la codifica integrata del programma CFF

Due perfezionamenti completano il quadro. In primo luogo, i dizionari /Encoding dei font semplici vengono risolti in base alla priorità prescritta da ISO 32000-1 §9.6.6: gli array /Differences hanno la precedenza sulla codifica di base, che a sua volta ha la precedenza sulla mappa interna del programma del font — il percorso su cui si basano le toolchain derivate da TeX e PostScript, con nomi di glifi risolti tramite la Adobe Glyph List, il charset CFF o la cmap TrueType. In secondo luogo, i font Type3, i cui glifi sono a loro volta piccoli flussi di contenuto, vengono riprodotti attraverso il renderer con la composizione di matrice del font, dimensione del font e matrice del testo; le larghezze nello spazio dei glifi (/Widths) vengono interpretate attraverso la /FontMatrix como richiesto da ISO 32000-1 §9.6.5, e le procedure dei glifi che dichiarano un riquadro di delimitazione d1 vengono ritagliate su di esso, impedendo a un glifo di codice a barre malformato di disegnare all'esterno della sua cella. Quando un codice non può essere mappato — un programma danneggiato, un carattere non mappato — il renderer ricorre al disegno con font di sistema per quel glifo anziché saltare l'intera sequenza di testo

Come velocizzare i rendering ripetuti?

La risposta inclusa in HotPDF è una cache delle pagine utilizzate più di recente: RenderLoadedPageToBitmapCached conserva fino a RenderCacheCapacity pagine renderizzate (predefinito 8) indicizzate per indice di pagina e DPI, e un successo di cache (hit) restituisce una nuova copia di proprietà del chiamante senza toccare il flusso di contenuti — in genere migliaia di volte più veloce rispetto alla reinterpretazione della pagina. Questo schema si adatta perfettamente ai visualizzatori: un utente che passa da una pagina all'altra o un evento di ridimensionamento che richiede la stessa pagina agli stessi DPI colpirà la cache ogni volta

// 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

Valuta attentamente l'impatto sulla memoria prima di aumentare la capacità. Una pagina US Letter a 300 DPI equivale a 2550×3300 pixel, circa 25 MB come bitmap a 24 bit, quindi otto pagine in cache a risoluzione di esportazione occupano circa 200 MB. Ai DPI delle miniature, le stesse otto voci pesano molto meno di un megabyte. Dimensiona RenderCacheCapacity in base ai DPI effettivi a cui esegui il caching e chiama InvalidateRenderedPageCache dopo ogni modifica sul posto — la cache è indicizzata solo per pagina e DPI, e non può rilevare che il contenuto sottostante è cambiato. Il caricamento di un nuovo documento la cancella automaticamente

Una seconda cache opera al di sotto della cache di pagina: gli XObject immagine decodificati vengono conservati in un archivio limitato in byte regolato da ImageCacheMaxBytes (predefinito 32 MB) con eliminazione degli elementi meno usati di recente. Un logo o un'immagine di intestazione ripetuta su ogni pagina viene decodificata una sola volta per caricamento del documento anziché una volta per operatore Do, dimezzando approssimativamente il tempo di rendering per le pagine con immagini condivise e velocizzando l'esportazione TIFF multipagina nella stessa misura. Anche InvalidateRenderedPageCache svuota questa cache

Cosa viene ancora renderizzato in modo approssimativo

Il renderer ha come target il subset comune di documenti PDF, ed è utile sapere dove si trovano i limiti. Gli spazi colore CalRGB, Lab e basati su ICC sono approssimati anziché gestiti a livello di colore — gli spazi colore del dispositivo, le tavolozze indicizzate e le ricerche di colore per funzioni campionate di tipo 0 sono gestiti, ma un file destinato alla produzione di stampa basato su intenti di rendering ICC non sarà colorimetricamente esatto. I pattern di sfumatura (sh) e le modalità di fusione oltre il semplice canale alfa sono ugualmente esclusi, e la ricorsione di Form XObject è limitata in profondità per evitare loop infiniti. Per fatture, report, contratti e moduli — pagine costituite da testo, tracciati e immagini — l'output è fedele; per una bozza grafica ricca di gradienti e gruppi di trasparenza, considera la bitmap come un'anteprima, non come una prova

Lettura pratica: se la tua pipeline genera documenti con HotPDF o consuma PDF aziendali tipici, RenderLoadedPageToBitmap li restituisce con le forme esatte dei glifi incorporati, avanzamenti CID corretti e geometria di pagina corretta. Le approssimazioni risiedono negli angoli del modello grafico che i documenti aziendali visitano raramente

RenderLoadedPageToBitmap, la sua variante con cache e la pipeline di rendering dei glifi incorporati descritta qui sono fornite come parte del componente HotPDF Component per Delphi e C++Builder — una libreria VCL nativa senza dipendenze da DLL esterne, che copre la creazione di PDF, la modifica, l'estrazione del testo e il rendering delle pagine in un unico pacchetto