HotPDF tegner vektorgrafik ved at opbygge en sti på den aktuelle side og derefter bede om at få den malet. Der er intet bitmap-trin indimellem. En linje, du tegner med MoveTo og LineTo, ender som PDF-stioperatorer i indholdsstrømmen, så den forbliver en ægte vektor: skarp ved 50 % zoom, skarp ved 1600 %, og en brøkdel af den størrelse, en rasteriseret version ville koste. Til diagrammer, tabellinjer, diagramakser og formulardekorationer er det præcis, hvad du ønsker, og API'et bag det er lille nok til at lære på én gang
Hele tegnefladen lever på THotPDF.CurrentPage. Mellem BeginDoc og EndDoc indstiller du farve og linjebredde på det sideobjekt, lægger geometri ned, og kalder en maleoperator for at anvende det. De fire primitiver, du vil bruge mest, er MoveTo og LineTo til vilkårlige stier, Rectangle til bokse, Circle til diske, og de to maleoperatorer Stroke og Fill
Koordinatsystemet har origo nederst til venstre
Dette er den ene ting, der spænder ben for alle, der kommer fra VCL. Det TCanvas, du maler kontroller med, sætter origo (nulpunktet) i øverste venstre hjørne, hvor Y vokser nedad. PDF gør det modsatte. HotPDF måler fra det nederste venstre hjørne af siden i punkter (1/72 tomme), hvor Y stiger, når du bevæger dig opad. Et punkt på Y := 720 sidder nær toppen af en US Letter-side, som er 792 punkter høj, og Y := 50 sidder nær bunden. Hvis din første tegning kommer ud spejlet vertikalt, er det derfor: kode overført fra skærmgrafik antager den forkerte retning og løber ud over den nederste kant
Den samme konvention styrer TextOut, så tekst og former deler én mental model, når du først har internaliseret den. Planlæg et layout ved at beslutte, hvor bunden af hvert element sidder, ikke toppen, og resten følger deraf
Stier: MoveTo, LineTo, Stroke
En optegnet sti (stroked path) er en pen løftet, placeret og trukket. MoveTo løfter pennen og sætter startpunktet uden at markere noget. Hver LineTo udvider den aktuelle sti til et nyt punkt. Intet vises på siden, før du kalder Stroke, som tegner den akkumulerede sti med den aktuelle stroke-farve og linjebredde, og derefter rydder stien, så næste MoveTo starter på en frisk
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;
To detaljer sparer rigtig fejlfindingstid. Linjebredde er en tilstand, ikke et argument: SetLineWidth indstiller den én gang, og hver efterfølgende Stroke bruger den værdi, indtil du ændrer den igen, hvilket er grunden til, at polylinjen ovenfor er tykkere end reglen (the rule). Og stien nulstilles efter hver Stroke, så en glemt Stroke betyder, at den geometri, du så omhyggeligt lagde ud, slet ikke renderes. Hvis en form mangler fra outputtet, er malekaldet det første sted at kigge
Koordinaterne er punkter, og punkter er brøkdele. MoveTo og LineTo accepterer Single-værdier, så en hårlinje på 0.5 punkter eller en position på 72.25 er lovlig og meningsfuld, ikke afrundet til den nærmeste hele enhed. Den præcision betyder noget i to modsatte retninger. En linjebredde under cirka 0.5 kan renderes som en enhedsafhængig tyndest mulig linje, der forsvinder på skærmen og dukker op igen ved udskrivning, så en synlig linje kræver en bredde, du sætter med vilje, i stedet for standarden. I den anden ende forhindrer fastgørelse (snapping) af tabellinjer og gitterlinjer til hele punkt-koordinater et tæt gitter i at se lidt ujævnt ud, hvor tilstødende linjer afrundes forskelligt. Bestem gitterafstanden i punkter på forhånd, og resten af layoutet arver den
Udfyldte former og farve
Lukkede primitiver kan udfyldes (filled) i stedet for at blive optegnet (outlined). Rectangle tager en position og en størrelse, Circle tager et centrum og en radius, og begge anvendes med Fill, som maler det indre i den aktuelle fill-farve, eller med Stroke kun for en omrids (outline). Fill-farve og stroke-farve er adskilte tilstande, der indstilles med SetRGBFillColor og SetRGBStrokeColor, som begge tager en enkelt TColor. Det betyder, at du kan genbruge Delphis farvekonstanter og RGB-hjælperen direkte
// 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;
Hold øje med argumentformen på Rectangle. Det er position-plus-størrelse, X, Y, Width, Height, ikke to modsatte hjørner. TCanvas.Rectangle, som Delphi-udviklere kender, tager (Left, Top, Right, Bottom), så muskelhukommelsen vil give HotPDF et andet hjørne, hvor det forventer en bredde og en højde, og boksen kommer ud i den forkerte størrelse. (X, Y)-parret er det nederste venstre hjørne, i overensstemmelse med sidens origo. For en cirkel er (X, Y) centrum, og det tredje argument er radius i punkter
Ét farvevalg det oprindelige eksempel fik forkert
En ældre version af dette eksempel seedede farver med Random($FFFFFF) på hver form. Det ser livligt ud, og det er det forkerte instinkt for genererede dokumenter. En PDF, du bygger ud fra kode, er som regel noget, du også gerne vil teste, og tilfældige fill-farver gør outputtet umuligt at sammenligne kørsel for kørsel: en byte-for-byte diff mod en kendt-god fil fejler hver gang uden nogen reel grund. Vælg eksplicitte farver. Når du ønsker variation på tværs af en række former, så driv det fra dine data eller et fast palet-array, så det samme input altid producerer den samme fil. Determinisme er mere værd end nyhedsværdi, når artefaktet bevæger sig gennem en release-pipeline
At sætte primitiverne sammen: en callout-boks
Hver primitiv er simpel i sig selv; gevinsten viser sig, når en håndfuld af dem sammensættes til noget, en rapport faktisk har brug for. En callout, den anmærkede boks, der peger på en figur og forklarer den, bruger alt, der er dækket indtil videre: et udfyldt rektangel med en kant, en optegnet (stroked) pointer-linje, en prik til at forankre pointeren, og tekst lagt ud inde i boksen ved hjælp af de samme bund-venstre-koordinater, som formerne bruger. FillAndStroke gør sig fortjent til sin plads her, idet den maler det indre og omridset af én sti i en enkelt anvendelse (commit) i stedet for at bygge rektanglet to gange
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Callout.pdf';
Pdf.BeginDoc;
// 1. The box: pale fill plus a visible border, one path, one commit.
// Rectangle is lower-left corner plus size, Y measured from the bottom
Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214)); // pale amber panel
Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40)); // darker rim
Pdf.CurrentPage.SetLineWidth(1);
Pdf.CurrentPage.Rectangle(90, 600, 240, 70);
Pdf.CurrentPage.FillAndStroke;
// 2. The pointer: one stroked segment from the box edge down
// toward the thing being annotated
Pdf.CurrentPage.SetLineWidth(1.5);
Pdf.CurrentPage.MoveTo(90, 615); // left edge of the box
Pdf.CurrentPage.LineTo(66, 546);
Pdf.CurrentPage.Stroke;
// 3. A filled dot anchors the pointer at its target
Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
Pdf.CurrentPage.Circle(64, 542, 3);
Pdf.CurrentPage.Fill;
// 4. The label, positioned relative to the box's lower-left corner.
// Text and shapes share one coordinate system, so the offsets
// are plain arithmetic against (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;
Bemærk hvor lidt tilstandsstyring komposit-formen (composite) har brug for. Fill-farve, stroke-farve og linjebredde indstilles hver især umiddelbart før den form, der bruger dem, så hver blok af tegningen læses som en selvstændig enhed og kan omarrangeres eller udtrækkes i en hjælper uden at trække skjult tilstand med sig. Pak dette ind i en procedure, der tager ankerpunktet og strengene, og du har en genanvendelig diagramanmærkning til en pris af fyrre linjer
Hvor vektortegning betaler sig, og hvor det ikke gør
Grib efter disse sti- og formkald, når geometrien genereres: diagrammets gitterlinjer og søjler, de linjerede streger i en fakturatabel, callout-bokse på et diagram, et logo-mærke udtrykt som en håndfuld stier. Det hele skalerer uden at sløre og tilføjer næsten intet til filstørrelsen, fordi et rektangel er nogle få tal i stedet for tusindvis af pixels. Bagsiden er også ærlig. Hvis det, du faktisk har, er et fotografi eller et skærmbillede, så tegn det som et billede med AddImage og ShowImage i stedet; at spore (trace) en bitmap med vektorkald giver dig ingenting. De lige segmenter, rektangler og cirkler ovenfor bærer størstedelen af reelt rapporteringsarbejde, og de tre forfininger, udviklere spørger om næste gang, kurver, stregmønstre (dash patterns) og gennemsigtighed, sidder på det samme sideobjekt
Kurver, dashes og gennemsigtighed kort fortalt
Frihåndskurver (Freeform curves) udvider det samme maskineri for stier, som du allerede har. CurveToC(X1, Y1, X2, Y2, X3, Y3) tilføjer et kubisk Bezier-segment fra det aktuelle punkt til (X3, Y3), der bøjer mod de to kontrolpunkter, og stenografivarianterne CurveToV og CurveToY dækker de tilfælde, hvor et kontrolpunkt falder sammen med et endepunkt. En sti kan blande LineTo og CurveToC-segmenter frit, før en enkelt Stroke eller Fill anvender det, hvilket er sådan, afrundede hjørner og glatte diagramlinjer bygges
Stiplede streger (dashed strokes) er en tilstand, præcis ligesom linjebredde. SetDash([3, 3], 0) skifter hvert efterfølgende stroke til et mønster med tre punkter tændt (on), tre punkter slukket (off), hvor arrayet staver on/off-længderne i punkter og det andet argument angiver fasen for, hvor cyklussen starter; NoDash returnerer pennen til en massiv linje. Indstil den, tegn (stroke) de gitterlinjer, der skal bruge den, og nulstil den før den næste massive linje, ellers vil stiplet stille og roligt inficere alt, hvad der følger
Gennemsigtighed kører gennem en navngiven grafiktilstand snarere end et farveargument, fordi alfa i PDF er en egenskab i ordbogen for grafiktilstanden. Registrer en på dokumentet med RegisterExtGState, send en fill-alfa og en stroke-alfa mellem 0 og 1, og anvend derefter det navn, den returnerer, med CurrentPage.SetGraphicsState; fills og strokes fra det punkt males med den registrerede uigennemsigtighed (opacity). Det er en tungere ceremoni end farvesætterne (color setters), og det er det hele værd den første gang en highlight-bjælke skal sidde over tekst uden at skjule den
Den resterende vane, der er værd at holde fast i, er verifikation. Genereret geometri kan passere på din maskine og fejle på en kundes maskine, som regel på grund af udskiftning af skrifttyper i enhver tekst, du blander ind, eller en antagelse om sidestørrelse, der ikke holder. Åbn den færdige fil på et par zoomniveauer for at bekræfte, at kanterne forbliver skarpe, og tjek, at hver form lander inden for den margenboks (margin box), du havde tiltænkt. Med et deterministisk farveskema kan det tjek automatiseres mod en reference-PDF frem for at blive vurderet med øjet
De MoveTo, LineTo, Stroke, Fill og farvekald, der er vist her, er en del af HotPDF-komponenten til Delphi og C++Builder