Articolo tecnico

Disegno su canvas HotPDF in Delphi: percorsi vettoriali e colore

HotPDF disegna grafica vettoriale costruendo un percorso sulla pagina corrente e poi chiedendo che venga dipinto. Non c'è alcun passaggio bitmap nel mezzo. Una linea che disegnate con MoveTo e LineTo finisce come operatori di percorso PDF nel content stream, quindi resta un vero vettore: nitida al 50% di zoom, nitida al 1600%, e una frazione della dimensione che costerebbe una versione rasterizzata. Per diagrammi, filetti di tabella, assi di grafici e decorazioni di moduli è esattamente ciò che volete, e l'API dietro tutto questo è abbastanza piccola da imparare in una sola seduta

L'intera superficie di disegno vive su THotPDF.CurrentPage. Tra BeginDoc ed EndDoc impostate colore e spessore linea su quell'oggetto pagina, deponete la geometria e chiamate un operatore di pittura per confermarla. Le quattro primitive che userete di più sono MoveTo e LineTo per percorsi arbitrari, Rectangle per i riquadri, Circle per i dischi e i due operatori di pittura Stroke e Fill

Il sistema di coordinate parte dal basso a sinistra

Questa è l'unica cosa che fa inciampare chiunque arrivi dalla VCL. Il TCanvas con cui dipingete i controlli mette l'origine nell'angolo superiore sinistro con Y crescente verso il basso. Il PDF fa l'opposto. HotPDF misura dall'angolo inferiore sinistro della pagina in punti (1/72 di pollice), con Y che aumenta man mano che si sale. Un punto a Y := 720 si trova vicino alla cima di una pagina US Letter, che è alta 792 punti, e Y := 50 si trova vicino al fondo. Se il vostro primo disegno esce specchiato verticalmente, il motivo è questo: il codice portato dalla grafica su schermo assume la direzione sbagliata e scappa oltre il bordo inferiore

La stessa convenzione governa TextOut, quindi testo e forme condividono un unico modello mentale una volta interiorizzato. Pianificate un layout decidendo dove si trova il fondo di ogni elemento, non la cima, e il resto viene da sé

Percorsi: MoveTo, LineTo, Stroke

Un percorso tracciato è una penna sollevata, appoggiata e trascinata. MoveTo solleva la penna e imposta il punto di partenza senza segnare nulla. Ogni LineTo estende il percorso corrente fino a un nuovo punto. Nulla appare sulla pagina finché non chiamate Stroke, che disegna il percorso accumulato usando il colore di tratto e lo spessore linea correnti, poi svuota il percorso in modo che il MoveTo successivo riparta da zero

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'DrawPaths.pdf';
    Pdf.BeginDoc;

    // Line width is in points and applies until you change it.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // A horizontal rule near the top of the page (Y measured from bottom).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // commit the path; nothing drew before this

    // A thicker connected polyline: three segments in one path.
    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 tempo di debug reale. Lo spessore linea è stato, non un 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 che avete disposto con tanta cura non viene mai renderizzata. Se una forma manca dall'output, la chiamata di pittura è il primo posto dove guardare

Le coordinate sono punti, e i punti sono frazionari. MoveTo e LineTo accettano valori Single, quindi una linea sottilissima a 0.5 punti o una posizione a 72.25 è legale e significativa, non arrotondata via all'unità intera più vicina. Quella precisione conta in due direzioni opposte. Uno spessore linea sotto circa 0.5 può essere reso come la linea più sottile possibile dipendente dal dispositivo, che scompare sullo schermo e ricompare in stampa, quindi un filetto visibile vuole uno spessore impostato di proposito anziché quello predefinito. All'altro estremo, agganciare i filetti di tabella e le linee di griglia a coordinate in punti interi evita che una griglia densa appaia leggermente irregolare dove linee adiacenti arrotondano in modo diverso. Decidete in anticipo il passo della griglia in punti e il resto del layout lo eredita

Forme riempite e colore

Le primitive chiuse possono essere riempite invece che solo contornate. Rectangle accetta una posizione e una dimensione, Circle accetta un centro e un raggio, ed entrambe si confermano con Fill, che dipinge l'interno con il colore di riempimento corrente, oppure con Stroke per il solo contorno. Colore di riempimento e colore di tratto sono due pezzi di stato separati, impostati con SetRGBFillColor e SetRGBStrokeColor, entrambi i quali accettano un singolo TColor. Ciò significa che potete riutilizzare direttamente le costanti colore di Delphi e l'helper RGB

// Rectangle(X, Y, Width, Height): X and Y are the lower-left corner.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X and Y are the center.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Outline only: set a stroke color and a width, then 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 accetta (Left, Top, Right, Bottom), quindi la memoria muscolare passerà a HotPDF un secondo angolo dove si aspetta una larghezza e un'altezza, e il riquadro esce della dimensione sbagliata. La coppia (X, Y) è l'angolo inferiore sinistro, 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 che costruite da codice è di solito qualcosa che volete anche testare, e i colori di riempimento casuali rendono l'output impossibile da confrontare da un'esecuzione all'altra: un diff byte per byte contro un file di riferimento noto fallisce ogni volta, senza alcun motivo reale. Scegliete colori espliciti. Quando volete varietà in una serie di forme, guidatela dai vostri dati o da un array di palette fisso, in modo che lo stesso input produca sempre lo stesso file. Il determinismo vale più della novità quando l'artefatto attraversa una pipeline di rilascio

Dove il disegno vettoriale ripaga, e dove no

Ricorrete a queste chiamate di percorso e forma quando la geometria è generata: linee di griglia e barre di grafici, i filetti di una tabella di fattura, riquadri di richiamo su un diagramma, un marchio espresso come una manciata di percorsi. Tutto questo scala senza sfocature e aggiunge quasi nulla alla dimensione del file, perché un rettangolo è pochi numeri anziché migliaia di pixel. Anche il rovescio della medaglia è onesto. Se ciò che avete davvero è una fotografia o uno screenshot, disegnatelo come immagine con AddImage e ShowImage; ricalcare una bitmap con chiamate vettoriali non vi fa guadagnare nulla. Anche le curve complesse sono fuori dall'ambito di questa pagina. Le primitive qui sopra sono segmenti rettilinei, rettangoli e cerchi, che coprono la grande maggioranza del lavoro di reportistica reale; tutto ciò che richiede curve di Bézier a mano libera è una parte separata dell'API

L'ultima abitudine da conservare è la verifica. La geometria generata può passare sulla vostra macchina e fallire su quella di un cliente, di solito per la sostituzione dei font in qualsiasi testo che mescolate o per un'assunzione sulla dimensione di 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 intendevate. Con uno schema di colori deterministico, quel controllo può essere automatizzato contro un PDF di riferimento anziché fatto a occhio

Le chiamate MoveTo, LineTo, Stroke, Fill e quelle relative al colore mostrate qui fanno parte del HotPDF Component per Delphi e C++Builder

Il disegno aggiornato include anche il box di richiamo composto da primitive vettoriali e ricorda che curve, tratti tratteggiati e trasparenza vanno valutati separatamente dal semplice percorso riempito