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 parte in basso a sinistra
Questa è la cosa che fa inciampare chiunque arrivi dalla VCL. Il TCanvas con cui dipingete i controlli mette l'origine nell'angolo in alto a sinistra con la Y che cresce verso il basso. Il PDF fa l'opposto. HotPDF misura dall'angolo in basso a sinistra della pagina in punti (1/72 di pollice), con la Y che cresce salendo. Un punto a Y := 720 si trova vicino alla sommità di una pagina US Letter, alta 792 punti, mentre Y := 50 sta vicino al fondo. Se il vostro primo disegno esce specchiato in verticale, il motivo è questo: codice portato dalla grafica a schermo presuppone la direzione sbagliata ed esce dal bordo inferiore
La stessa convenzione governa TextOut, quindi testo e forme condividono un unico modello mentale una volta che lo avete interiorizzato. Progettate un layout decidendo dove sta il fondo di ciascun elemento, non la sommità, e il resto segue
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 dal basso).
Pdf.CurrentPage.MoveTo(72, 720);
Pdf.CurrentPage.LineTo(523, 720);
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, 640);
Pdf.CurrentPage.LineTo(172, 690);
Pdf.CurrentPage.LineTo(272, 620);
Pdf.CurrentPage.LineTo(372, 680);
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 basso a sinistra.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;
// Circle(X, Y, Radius): X e Y sono il centro.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 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, 400, 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 basso a sinistra, coerente con l'origine della pagina. 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 dal basso a sinistra usate dalle 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 basso a sinistra più dimensioni, Y misurata dal basso
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, 600, 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, 615); // bordo sinistro del riquadro
Pdf.CurrentPage.LineTo(66, 546);
Pdf.CurrentPage.Stroke;
// 3. Un punto riempito ancora il puntatore al suo bersaglio
Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
Pdf.CurrentPage.Circle(64, 542, 3);
Pdf.CurrentPage.Fill;
// 4. L'etichetta, posizionata rispetto all'angolo in basso a sinistra del riquadro.
// Testo e forme condividono un solo sistema di coordinate, quindi gli
// scostamenti sono semplice aritmetica rispetto a (90, 600)
Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
Pdf.CurrentPage.TextOut(102, 645, 0, 'Check this total');
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(102, 628, 0, 'The rounding rule changed in the');
Pdf.CurrentPage.TextOut(102, 616, 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