Generare un report si riduce a collocare tre cose su una pagina e a farle concordare su dove si trovano: testo a coordinate note, font che sul server si rendono come sul vostro desktop e immagini dimensionate per stare nello spazio previsto. Tutto il resto di ciò che fa una libreria di reportistica è disposto attorno a questi tre elementi. HotPDF, la libreria di generazione PDF di losLab per Delphi e C++Builder, ve li offre ciascuno come chiamata diretta sull'oggetto pagina, e l'unico vero attrito è il sistema di coordinate sottostante, che corre nel verso opposto rispetto al canvas VCL a cui siete abituati. Sistemate prima quell'orientamento e il resto del lavoro di impaginazione smette di ostacolarvi
Posizionamento del testo e origine in basso a sinistra
Il primo report di quasi chiunque esce capovolto. Il titolo finisce vicino al bordo inferiore e ogni riga sotto di esso sale verso l'alto. Non si sta guastando nulla. Lo spazio utente PDF, definito in ISO 32000-1 §8.3, colloca l'origine nell'angolo in basso a sinistra con la Y che cresce verso l'alto, ossia l'immagine speculare del canvas GDI dove la Y cresce verso il basso a partire dall'angolo in alto a sinistra. Cinque minuti spesi a fare pace con questo fatto salvano un layout che altrimenti riscrivereste appena i numeri smettono di avere senso
La chiamata centrale dell'oggetto pagina è TextOut(X, Y, Angle, Text). X e Y collocano il testo in punti a partire dall'angolo in basso a sinistra, mentre Angle lo ruota in gradi, ed è così che si disegna una scritta diagonale DRAFT o COPY senza alcun supporto speciale. Il trucco che permette all'intuizione formata sulla VCL di continuare a funzionare consiste nell'esprimere la Y come altezza della pagina meno la distanza desiderata dal margine superiore:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-0001.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 792 - 50, 0, 'INVOICE'); // 50pt dal bordo alto della Letter
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY'); // timbro ruotato
Pdf.AddPage; // CurrentPage punta ora a questa
Pdf.CurrentPage.SetFont('Arial', [], 10); // lo stato del font non si eredita
Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
I due comportamenti con stato presenti in quel listato sono responsabili della maggior parte dei bug che si manifestano solo a pagina due. AddPage ripunta CurrentPage sulla pagina appena creata, quindi un riferimento di pagina messo in cache in precedenza non disegna più dove vi aspettate. Anche la selezione del font è per pagina e non per documento. Se saltate il SetFont dopo un AddPage, il primo TextOut sulla pagina nuova ripiega sul valore predefinito con cui la pagina è nata, non sul font di intestazione in grassetto impostato tre pagine prima. L'abitudine sicura è trattare "aprire una nuova pagina" e "ristabilire lo stato del testo" come un unico passo inseparabile nel ciclo di generazione del report
Font che esistono sul server, non solo sul vostro desktop
La maggior parte dei problemi di font sono in realtà problemi di distribuzione travestiti. La vostra macchina di sviluppo ha il font aziendale installato, quindi il report appare corretto sul vostro schermo e viene rilasciato. L'host di produzione esegue il job con un account di servizio che quel font non lo ha mai avuto installato, il renderer sostituisce in silenzio qualcosa che riesce a trovare, e la prima notizia che se ne ha è un cliente che chiede perché sia cambiata la carta intestata. La via d'uscita è smettere di fidarsi della cartella dei font del sistema operativo e caricare il font da un file che il vostro installer deposita su disco. La chiamata di registrazione Unicode di HotPDF prende un percorso e fa esattamente questo:
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));
TextOut accetta direttamente una WideString, e questo conta più di quanto sembri a prima vista. Un nome di cliente con un accento, una via tedesca, una città polacca: non sono casi limite, sono il contenuto normale di una tabella clienti, e passano dalla stessa chiamata delle etichette ASCII che scrivete a codice, purché il font registrato contenga davvero i glifi. Con i font incorporati viaggia un vincolo di versione: il documento deve essere PDF 1.5 o successivo, quindi se un requisito estraneo vi tiene ancorati a una versione più vecchia, sarà proprio quello a rompersi in silenzio. Gli alfabeti da destra a sinistra come l'arabo e l'ebraico richiedono una vera fase di shaping anziché una semplice ricerca dei glifi, e hanno una pipeline propria; si veda il nostro articolo sullo shaping del testo con script complessi in HotPDF
Quando nessun font installato riesce a esprimere ciò che vi serve, si pensi ai caratteri MICR su un assegno o a un set di simboli proprietario, i font Type 3 colmano il vuoto. Ogni glifo si definisce come un piccolo content stream tramite RegisterType3Font e AddType3Glyph. È un angolo specializzato dell'API e vi capiterà di rado di ricorrervi, ma è molto più pulito che spargere centinaia di minuscoli bitmap di simboli su una pagina
Immagini: gli argomenti centrali sono larghezza e altezza, non un angolo
La gestione delle immagini si divide in due passi, e tenerli separati è tutto il punto. AddImage prende un TBitmap o un TJPEGImage, lo incorpora una volta sola e restituisce un indice. Le illustrazioni PNG vanno decodificate in un bitmap prima di arrivare lì. ShowImage disegna poi quell'indice dove e quante volte volete. L'ordine degli argomenti di ShowImage è l'unico punto su cui vale la pena rallentare a leggere:
var
Png: TPngImage;
Logo: TBitmap;
LogoIdx: Integer;
begin
Png := TPngImage.Create;
Logo := TBitmap.Create;
try
Png.LoadFromFile('brand-logo.png');
Logo.Assign(Png); // decodifica il PNG in un bitmap
LogoIdx := Pdf.AddImage(Logo, icFlate); // senza perdita per grafica a colori piatti
finally
Logo.Free;
Png.Free;
end;
// (Index, X, Y, Width, Height, Angle): non (X1, Y1, X2, Y2)
Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
I due numeri dopo la posizione sono una larghezza e un'altezza. Non sono le coordinate dell'angolo opposto, e l'argomento finale è un angolo di rotazione in gradi. Se leggete la firma come un riquadro X1/Y1/X2/Y2, un logo da 120 per 40 collocato in (50, 700) si estende invece da lì fino a (120, 40), dilagando su gran parte della pagina. L'output rende l'errore evidente mentre il codice sorgente sembra del tutto ragionevole, ed è questo che fa perdere un pomeriggio. KeepImageAspectRatio vale True per impostazione predefinita, quindi un riquadro dalle proporzioni sbagliate inserisce l'immagine con bande vuote invece di deformarla; portatelo a False solo quando volete davvero stirarla
La separazione tra registrare e collocare si ripaga sulle tirature lunghe. Poiché AddImage incorpora i pixel una volta sola e ogni ShowImage con quell'indice rimanda allo stesso oggetto incorporato, è il punto in cui chiamate AddImage a decidere la dimensione del file. Chiamatelo dentro il ciclo delle pagine per un estratto conto da 500 pagine e lo stesso logo viene incorporato 500 volte. Chiamatelo una volta prima del ciclo, conservate l'indice, e il logo viene memorizzato una volta sola. Un piccolo dizionario con chiave il percorso della risorsa basta ad assicurare che ogni immagine distinta venga registrata esattamente una volta
La scelta del codec è l'altra leva sulla dimensione. Il contenuto fotografico, gli allegati scansionati e simili, va in JPEG: passate icJpeg ad AddImage e abbassate JpegQuality attorno a 85, dato che la proprietà parte da 100 e a 85 la differenza è invisibile su una pagina stampata. Le illustrazioni a colori piatti come loghi, grafici e disegni al tratto vanno in icFlate, dove la compressione senza perdita è già compatta e il JPEG spalmerebbe un ringing visibile attorno ai bordi netti. Una tiratura di estratti conto che spinge una foto a piena qualità su ogni pagina può gonfiarsi fino ai gigabyte; lo stesso contenuto a JPEG 85 si assesta attorno a un decimo della dimensione, e nessun lettore se ne accorge
Filetti, riquadri e sfondi con le primitive di path
La linea orizzontale sotto l'intestazione di una tabella e il riquadro grigio dietro la cifra dei totali non hanno bisogno di essere immagini. Disegnateli come vettori e restano nitidi a qualsiasi zoom, si stampano definiti e non aggiungono quasi nulla al file. HotPDF segue lo stesso modello dei content stream PDF grezzi: si costruisce un path, poi si chiama un operatore che lo dipinge
// Filetto orizzontale sotto l'intestazione della tabella
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;
// Riquadro ombreggiato dei totali: X, Y, larghezza, altezza
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;
L'ordine non è facoltativo: impostate lo stato di disegno, costruite il path, poi chiamate Stroke oppure Fill. Un path che costruite ma non dipingete mai non contribuisce in nulla alla pagina, ed è quasi sempre la risposta quando un filetto "non si vede". SetRGBFillColor prende un singolo TColor, quindi le costanti VCL familiari come clNavy e clBlack si inseriscono direttamente, e Rectangle usa gli stessi argomenti di larghezza e altezza del posizionamento delle immagini anziché due angoli. Un avvertimento sulle linee sottili: tutto ciò che sta sotto il mezzo punto circa può apparire elegante su un monitor e poi sparire su una stampante da ufficio a 600 dpi, quindi 0,75pt è un limite inferiore ragionevole per qualsiasi filetto che debba sopravvivere alla stampa
Impaginazione su dati reali, non su dati di esempio
Un dettaglio da sistemare prima che il layout si consolidi: le colonne numeriche vanno allineate sul bordo destro, e il modo per farlo è misurare la larghezza renderizzata di ogni valore e posizionarlo all'indietro rispetto al confine della colonna, non riempire la stringa di spazi iniziali. Il riempimento con spazi si allinea solo in un font a spaziatura fissa, e nessuno compone un report finanziario in un font a spaziatura fissa. Fate passare prima i valori attraverso le routine di Delphi sensibili al locale come FormatFloat, così il separatore delle migliaia di cui misurate la larghezza è lo stesso che il locale del cliente mostrerà davvero
Il pericolo dell'impaginazione è che la scriviate contro il dataset dimostrativo, dove dieci righe corte stanno in una pagina e il ciclo non deve mai interrompersi. La produzione vi consegna un cliente la cui ragione sociale è lunga 140 caratteri e un estratto conto con 4.000 voci, e adesso il ciclo deve spezzarsi correttamente ogni volta. Lo schema che regge è un unico cursore Y che si sposta verso il basso man mano che sottraete l'altezza di ogni riga, più un controllo che apre una nuova pagina nel momento in cui il cursore supererebbe il margine inferiore. Verso il basso significa qui Y decrescente, ed è l'unico punto in cui l'origine in basso a sinistra resta controintuitiva. Tenete tutto questo in una sola routine che riemetta anche SetFont e ridisegni l'intestazione corrente sulla nuova pagina, e i bug da pagina sbagliata di uno non troveranno appiglio. Quando gli stessi report devono anche rispettare regole di archiviazione o di accessibilità, le scelte che fate proprio qui, quali font incorporate, se l'output è taggato, quali spazi colore usate, sono quelle che quegli standard controllano; la guida HotPDF a PDF/A, PDF/X e PDF/UA merita una lettura prima che il template si irrigidisca
Ogni chiamata mostrata qui, il posizionamento del testo, la registrazione dei font, l'incorporamento delle immagini e il disegno dei path, è inclusa nel componente HotPDF per Delphi per Delphi e C++Builder, il cui manuale di riferimento documenta l'intera API di output insieme alle funzionalità di moduli, cifratura e firma che le stanno accanto