Tehnički članak

Crtanje na HotPDF Canvas-u u Delphiju: vektorske staze (paths) i boja

HotPDF iscrtava vektorsku grafiku tako da izgradi putanju na trenutnoj stranici, a zatim zatraži da se ona oboji. Između nema koraka s bitmapom. Linija koju nacrtate pomoću MoveTo i LineTo na kraju postaje PDF operatorima putanje u toku sadržaja, pa ostaje pravi vektor: oštra pri uvećanju 50%, oštra pri uvećanju 1600%, i tek djelić veličine koju bi koštala rasterizirana verzija. Za dijagrame, linije tablica, osi grafikona i ukrase obrazaca, to je upravo ono što želite, a API iza toga dovoljno je malen da se nauči u jednom sjedenju

Cijela ploha za crtanje živi na THotPDF.CurrentPage. Između BeginDoc i EndDoc postavljate boju i debljinu linije na tom objektu stranice, polažete geometriju, i pozivate operator bojenja kako biste je potvrdili. Četiri primitiva koje ćete najčešće koristiti su MoveTo i LineTo za proizvoljne putanje, Rectangle za pravokutnike, Circle za krugove, i dva operatora bojenja Stroke i Fill

Koordinatni sustav polazi od donjeg lijevog ruba

Ovo je jedina stvar koja spotakne svakoga tko dolazi iz VCL-a. TCanvas kojim bojite kontrole stavlja ishodište u gornji lijevi kut, a Y raste prema dolje. PDF radi suprotno. HotPDF mjeri od donjeg lijevog kuta stranice u točkama (1/72 inča), pri čemu Y raste kako idete prema gore. Točka na Y := 720 nalazi se blizu vrha stranice US Letter, koja je visoka 792 točke, a Y := 50 nalazi se blizu dna. Ako vam prvi crtež ispadne okomito zrcaljen, to je razlog: kod prenesen iz grafike zaslona pretpostavlja pogrešan smjer i otrči izvan donjeg ruba

Ista konvencija vrijedi za TextOut, pa tekst i oblici dijele jedan mentalni model čim ga usvojite. Planirajte raspored odlučujući gdje se nalazi dno svakog elementa, a ne vrh, i ostatak slijedi

Usporedba ishodišta gore-lijevo na zaslonskom platnu i ishodišta dolje-lijevo u PDF-u: ista točka pri vrhu stranice US Letter čita Y = 72 u TCanvas kodu, ali Y = 720 u HotPDF točkama, pa preneseni kod bez okretanja Y crta zrcalno
HotPDF mjeri od donjeg lijevog ugla u točkama, pa mjesto blizu vrha stranice 612 puta 792 nosi Y = 720 — ista fizička lokacija koju TCanvas kod adresira kao mali Y prema dolje

Putanje: MoveTo, LineTo, Stroke

Putanja s obrisom je pero koje se podigne, postavi i povuče. MoveTo podiže pero i postavlja početnu točku bez crtanja ičega. Svaki LineTo produžuje trenutnu putanju do nove točke. Ništa se ne pojavljuje na stranici dok ne pozovete Stroke, koji crta akumuliranu putanju koristeći trenutnu boju obrisa i debljinu linije, a zatim briše putanju tako da sljedeći MoveTo počinje iznova

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

    // Debljina linije je u točkama i vrijedi dok je ne promijenite.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // Vodoravna linija blizu vrha stranice (Y mjeren od dna).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // potvrdi putanju; ništa prije ovoga nije nacrtano

    // Deblja povezana izlomljena linija: tri segmenta u jednoj putanji.
    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;

Dva detalja štede stvarno vrijeme otklanjanja pogrešaka. Debljina linije je stanje, a ne argument: SetLineWidth je postavlja jednom, a svaki naredni Stroke koristi tu vrijednost dok je opet ne promijenite, zbog čega je gornja izlomljena linija deblja od ravne crte. A putanja se resetira nakon svakog Stroke, pa zaboravljeni Stroke znači da se geometrija koju ste tako pažljivo postavili uopće ne iscrtava. Ako neki oblik nedostaje u izlazu, poziv za bojenje prvo je mjesto za provjeru

Koordinate su u točkama, a točke su razlomljene. MoveTo i LineTo prihvaćaju vrijednosti tipa Single, pa je vlas od 0.5 točaka ili pozicija na 72.25 valjana i smislena, a ne zaokružena na najbliži cijeli broj. Ta preciznost je važna u dva suprotna smjera. Debljina linije ispod otprilike 0.5 može se iscrtati kao najtanja moguća linija ovisna o uređaju, koja nestane na zaslonu, a pojavi se pri ispisu, pa vidljiva crta treba širinu koju namjerno postavite umjesto zadane. Na drugom kraju, poravnavanje tabličnih linija i mrežnih linija na cjelobrojne koordinate u točkama sprječava da gusta mreža izgleda blago neravnomjerno tamo gdje se susjedne linije zaokružuju drugačije. Odlučite razmak mreže u točkama unaprijed i ostatak rasporeda ga nasljeđuje

Ispunjeni oblici i boja

Zatvoreni primitivi mogu se ispuniti umjesto ocrtati. Rectangle uzima poziciju i veličinu, Circle uzima središte i polumjer, a bilo koji od njih potvrđuje se pomoću Fill, koji boja unutrašnjost trenutnom bojom ispune, ili pomoću Stroke samo za obris. Boja ispune i boja obrisa odvojena su stanja, postavljaju se pomoću SetRGBFillColor i SetRGBStrokeColor, od kojih svaka uzima jedan TColor. To znači da možete izravno ponovno koristiti Delphijeve konstante boja i pomoćnu funkciju RGB

HotPDF model puta: MoveTo, LineTo, Rectangle i Circle grade nevidljivu tekuću putanju u memoriji, i samo predaja Stroke, Fill ili FillAndStroke crta je koristeći trajno grafičko stanje boje poteza, boje ispune i širine linije prije čišćenja međuspremnika
Geometrija se tiho nakuplja u trenutnoj putanji dok je operator slikanja ne potvrdi sa spremljenom stroke bojom, fill bojom i debljinom linije — zaboravljen poziv slikanja ostavlja oblik nenacrtanim
// Rectangle(X, Y, Width, Height): X i Y su donji lijevi kut.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X i Y su središte.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Samo obris: postavi boju obrisa i širinu, zatim Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;

Pripazite na oblik argumenata za Rectangle. To je pozicija-plus-veličina, X, Y, Width, Height, a ne dva suprotna kuta. TCanvas.Rectangle, koji poznaju Delphi razvijatelji, uzima (Left, Top, Right, Bottom), pa će mišićna memorija HotPDF-u predati drugi kut tamo gdje se očekuje širina i visina, i pravokutnik ispadne krive veličine. Par (X, Y) je donji lijevi kut, dosljedno ishodištu stranice. Za krug, (X, Y) je središte, a treći argument je polumjer u točkama

Jedan izbor boje koji je izvorni primjer pogrešno napravio

Starija verzija ovog primjera generirala je boje pomoću Random($FFFFFF) za svaki oblik. Izgleda živopisno, a to je pogrešan instinkt za generirane dokumente. PDF koji gradite iz koda obično je nešto što također želite testirati, a nasumične boje ispune čine izlaz neusporedivim iz pokretanja u pokretanje: usporedba bajt po bajt s poznato ispravnom datotekom svaki put ne uspije, bez pravog razloga. Birajte izričite boje. Kad želite raznolikost kroz niz oblika, izvedite je iz svojih podataka ili fiksnog niza palete, tako da isti ulaz uvijek proizvede istu datoteku. Determinizam vrijedi više od novosti kad artefakt prolazi kroz cjevovod izdavanja

Slaganje primitiva zajedno: okvir s oznakom

Svaki primitiv sam po sebi je jednostavan; isplativost se vidi kad se pregršt njih sastavi u nešto što izvještaj zaista treba. Oznaka (callout), okvir s napomenom koji pokazuje na sliku i objašnjava je, koristi sve dosad obrađeno: ispunjeni pravokutnik s obrubom, obrisanu liniju pokazivača, točku koja sidri pokazivač, i tekst postavljen unutar okvira koristeći iste koordinate donjeg lijevog kuta koje koriste oblici. FillAndStroke ovdje zaslužuje svoje mjesto, bojeći unutrašnjost i obris jedne putanje u jednoj potvrdi umjesto da se pravokutnik gradi dvaput

Anatomija okvira s natpisom sastavljenog od HotPDF primitiva: pravokutnik predan s FillAndStroke, potezana pokazna linija od MoveTo i LineTo, ispunjena točka sidra i TextOut trake natpisa koje dijele isti dolje-lijevo raster kao oblici
Četiri commita grade callout — FillAndStroke panel i rub slika zajedno, pokazivač i točka ponovno koriste stroke i fill stanje, a svaki ofset natpisa čista je aritmetika od ugla okvira (90, 600)
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Callout.pdf';
    Pdf.BeginDoc;

    // 1. Okvir: blijeda ispuna plus vidljiv obrub, jedna putanja, jedna potvrda.
    //    Rectangle je donji lijevi kut plus veličina, Y mjeren od dna
    Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214));   // blijedo jantarni panel
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40));  // tamniji rub
    Pdf.CurrentPage.SetLineWidth(1);
    Pdf.CurrentPage.Rectangle(90, 600, 240, 70);
    Pdf.CurrentPage.FillAndStroke;

    // 2. Pokazivač: jedan obrisani segment od ruba okvira prema dolje
    //    prema onome što se napominje
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.MoveTo(90, 615);        // lijevi rub okvira
    Pdf.CurrentPage.LineTo(66, 546);
    Pdf.CurrentPage.Stroke;

    // 3. Ispunjena točka sidri pokazivač na svom cilju
    Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
    Pdf.CurrentPage.Circle(64, 542, 3);
    Pdf.CurrentPage.Fill;

    // 4. Oznaka, pozicionirana u odnosu na donji lijevi kut okvira.
    //    Tekst i oblici dijele jedan koordinatni sustav, pa su pomaci
    //    obična aritmetika u odnosu na (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;

Primijetite koliko malo upravljanja stanjem ovaj sastav treba. Boja ispune, boja obrisa i debljina linije svaka se postavlja neposredno prije oblika koji je koristi, pa se svaki blok crteža čita kao samostalna cjelina i može se preurediti ili izdvojiti u pomoćnu funkciju bez povlačenja skrivenog stanja za sobom. Zamotajte ovo u proceduru koja uzima sidrenu točku i nizove znakova, i imate ponovno upotrebljivu napomenu za dijagram uz cijenu od svega četrdeset redaka

Gdje se vektorsko crtanje isplati, a gdje ne

Posegnite za ovim pozivima za putanje i oblike kad je geometrija generirana: mrežne linije i stupci grafikona, linije tablice računa, okviri s oznakama na dijagramu, znak logotipa izražen kao pregršt putanja. Sve se to skalira bez zamućenja i gotovo ništa ne dodaje veličini datoteke, jer je pravokutnik nekoliko brojeva, a ne tisuće piksela. I druga strana je poštena. Ako zapravo imate fotografiju ili snimku zaslona, umjesto toga to nacrtajte kao sliku pomoću AddImage i ShowImage; precrtavanje bitmape vektorskim pozivima ne donosi vam ništa. Ravni segmenti, pravokutnici i krugovi iznad nose veliku većinu stvarnog posla izvještavanja, a tri usavršavanja o kojima razvijatelji sljedeće pitaju, krivulje, uzorci crtkanja i prozirnost, nalaze se na istom objektu stranice

Ukratko o krivuljama, crtkanju i prozirnosti

Slobodne krivulje proširuju isti mehanizam putanja koji već imate. CurveToC(X1, Y1, X2, Y2, X3, Y3) dodaje kubični Bezierov segment od trenutne točke do (X3, Y3), savijajući se prema dvjema kontrolnim točkama, a skraćene varijante CurveToV i CurveToY pokrivaju slučajeve kad se jedna kontrolna točka poklapa s krajnjom točkom. Putanja može slobodno miješati segmente LineTo i CurveToC prije nego što ih jedan Stroke ili Fill potvrdi, i tako se grade zaobljeni kutovi i glatke linije grafikona

Crtkani obrisi su stanje, baš kao i debljina linije. SetDash([3, 3], 0) prebacuje svaki naredni obris na uzorak od tri točke uključeno, tri točke isključeno, pri čemu niz ispisuje duljine uključenih/isključenih segmenata u točkama, a drugi argument fazira gdje ciklus počinje; NoDash vraća pero na punu liniju. Postavite je, obrišite mrežne linije koje je trebaju, i vratite je prije sljedeće pune crte, inače crtkanje tiho zarazi sve što slijedi

Prozirnost prolazi kroz imenovano grafičko stanje, a ne kroz argument boje, jer je alfa u PDF-u svojstvo rječnika grafičkog stanja. Registrirajte ga u dokumentu pomoću RegisterExtGState, predajući alfu ispune i alfu obrisa između 0 i 1, zatim primijenite naziv koji vraća pomoću CurrentPage.SetGraphicsState; ispune i obrisi od te točke boje se s registriranom prozirnošću. To je teža ceremonija od postavljača boje, a isplati se prvi put kad traka za isticanje mora ležati preko teksta bez da ga sakrije

Preostala navika vrijedna zadržavanja je provjera. Generirana geometrija može proći na vašem stroju, a zakazati na korisnikovom, obično zbog zamjene fonta u bilo kojem tekstu koji umiješate, ili pretpostavke o veličini stranice koja ne vrijedi. Otvorite gotovu datoteku pri nekoliko razina zuma kako biste potvrdili da rubovi ostaju čisti, i provjerite da svaki oblik pada unutar okvira margine koji ste namjeravali. S determinističkom shemom boja, ta se provjera može automatizirati usporedbom s referentnim PDF-om umjesto procjenjivati okom

Pozivi MoveTo, LineTo, Stroke, Fill, i pozivi za boju prikazani ovdje dio su HotPDF Delphi komponente za Delphi i C++Builder