Articol tehnic

Desen vectorial cu HotPDF în Delphi: trasee și culoare

HotPDF desenează grafică vectorială construind un traseu pe pagina curentă și cerând apoi ca acesta să fie pictat. Nu există niciun pas de bitmap între cele două. O linie pe care o desenați cu MoveTo și LineTo ajunge ca operatori de traseu PDF în fluxul de conținut, deci rămâne un vector adevărat: clară la 50% mărire, clară la 1600% și cu o fracțiune din dimensiunea pe care ar costa-o o variantă rasterizată. Pentru diagrame, linii de tabel, axe de grafic și decorațiuni de formular, exact asta vă doriți, iar API-ul din spate este destul de mic ca să fie învățat dintr-o singură ședință

Toată suprafața de desen stă pe THotPDF.CurrentPage. Între BeginDoc și EndDoc setați culoarea și lățimea liniei pe acel obiect pagină, așezați geometria și apelați un operator de pictare care o confirmă. Cele patru primitive pe care le veți folosi cel mai des sunt MoveTo și LineTo pentru trasee arbitrare, Rectangle pentru casete, Circle pentru discuri și cei doi operatori de pictare, Stroke și Fill

Sistemul de coordonate are originea în stânga jos

Acesta este lucrul care încurcă pe toată lumea venită dinspre VCL. Obiectul TCanvas cu care pictați controale pune originea în colțul din stânga sus, cu Y crescând în jos. PDF face invers. HotPDF măsoară din colțul din stânga jos al paginii, în puncte (1/72 dintr-un inch), cu Y crescând pe măsură ce urcați. Un punct la Y := 720 stă lângă partea de sus a unei pagini US Letter, care are 792 de puncte înălțime, iar Y := 50 stă lângă partea de jos. Dacă primul dumneavoastră desen iese oglindit pe verticală, acesta este motivul: codul portat din grafica de ecran presupune direcția greșită și fuge dincolo de marginea de jos

Aceeași convenție guvernează și TextOut, așa că textul și formele împart un singur model mental, odată ce îl interiorizați. Planificați un aranjament hotărând unde stă baza fiecărui element, nu vârful, iar restul decurge de acolo

Comparație între originea din stânga sus a pânzei de ecran și originea din stânga jos a PDF-ului: același punct de lângă partea de sus a unei pagini US Letter se citește Y = 72 în cod TCanvas, dar Y = 720 în puncte HotPDF, deci codul portat fără inversarea lui Y desenează oglindit
HotPDF măsoară din colțul din stânga jos, în puncte, așa că un loc de lângă partea de sus a paginii de 612 pe 792 poartă Y = 720 — aceeași poziție fizică pe care codul TCanvas o adresează printr-un Y mic măsurat în jos

Trasee: MoveTo, LineTo, Stroke

Un traseu conturat este un stilou ridicat, așezat și tras. MoveTo ridică stiloul și fixează punctul de start fără să marcheze nimic. Fiecare LineTo prelungește traseul curent până la un punct nou. Nimic nu apare pe pagină până când apelați Stroke, care desenează traseul acumulat folosind culoarea de contur și lățimea de linie curente, apoi golește traseul, astfel încât următorul MoveTo să pornească de la zero

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

    // Lățimea liniei este în puncte și se aplică până când o schimbați.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // O linie orizontală lângă partea de sus a paginii (Y măsurat de jos).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // confirmă traseul; înainte nu s-a desenat nimic

    // O polilinie conectată mai groasă: trei segmente într-un singur traseu.
    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;

Două detalii economisesc timp real de depanare. Lățimea liniei este stare, nu argument: SetLineWidth o setează o dată, iar fiecare Stroke ulterior folosește acea valoare până când o schimbați din nou, motiv pentru care polilinia de mai sus este mai groasă decât linia. Iar traseul se resetează după fiecare Stroke, deci un Stroke uitat înseamnă că geometria pe care ați așezat-o cu atâta grijă nu se randează deloc. Dacă o formă lipsește din ieșire, apelul de pictare este primul loc unde să vă uitați

Coordonatele sunt puncte, iar punctele sunt fracționare. MoveTo și LineTo acceptă valori Single, deci o linie subțire de 0.5 puncte sau o poziție la 72.25 este legală și are sens, nu este rotunjită la cea mai apropiată unitate întreagă. Acea precizie contează în două direcții opuse. O lățime de linie sub aproximativ 0.5 se poate randa ca cea mai subțire linie posibilă, dependentă de dispozitiv, care dispare pe ecran și reapare la tipărire, deci o linie vizibilă vrea o lățime pe care o setați intenționat, nu valoarea implicită. La celălalt capăt, alinierea liniilor de tabel și a caroiajului la coordonate în puncte întregi împiedică o grilă densă să pară ușor neuniformă acolo unde liniile alăturate se rotunjesc diferit. Hotărâți pasul grilei în puncte de la început, iar restul aranjamentului îl moștenește

Forme umplute și culoare

Primitivele închise pot fi umplute, nu doar conturate. Rectangle primește o poziție și o dimensiune, Circle primește un centru și o rază, iar oricare dintre ele se confirmă cu Fill, care pictează interiorul în culoarea de umplere curentă, sau cu Stroke, doar pentru contur. Culoarea de umplere și culoarea de contur sunt bucăți de stare separate, setate cu SetRGBFillColor și SetRGBStrokeColor, ambele primind un singur TColor. Asta înseamnă că puteți refolosi direct constantele de culoare din Delphi și funcția ajutătoare RGB

Modelul de trasee HotPDF: MoveTo, LineTo, Rectangle și Circle construiesc în memorie un traseu curent invizibil, iar doar confirmarea cu Stroke, Fill sau FillAndStroke îl pictează folosind starea grafică persistentă de culoare de contur, culoare de umplere și lățime de linie, înainte de a goli zona tampon
Geometria se acumulează în tăcere în traseul curent până când un operator de pictare o confirmă cu culoarea de contur, culoarea de umplere și lățimea de linie memorate — un apel de pictare uitat lasă forma nedesenată
// Rectangle(X, Y, Width, Height): X și Y sunt colțul din stânga jos.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X și Y sunt centrul.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Doar contur: setați o culoare de contur și o lățime, apoi Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;

Fiți atent la forma argumentelor lui Rectangle. Este poziție plus dimensiune, X, Y, Width, Height, nu două colțuri opuse. Metoda TCanvas.Rectangle pe care o știu dezvoltatorii Delphi primește (Left, Top, Right, Bottom), deci memoria musculară îi va da lui HotPDF un al doilea colț acolo unde el așteaptă o lățime și o înălțime, iar caseta iese de dimensiune greșită. Perechea (X, Y) este colțul din stânga jos, în acord cu originea paginii. Pentru un cerc, (X, Y) este centrul, iar al treilea argument este raza în puncte

O alegere de culoare pe care exemplul original a greșit-o

O versiune mai veche a acestui exemplu semăna culorile cu Random($FFFFFF) pentru fiecare formă. Arată vioi și este instinctul greșit pentru documente generate. Un PDF pe care îl construiți din cod este de obicei ceva ce vreți și să testați, iar culorile de umplere aleatorii fac ieșirea imposibil de comparat de la o rulare la alta: o diferență octet cu octet față de un fișier bun cunoscut eșuează de fiecare dată, fără vreun motiv real. Alegeți culori explicite. Când vreți varietate pe o serie de forme, conduceți-o din datele dumneavoastră sau dintr-un tablou fix de paletă, astfel încât aceeași intrare să producă întotdeauna același fișier. Determinismul valorează mai mult decât noutatea atunci când artefactul trece printr-un flux de livrare

Punând primitivele împreună: o casetă de explicație

Fiecare primitivă este simplă în sine; câștigul se vede atunci când câteva dintre ele se compun în ceva de care un raport chiar are nevoie. O casetă de explicație, caseta adnotată care arată spre o figură și o lămurește, folosește tot ce am acoperit până acum: un dreptunghi umplut cu bordură, o linie indicatoare conturată, un punct care ancorează indicatorul și text așezat în interiorul casetei folosind aceleași coordonate cu originea în stânga jos ca formele. FillAndStroke își merită locul aici, pictând interiorul și conturul unui singur traseu într-o singură confirmare, în loc să construiască dreptunghiul de două ori

Anatomia unei casete de explicație asamblate din primitive HotPDF: un dreptunghi confirmat cu FillAndStroke, o linie indicatoare conturată din MoveTo și LineTo, un punct de ancorare umplut și bare de etichetă TextOut care împart aceeași grilă cu originea în stânga jos ca formele
Patru confirmări construiesc caseta de explicație — FillAndStroke pictează panoul și rama împreună, indicatorul și punctul refolosesc starea de contur și de umplere, iar fiecare decalaj de etichetă este aritmetică simplă față de colțul casetei (90, 600)
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Callout.pdf';
    Pdf.BeginDoc;

    // 1. Caseta: umplere palidă plus o bordură vizibilă, un traseu, o confirmare.
    //    Rectangle este colțul din stânga jos plus dimensiunea, Y măsurat de jos
    Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214));   // panou chihlimbariu palid
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40));  // ramă mai închisă
    Pdf.CurrentPage.SetLineWidth(1);
    Pdf.CurrentPage.Rectangle(90, 600, 240, 70);
    Pdf.CurrentPage.FillAndStroke;

    // 2. Indicatorul: un segment conturat de la marginea casetei în jos,
    //    spre lucrul care este adnotat
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.MoveTo(90, 615);        // marginea din stânga a casetei
    Pdf.CurrentPage.LineTo(66, 546);
    Pdf.CurrentPage.Stroke;

    // 3. Un punct umplut ancorează indicatorul la ținta lui
    Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
    Pdf.CurrentPage.Circle(64, 542, 3);
    Pdf.CurrentPage.Fill;

    // 4. Eticheta, poziționată față de colțul din stânga jos al casetei.
    //    Textul și formele împart un singur sistem de coordonate, deci
    //    decalajele sunt aritmetică simplă față de (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;

Observați cât de puțină administrare de stare cere compoziția. Culoarea de umplere, culoarea de contur și lățimea de linie sunt setate fiecare chiar înaintea formei care le folosește, așa că fiecare bloc al desenului se citește ca o unitate de sine stătătoare și poate fi reordonat sau extras într-o funcție ajutătoare fără să târască după el stare ascunsă. Împachetați asta într-o procedură care primește punctul de ancorare și șirurile și aveți o adnotare de diagramă reutilizabilă pentru prețul a patruzeci de linii

Unde se merită desenul vectorial și unde nu

Apelați la aceste funcții de traseu și de forme atunci când geometria este generată: caroiajul și barele unui grafic, liniile unui tabel de factură, casete de explicație pe o diagramă, o siglă exprimată prin câteva trasee. Toate se scalează fără neclaritate și adaugă aproape nimic la dimensiunea fișierului, pentru că un dreptunghi înseamnă câteva numere, nu mii de pixeli. Reversul este la fel de cinstit. Dacă ce aveți de fapt este o fotografie sau o captură de ecran, desenați-o ca imagine cu AddImage și ShowImage; trasarea unui bitmap prin apeluri vectoriale nu vă aduce nimic. Segmentele drepte, dreptunghiurile și cercurile de mai sus duc marea majoritate a muncii reale de raportare, iar cele trei rafinamente despre care dezvoltatorii întreabă imediat după, curbele, tiparele de linie întreruptă și transparența, stau pe același obiect pagină

Curbe, linii întrerupte și transparență, pe scurt

Curbele libere prelungesc aceeași mașinărie de trasee pe care o aveți deja. CurveToC(X1, Y1, X2, Y2, X3, Y3) adaugă un segment Bezier cubic de la punctul curent până la (X3, Y3), curbându-se spre cele două puncte de control, iar variantele prescurtate CurveToV și CurveToY acoperă cazurile în care un punct de control coincide cu o extremitate. Un traseu poate amesteca liber segmente LineTo și CurveToC înainte ca un singur Stroke sau Fill să îl confirme, și așa se construiesc colțurile rotunjite și liniile netede de grafic

Conturele întrerupte sunt stare, exact ca lățimea liniei. SetDash([3, 3], 0) comută fiecare contur ulterior pe un tipar de trei puncte desenate și trei puncte libere, tabloul indicând lungimile de rulare desenat/liber în puncte, iar al doilea argument defazând locul unde începe ciclul; NoDash readuce stiloul la linie continuă. Setați-l, conturați caroiajul care îl vrea și resetați-l înaintea următoarei linii continue, altfel linia întreruptă infectează în tăcere tot ce urmează

Transparența trece printr-o stare grafică denumită, nu printr-un argument de culoare, pentru că în PDF canalul alfa este o proprietate a dicționarului de stare grafică. Înregistrați una pe document cu RegisterExtGState, transmițând un alfa de umplere și un alfa de contur între 0 și 1, apoi aplicați numele returnat cu CurrentPage.SetGraphicsState; umplerile și contururile de acolo încolo pictează la opacitatea înregistrată. Este un ceremonial mai greoi decât cel al funcțiilor de culoare și merită prima dată când o bară de evidențiere trebuie să stea peste text fără să îl ascundă

Obiceiul care mai merită păstrat este verificarea. Geometria generată poate trece pe mașina dumneavoastră și cădea pe a unui client, de obicei din cauza substituirii fontului în orice text amestecat sau a unei presupuneri despre dimensiunea paginii care nu se susține. Deschideți fișierul finit la câteva niveluri de mărire ca să confirmați că muchiile rămân curate și verificați că fiecare formă aterizează în interiorul casetei de margini pe care ați intenționat-o. Cu o schemă de culori deterministă, acea verificare poate fi automatizată față de un PDF de referință, nu evaluată din ochi

Apelurile MoveTo, LineTo, Stroke, Fill și cele de culoare prezentate aici fac parte din HotPDF Delphi Component pentru Delphi și C++Builder