HotPDF disegna grafica vettoriale costruendo un percorso sulla pagina corrente e chiedendo poi che venga dipinto. Non c'è alcun passaggio bitmap nel mezzo. Una linea tracciata con MoveTo e LineTo finisce come operatore di percorso PDF nel content stream, quindi resta un vero vettore: nitida al 50% di zoom, nitida al 1600% e con un ingombro pari a una frazione di quello che costerebbe una versione rasterizzata. Per diagrammi, filetti di tabella, assi di grafici e decorazioni dei moduli è esattamente ciò che serve, e l'API che c'è dietro è abbastanza piccola da imparare in una sola sessione
L'intera superficie di disegno vive su THotPDF.CurrentPage. Fra BeginDoc ed EndDoc impostate colore e spessore della linea su quell'oggetto pagina, posate la geometria e chiamate un operatore di disegno per confermarla. Le quattro primitive che userete di più sono MoveTo e LineTo per i percorsi arbitrari, Rectangle per i riquadri, Circle per i dischi, e i due operatori di disegno Stroke e Fill
Il sistema di coordinate è in alto a sinistra, come TCanvas
Questa è la cosa che fa inciampare gli sviluppatori che conoscono le specifiche del PDF. Lo spazio utente del PDF mette l'origine nell'angolo in basso a sinistra con la Y che cresce verso l'alto, ma le chiamate di disegno pagina di HotPDF non vi costringono a lavorarci. Come il TCanvas con cui dipingete i controlli, HotPDF misura dall'angolo in alto a sinistra della pagina in punti (1/72 di pollice), con la Y che cresce scendendo, e THPDFPage.YProjection capovolge ogni Y nello spazio utente PDF quando scrive il content stream. Un punto a Y := 72 sta un pollice sotto la sommità di una pagina US Letter, alta 792 punti, mentre Y := 720 sta un pollice sopra il fondo. Se il vostro primo disegno esce specchiato in verticale, la causa più comune è codice copiato da un esempio PDF grezzo che sottrae già la Y dall'altezza della pagina, così HotPDF la capovolge una seconda volta
La stessa convenzione governa TextOut, dove per impostazione predefinita la Y che passate è il bordo superiore del testo, quindi testo e forme condividono un unico modello mentale. Progettate un layout decidendo dove sta la sommità di ciascun elemento e il resto segue. Le eccezioni sono le chiamate che indirizzano il file PDF anziché la superficie di disegno: i setter dei page box come SetCropBox, e la famiglia StampLoadedPageText e StampLoadedPageImage che timbra un documento già caricato, ricevono coordinate dello spazio utente PDF con l'origine in basso a sinistra
Percorsi: MoveTo, LineTo, Stroke
Un percorso tracciato è una penna sollevata, appoggiata e trascinata. MoveTo solleva la penna e fissa il punto di partenza senza segnare nulla. Ogni LineTo estende il percorso corrente fino a un nuovo punto. Sulla pagina non compare nulla finché non chiamate Stroke, che disegna il percorso accumulato usando il colore di tratto e lo spessore correnti, poi azzera il percorso così che il MoveTo successivo riparta da capo
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'DrawPaths.pdf';
Pdf.BeginDoc;
// Lo spessore è in punti e vale finché non lo cambiate.
Pdf.CurrentPage.SetLineWidth(1.5);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
// Una riga orizzontale in alto nella pagina (Y misurata dall'alto).
Pdf.CurrentPage.MoveTo(72, 72);
Pdf.CurrentPage.LineTo(523, 72);
Pdf.CurrentPage.Stroke; // conferma il percorso; prima non si vedeva nulla
// Una polilinea più spessa: tre segmenti in un solo percorso.
Pdf.CurrentPage.SetLineWidth(3);
Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
Pdf.CurrentPage.MoveTo(72, 152);
Pdf.CurrentPage.LineTo(172, 102);
Pdf.CurrentPage.LineTo(272, 172);
Pdf.CurrentPage.LineTo(372, 112);
Pdf.CurrentPage.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Due dettagli fanno risparmiare parecchio tempo di debug. Lo spessore della linea è stato, non argomento: SetLineWidth lo imposta una volta e ogni Stroke successivo usa quel valore finché non lo cambiate di nuovo, ed è per questo che la polilinea qui sopra è più spessa del filetto. E il percorso si azzera dopo ogni Stroke, quindi uno Stroke dimenticato significa che la geometria posata con tanta cura non viene disegnata affatto. Se una forma manca dall'uscita, la chiamata di disegno è il primo posto in cui guardare
Le coordinate sono in punti, e i punti sono frazionari. MoveTo e LineTo accettano valori Single, quindi un filo di 0.5 punti o una posizione a 72.25 sono legittimi e significativi, non arrotondati via all'unità intera più vicina. Quella precisione conta in due direzioni opposte. Uno spessore inferiore a circa 0.5 può essere disegnato come la linea più sottile possibile dipendente dal dispositivo, che sparisce a schermo e riappare in stampa, perciò un filetto visibile vuole uno spessore scelto di proposito invece del valore predefinito. All'estremo opposto, agganciare filetti di tabella e linee di griglia a coordinate intere evita che una griglia fitta sembri leggermente irregolare dove linee adiacenti arrotondano in modo diverso. Decidete in anticipo il passo della griglia in punti e il resto dell'impaginazione lo eredita
Forme riempite e colore
Le primitive chiuse possono essere riempite invece che contornate. Rectangle riceve una posizione e una dimensione, Circle riceve un centro e un raggio, e l'una o l'altra si confermano con Fill, che dipinge l'interno nel colore di riempimento corrente, oppure con Stroke per il solo contorno. Colore di riempimento e colore di tratto sono due pezzi di stato distinti, impostati con SetRGBFillColor e SetRGBStrokeColor, che ricevono entrambi un singolo TColor. Ciò significa che potete riusare direttamente le costanti di colore di Delphi e la funzione RGB
// Rectangle(X, Y, Width, Height): X e Y sono l'angolo in alto a sinistra.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 202, 160, 90);
Pdf.CurrentPage.Fill;
// Circle(X, Y, Radius): X e Y sono il centro.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 247, 45);
Pdf.CurrentPage.Fill;
// Solo contorno: impostate colore e spessore del tratto, poi Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 332, 160, 60);
Pdf.CurrentPage.Stroke;
Attenzione alla forma degli argomenti di Rectangle. È posizione più dimensione, X, Y, Width, Height, non due angoli opposti. Il TCanvas.Rectangle che gli sviluppatori Delphi conoscono riceve (Left, Top, Right, Bottom), quindi la memoria muscolare passerà a HotPDF un secondo angolo dove esso si aspetta una larghezza e un'altezza, e il riquadro esce della misura sbagliata. La coppia (X, Y) è l'angolo in alto a sinistra, coerente con l'origine della pagina, e il riquadro si estende verso destra e verso il basso da lì. Per un cerchio, (X, Y) è il centro e il terzo argomento è il raggio in punti
Una scelta di colore che l'esempio originale sbagliava
Una versione più vecchia di questo esempio inizializzava i colori con Random($FFFFFF) su ogni forma. Sembra vivace, ed è l'istinto sbagliato per i documenti generati. Un PDF costruito da codice è di solito qualcosa che volete anche testare, e i colori di riempimento casuali rendono impossibile confrontare l'uscita fra una esecuzione e l'altra: un diff byte per byte contro un file di riferimento fallisce ogni volta, senza motivo reale. Scegliete colori espliciti. Quando volete varietà lungo una serie di forme, pilotatela dai vostri dati o da un array di palette fissa, così lo stesso ingresso produce sempre lo stesso file. Il determinismo vale più della novità quando l'artefatto attraversa una catena di rilascio
Mettere insieme le primitive: un riquadro di richiamo
Ogni primitiva è semplice presa da sola; il guadagno si vede quando una manciata di esse compone qualcosa di cui un rapporto ha davvero bisogno. Un richiamo, il riquadro annotato che punta a una figura e la spiega, usa tutto ciò che abbiamo visto finora: un rettangolo riempito con bordo, una linea di puntamento tracciata, un punto che ancora il puntatore e del testo disposto dentro il riquadro con le stesse coordinate in alto a sinistra delle forme. FillAndStroke si guadagna qui il suo posto, dipingendo interno e contorno di un solo percorso in una sola conferma invece di costruire il rettangolo due volte
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Callout.pdf';
Pdf.BeginDoc;
// 1. Il riquadro: riempimento chiaro più bordo visibile, un percorso, una conferma.
// Rectangle vuole angolo in alto a sinistra più dimensioni, Y misurata dall'alto
Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214)); // pannello ambra chiaro
Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40)); // bordo più scuro
Pdf.CurrentPage.SetLineWidth(1);
Pdf.CurrentPage.Rectangle(90, 122, 240, 70);
Pdf.CurrentPage.FillAndStroke;
// 2. Il puntatore: un segmento tracciato dal bordo del riquadro verso il basso
// in direzione della cosa che state annotando
Pdf.CurrentPage.SetLineWidth(1.5);
Pdf.CurrentPage.MoveTo(90, 177); // bordo sinistro del riquadro
Pdf.CurrentPage.LineTo(66, 246);
Pdf.CurrentPage.Stroke;
// 3. Un punto riempito ancora il puntatore al suo bersaglio
Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
Pdf.CurrentPage.Circle(64, 250, 3);
Pdf.CurrentPage.Fill;
// 4. L'etichetta, posizionata rispetto all'angolo in alto a sinistra del riquadro.
// Testo e forme condividono un solo sistema di coordinate, quindi gli
// scostamenti sono semplice aritmetica rispetto a (90, 122); ogni Y è il bordo superiore del testo
Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
Pdf.CurrentPage.TextOut(102, 134, 0, 'Check this total');
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(102, 152, 0, 'The rounding rule changed in the');
Pdf.CurrentPage.TextOut(102, 164, 0, 'June release; verify against v2.1');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Notate quanta poca gestione di stato richieda il composito. Colore di riempimento, colore di tratto e spessore vengono impostati subito prima della forma che li usa, così ogni blocco del disegno si legge come una unità autonoma e può essere riordinato o estratto in una procedura di supporto senza trascinarsi dietro stato nascosto. Racchiudete tutto questo in una procedura che riceve il punto di ancoraggio e le stringhe, e avete una annotazione di diagramma riutilizzabile al costo di quaranta righe
Dove il disegno vettoriale rende, e dove no
Ricorrete a queste chiamate di percorso e forma quando la geometria è generata: linee di griglia e barre dei grafici, i filetti di una tabella di fattura, riquadri di richiamo su un diagramma, un marchio espresso come una manciata di percorsi. Tutto ciò scala senza sfocature e aggiunge quasi nulla alla dimensione del file, perché un rettangolo è qualche numero anziché migliaia di pixel. Anche il rovescio della medaglia è onesto. Se ciò che avete davvero è una fotografia o una schermata, disegnatela invece come immagine con AddImage e ShowImage; ricalcare una bitmap con chiamate vettoriali non vi porta nulla. I segmenti rettilinei, i rettangoli e i cerchi visti sopra reggono la larga maggioranza del lavoro reale di reportistica, e i tre affinamenti che gli sviluppatori chiedono subito dopo, curve, tratteggi e trasparenza, stanno sullo stesso oggetto pagina
Curve, tratteggi e trasparenza in breve
Le curve libere estendono la stessa macchina dei percorsi che avete già. CurveToC(X1, Y1, X2, Y2, X3, Y3) aggiunge un segmento di Bezier cubica dal punto corrente a (X3, Y3), piegando verso i due punti di controllo, e le varianti abbreviate CurveToV e CurveToY coprono i casi in cui un punto di controllo coincide con un estremo. Un percorso può mescolare liberamente segmenti LineTo e CurveToC prima che un solo Stroke o Fill lo confermi, ed è così che si costruiscono angoli arrotondati e linee di grafico morbide
I tratti tratteggiati sono stato, esattamente come lo spessore. SetDash([3, 3], 0) commuta ogni tratto successivo su uno schema di tre punti pieni e tre vuoti, con l'array che indica in punti le lunghezze dei tratti attivi e inattivi e il secondo argomento che sfasa il punto di inizio del ciclo; NoDash riporta la penna alla linea continua. Impostatelo, tracciate le linee di griglia che lo vogliono e riportatelo indietro prima del filetto continuo successivo, altrimenti il tratteggio contagia in silenzio tutto ciò che segue
La trasparenza passa per uno stato grafico con nome anziché per un argomento di colore, perché nel PDF l'alfa è una proprietà del dizionario di stato grafico. Registratene uno sul documento con RegisterExtGState, passando un alfa di riempimento e un alfa di tratto fra 0 e 1, poi applicate il nome restituito con CurrentPage.SetGraphicsState; da quel punto riempimenti e tratti dipingono con l'opacità registrata. È una cerimonia più pesante rispetto ai setter di colore, e ne vale la pena la prima volta che una barra di evidenziazione deve stare sopra il testo senza nasconderlo
L'abitudine restante che vale la pena tenere è la verifica. La geometria generata può passare sulla vostra macchina e fallire su quella di un cliente, di solito per una sostituzione di font in un testo mescolato al disegno o per una assunzione sul formato pagina che non regge. Aprite il file finito a qualche livello di zoom per confermare che i bordi restino puliti, e controllate che ogni forma cada dentro il riquadro dei margini che avevate previsto. Con uno schema di colori deterministico, quel controllo si può automatizzare contro un PDF di riferimento invece di farlo a occhio
Le chiamate MoveTo, LineTo, Stroke, Fill e quelle di colore mostrate qui fanno parte del HotPDF Delphi Component per Delphi e C++Builder