HotPDF, il componente PDF nativo per Delphi e C++Builder, importa i metafile Windows EMF e WMF interpretando ogni record GDI direttamente in operatori PDF invece di appiattire il file in un bitmap: i riempimenti a gradiente diventano pattern di shading assiale PDF, i pennelli a tratteggio diventano pattern di tiling PDF, e un gate centralizzato sullo stato del percorso blocca i record malformati impedendo che corrompano l'output. Ogni grafico che un TChart, una superficie GDI+ o un semplice TCanvas può esportare come metafile avanzato è un candidato per questo percorso, e la differenza si nota nel momento in cui qualcuno ingrandisce la pagina o la invia a una stampante ad alta risoluzione
L'alternativa a cui la maggior parte degli sviluppatori Delphi ricorre per default è rasterizzare il metafile in un bitmap prima di collocarlo sulla pagina, e il costo emerge solo più tardi: un grafico a barre nitido sullo schermo diventa visibilmente sgranato nel momento in cui il PDF viene stampato a 600 DPI o proiettato su uno schermo in sala riunioni, e una regione CAD riempita a tratteggio collassa in un unico rettangolo grigio uniforme se lo stile di riempimento non viene mantenuto. Leggere il metafile come un programma anziché come un'immagine è ciò che evita entrambi i problemi, ed è il percorso più difficile da implementare correttamente, motivo per cui vale la pena conoscere le insidie descritte di seguito prima che un report venga pubblicato
Perché interpretare un metafile invece di appiattirlo in un bitmap?
HotPDF mantiene l'importazione EMF e WMF sul percorso vettoriale perché un metafile Windows è una sequenza registrata di chiamate di disegno GDI, non un'immagine, e riprodurre quelle chiamate come operatori PDF di percorso, testo e shading è ciò che permette al risultato di scalare come il resto della pagina. THPDFPage.ShowMetafile e la sua controparte ShowMetafileEx sono i punti di ingresso che un'applicazione chiama, ed entrambi passano il metafile a THPDFWmf, la classe che percorre ogni record GDI e lo traduce. La distinzione non è assoluta, e HotPDF non finge il contrario: un record di metafile che è genuinamente dato raster, ad esempio un blit bitmap StretchDIBits, viene incorporato come un vero XObject Image PDF tramite AddImage e ShowImage, la stessa coppia di chiamate usata per qualsiasi altra immagine sulla pagina, invece di essere forzato in operatori di percorso incapaci di esprimere una fotografia. Linee, riempimenti e testo restano vettoriali; i pixel che erano già pixel nella sorgente restano pixel nell'output. La chiamata più semplice non richiede altro che il metafile caricato:
var
Pdf: THotPDF;
Chart: TMetafile;
begin
Pdf := THotPDF.Create(nil);
Chart := TMetafile.Create;
try
Chart.LoadFromFile('quarterly-revenue.emf'); // exported from TChart or GDI+
Pdf.FileName := 'quarterly-report.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafile(Chart);
Pdf.EndDoc;
finally
Chart.Free;
Pdf.Free;
end;
end;
Come converte l'interprete le coordinate GDI nello spazio pagina PDF?
HotPDF risponde con un singolo passaggio sul flusso di record del metafile stesso, invece di una seconda implementazione di GDI. THPDFWmf.Analyse legge l'intestazione del metafile tramite la chiamata Win32 GetEnhMetaFileHeader, azzera il proprio stato di disegno interno e richiama EnumEnhMetafile, la stessa API di enumerazione che userebbe un visualizzatore di metafile, così che ogni record EMR_* raggiunga THPDFWmf.ExecuteRecord nell'ordine in cui è stato originariamente registrato. GDI esprime le coordinate dall'alto verso il basso in unità di dispositivo o logiche scelte dalla modalità di mappatura propria del metafile; una pagina PDF è invece dal basso verso l'alto in punti dello spazio utente, il sistema di coordinate trattato nel modello di disegno canvas di HotPDF per percorsi e riempimenti. Ogni gestore di record risolve questa discrepanza tramite ScaleX e ScaleY, che richiamano ProjectX e ProjectY per riprodurre la stessa formula GDI di conversione finestra-viewport per le modalità di mappatura anisotropica e isotropica, cosicché una forma registrata larga cinque unità logiche finisca alla larghezza corretta in punti PDF indipendentemente dalle estensioni di finestra e viewport impostate dall'applicazione sorgente
Come diventa un riempimento a gradiente GDI un pattern di shading PDF?
Un record EMR_GRADIENTFILL diventa un vero pattern di shading assiale PDF di Tipo 2 (ISO 32000-1 §8.7.4.5) ogni volta che GDI lo ha registrato in una delle due modalità rettangolo. THPDFWmf.VEMRGradientFill legge il layout proprio del record direttamente dal buffer di byte grezzo, seguendo la struttura MS-EMF §2.3.1.6: un array di vertici RGBA a 16 bit per angolo, seguito da un elenco di rettangoli, ciascuno dei quali fa riferimento a due di quei vertici. Per GRADIENT_FILL_RECT_H, i colori si susseguono da sinistra a destra lungo la linea mediana orizzontale del rettangolo; per GRADIENT_FILL_RECT_V, si susseguono dall'alto verso il basso lungo la linea mediana verticale. In entrambi i casi, i due colori d'angolo e le coordinate del rettangolo proiettate vanno direttamente in THotPDF.RegisterAxialGradient, che restituisce un nome di pattern, e la pagina disegna il rettangolo e lo riempie tramite quel pattern (SetFillPattern) invece di una chiamata piatta SetRGBFillColor, cosicché un'intestazione a bande in stile foglio di calcolo o l'area di un grafico a gradiente mantenga la propria sfumatura invece di collassare su un unico colore medio
La modalità triangolo Gouraud è la lacuna dichiarata onestamente. Quando il campo ulMode del record segnala GRADIENT_FILL_TRIANGLE, VEMRGradientFill lo riconosce, registra nel log che la modalità triangolo non è ancora implementata e salta il rettangolo invece di tentare un'approssimazione a due colori. L'interpolazione per vertice, per pixel su una mesh triangolare arbitraria non si riduce a uno shading assiale o radiale a due stop, ed esprimerla correttamente significherebbe emettere uno shading mesh PDF di Tipo 4 o Tipo 5, la stessa famiglia di shading che anche il renderer di pagina di HotPDF lascia non dipinta quando rilegge un PDF. Due percorsi di codice non correlati finiscono sullo stesso confine: gli shading mesh sono la lacuna sia sul lato di scrittura sia su quello di lettura, e un diagramma sorgente che usa triangoli Gouraud per un morbido bagliore radiale ripiega su qualunque fosse l'ultimo pennello a tinta unita, non su un'approssimazione renderizzata
I pennelli a tratteggio diventano pattern di tiling, non grigio appiattito
Un pennello a tratteggio GDI mantiene la propria texture nel PDF perché THPDFWmf.SetBrushColor verifica CurrentBrush.lbStyle per BS_HATCHED prima di ricadere su un riempimento a tinta unita, instradando quel caso verso SetHatchBrushPattern. Quel metodo scrive uno stream di contenuto PDF a unità 8 per 8 fatto di operatori di linea tracciata, m, l e S, scelti in base allo stile di tratteggio GDI: un singolo tratto orizzontale o verticale per HS_HORIZONTAL e HS_VERTICAL, tre diagonali parallele per HS_FDIAGONAL e HS_BDIAGONAL, e le combinazioni orizzontale-più-verticale o entrambe le diagonali per HS_CROSS e HS_DIAGCROSS. THotPDF.RegisterTilingPattern registra quello stream di contenuto come pattern di tiling colorato (PaintType 1, ISO 32000-1 §8.7.3.1) con XStep e YStep a 8 unità, e la pagina riempie tramite SetFillPattern allo stesso modo di uno shading assiale. Una planimetria CAD o un disegno tecnico che si affida ai riempimenti a tratteggio per distinguere i materiali mantiene quel linguaggio visivo nel PDF invece di perdere ogni regione in un grigio identico
Non ogni pennello riceve questo trattamento, e la lacuna vale la pena conoscerla prima che un'importazione CAD venga pubblicata. EMR_CREATEDIBPATTERNBRUSHPT, il record per un pennello a pattern con immagine bitmap personalizzata anziché uno dei sei stili di tratteggio stock di GDI, registra solo il proprio handle in modo che i successivi record SELECTOBJECT e DELETEOBJECT restino coerenti; HotPDF non espone ancora una pipeline di risorse Pattern PDF per immagini di tile arbitrarie, quindi selezionare quel pennello ricade su un fallback a tinta unita invece della texture sorgente. Se un riempimento appare piatto laddove l'originale usava chiaramente una texture a immagine ripetuta, il pennello sorgente è quasi certamente un pattern DIB personalizzato piuttosto che un tratteggio standard, ed è l'unico caso che vale la pena controllare a mano per primo. Configurare un'importazione per un disegno di questo tipo passa comunque per lo stesso oggetto opzioni:
var
Pdf: THotPDF;
Drawing: TMetafile;
Options: THPDFEmfOptions;
begin
Pdf := THotPDF.Create(nil);
Drawing := TMetafile.Create;
Options := THPDFEmfOptions.Create;
try
Drawing.LoadFromFile('floor-plan.emf');
Options.Assign(Pdf.EmfOptions); // start from the document-wide defaults
Options.Redraw := False; // interpret the original EMF bytes, no GDI re-record pass
Options.ShowNullBrush := True; // keep explicitly unfilled CAD regions visible
Options.UseFrame := True; // clip output to the frame the EMF header declares
Pdf.FileName := 'floor-plan.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
Pdf.EndDoc;
finally
Options.Free;
Drawing.Free;
Pdf.Free;
end;
end;
Cosa impedisce a un metafile malformato di corrompere la pagina?
La risposta di HotPDF è un unico gate in cima a ExecuteRecord invece di un controllo difensivo ripetuto in ciascuno degli oltre ottanta gestori di record. Una parentesi di percorso GDI, aperta da EMR_BEGINPATH e chiusa da EMR_ENDPATH o EMR_ABORTPATH, viene tracciata da una proprietà privata PathContinue sostenuta dal campo FPathContinue. Finché quella parentesi resta aperta, ExecuteRecord lascia passare solo i record di costruzione del percorso, le varianti move, line, polyline, polygon, polybezier e polydraw, più CLOSEFIGURE e un piccolo insieme di record di trasformazione e stato del device context come SETWORLDTRANSFORM, SAVEDC e RESTOREDC. Ogni altro tipo di record che raggiunge ExecuteRecord mentre la parentesi è aperta, un EXTTEXTOUT vagante o un blit bitmap ad esempio, viene scartato centralmente con un singolo Exit nell'istante in cui arriva
Quel gate esiste perché una parentesi di percorso in un metafile scritto a mano, generato da uno strumento o semplicemente corrotto non è garantito che contenga solo ciò che un file ben formato collocherebbe tra i suoi record di apertura e chiusura. Un record di output testo che finisse tra EMR_BEGINPATH ed EMR_ENDPATH, senza un gate, o inquinerebbe la geometria del percorso in costruzione oppure emetterebbe un operatore PDF di visualizzazione testo nel mezzo di una sequenza che dovrebbe essere pura costruzione di percorso, ed entrambe le modalità di fallimento sono del tipo che emerge da un singolo input malformato proveniente da uno strumento di terze parti, non da qualcosa che una suite di test normale finisce per coprire. Centralizzare il controllo in ExecuteRecord significa che i singoli gestori VEMR* non devono ciascuno difendersi dall'essere chiamati nel momento sbagliato; il gate decide una volta sola, prima del dispatch, invece che ottanta volte dopo
Affiancare un grafico vettoriale a testo e immagini su una pagina
Una pagina di report raramente contiene solo un grafico, e ShowMetafile si compone con gli altri operatori di pagina di HotPDF esattamente come qualsiasi altra chiamata di disegno. Un'intestazione disegnata con TextOut, un grafico a barre a tratteggio importato come EMF e un logo posizionato con ShowImage possono trovarsi tutti sulla stessa pagina nello stesso stream di contenuto, ciascuno mantenendo la propria fedeltà nativa, lo schema di composizione trattato nella guida di HotPDF all'impaginazione di testo, font e immagini in un report:
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart); // hatch-filled bars, still vector
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);
L'interprete EMF e WMF, i pattern di shading assiale che registra per i riempimenti a gradiente e la mappatura a pattern di tiling per i pennelli a tratteggio descritti qui fanno tutti parte, di serie, del componente HotPDF standard per Delphi e C++Builder, una libreria VCL nativa senza alcuna dipendenza da DLL esterne per nessuna di queste funzionalità