Teknisk artikkel

HotPDF Canvas Tegning i Delphi: Vektorstier og Farge

HotPDF tegner vektorgrafikk ved å bygge en sti (path) på den gjeldende siden og deretter be om at den males. Det er ikke noe bitmap-trinn i mellom. En linje du tegner med MoveTo og LineTo ender opp som PDF-stioperatorer i innholdsstrømmen, så den forblir en sann vektor: skarp ved 50 % zoom, skarp ved 1600 %, og en brøkdel av størrelsen en rasterisert versjon ville koste. For diagrammer, tabelllinjer, diagramakser og skjemadekorasjoner er det nøyaktig hva du ønsker, og API-et bak det er lite nok til å læres i én økt

Hele tegneflaten lever på THotPDF.CurrentPage. Mellom BeginDoc og EndDoc angir du farge og linjebredde på det sideobjektet, legger ned geometri, og kaller en maleoperator for å forplikte det (commit). De fire primitivene du vil bruke mest er MoveTo og LineTo for vilkårlige stier, Rectangle for bokser, Circle for skiver, og de to maleoperatorene Stroke og Fill

Koordinatsystemet er nederst til venstre

Dette er den ene tingen som feller alle som ankommer fra VCL. TCanvas som du maler kontroller med, plasserer origo (startpunktet) øverst til venstre med Y som vokser nedover. PDF gjør det motsatte. HotPDF måler fra nederste venstre hjørne av siden i punkter (1/72 tomme), med Y som øker når du beveger deg oppover. Et punkt ved Y := 720 sitter nær toppen av en US Letter-side, som er 792 punkter høy, og Y := 50 sitter nær bunnen. Hvis din første tegning kommer ut speilvendt vertikalt, er dette grunnen: kode overført fra skjermgrafikk antar feil retning og renner utenfor den nedre kanten

Den samme konvensjonen styrer TextOut, så tekst og former deler én mental modell når du internaliserer den. Planlegg et oppsett ved å bestemme hvor bunnen av hvert element sitter, ikke toppen, og resten følger etter

Stier: MoveTo, LineTo, Stroke

En opptrukket sti (stroked path) er en penn løftet, plassert og dratt. MoveTo løfter pennen og setter startpunktet uten å markere noe. Hver LineTo utvider den gjeldende stien til et nytt punkt. Ingenting vises på siden før du kaller Stroke, som tegner den akkumulerte stien ved å bruke gjeldende strekfarge og linjebredde, og deretter tømmer stien slik at neste MoveTo starter på nytt

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

    // Linjebredde er i punkter og gjelder til du endrer den.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // En horisontal linje nær toppen av siden (Y målt fra bunnen).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // forplikt stien; ingenting tegnet før dette

    // En tykkere sammenkoblet polylinje: tre segmenter i én sti.
    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 reell feilsøkingstid. Linjebredde er tilstand, ikke et argument: SetLineWidth angir den én gang, og hver påfølgende Stroke bruker den verdien inntil du endrer den igjen, noe som er grunnen til at polylinjen ovenfor er tykkere enn linjen. Og stien nullstilles etter hver Stroke, så en glemt Stroke betyr at geometrien du så nøye la ut aldri gjengis overhodet. Hvis en form mangler fra utdataene, er malekallet det første stedet å se

Koordinatene er punkter, og punkter er brøkdeler. MoveTo og LineTo aksepterer Single-verdier, så en hårlinje på 0.5 punkter eller en posisjon på 72.25 er lovlig og meningsfull, ikke avrundet bort til nærmeste hele enhet. Den presisjonen betyr noe i to motsatte retninger. En linjebredde under ca 0.5 kan gjengis som en enhetsavhengig tynnest mulig linje som forsvinner på skjermen og dukker opp igjen når den skrives ut, så en synlig linje vil ha en bredde du angir med hensikt snarere enn standarden. I den andre enden, det å smekke tabelllinjer og rutenettlinjer til heltallskoordinater hindrer at et tett rutenett ser litt ujevnt ut der tilstøtende linjer avrundes forskjellig. Bestem rutenettavstanden i punkter på forhånd, og resten av oppsettet arver den

Fylte former og farge

Lukkede primitiver kan fylles i stedet for å bli omrisset. Rectangle tar en posisjon og en størrelse, Circle tar et senter og en radius, og hvilken som helst av dem forpliktes med Fill, som maler interiøret i den gjeldende fyllfargen, eller med Stroke kun for et omriss. Fyllfarge og strekfarge er separate tilstandsbiter, angitt med SetRGBFillColor og SetRGBStrokeColor, som begge tar en enkelt TColor. Det betyr at du kan gjenbruke Delphis fargekonstanter og RGB-hjelperen direkte

// Rectangle(X, Y, Width, Height): X og Y er nedre venstre hjørne.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X og Y er senteret.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Kun omriss: angi en strekfarge og en bredde, deretter Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;

Vær oppmerksom på argumentets form på Rectangle. Den er posisjon-pluss-størrelse, X, Y, Width, Height, ikke to motsatte hjørner. TCanvas.Rectangle som Delphi-utviklere kjenner, tar (Left, Top, Right, Bottom), så muskelminnet vil gi HotPDF et andre hjørne der den forventer en bredde og en høyde, og boksen kommer ut i feil størrelse. (X, Y)-paret er det nedre venstre hjørnet, konsistent med sideopprinnelsen. For en sirkel er (X, Y) sentrum, og det tredje argumentet er radien i punkter

Ett fargevalg det opprinnelige eksemplet tok feil av

En eldre versjon av dette eksemplet sådde farger med Random($FFFFFF) på hver form. Det ser livlig ut, og det er feil instinkt for genererte dokumenter. En PDF du bygger fra kode er vanligvis noe du også ønsker å teste, og tilfeldige fyllfarger gjør utdataene umulig å sammenligne kjøring for kjøring: en byte-for-byte-diff mot en kjent god fil feiler hver gang, uten noen reell grunn. Velg eksplisitte farger. Når du ønsker variasjon over en serie med former, driv den fra dataene dine eller en fast palett-array, slik at det samme inndataet alltid produserer den samme filen. Determinisme er verdt mer enn nyhet når artefakten beveger seg gjennom en utgivelsesrørledning (release pipeline)

Å sette sammen primitivene: en bildeforklaringsboks (callout)

Hver primitiv er enkel på egen hånd; gevinsten viser seg når en håndfull av dem komponeres til noe en rapport faktisk trenger. En callout, den kommenterte boksen som peker på en figur og forklarer den, bruker alt dekket så langt: et fylt rektangel med en kant, en trukket pekerlinje, en prikk for å forankre pekeren, og tekst lagt ut inne i boksen ved å bruke de samme nederst til venstre koordinatene formene bruker. FillAndStroke fortjener plassen sin her, og maler interiøret og omrisset til én sti i en enkelt forpliktelse (commit) i stedet for å bygge rektangelet to ganger

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

    // 1. Boksen: blekt fyll pluss en synlig kantlinje, én sti, én commit.
    //    Rectangle er nedre venstre hjørne pluss størrelse, Y målt fra bunnen
    Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214));   // blekt gult panel
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40));  // mørkere kant
    Pdf.CurrentPage.SetLineWidth(1);
    Pdf.CurrentPage.Rectangle(90, 600, 240, 70);
    Pdf.CurrentPage.FillAndStroke;

    // 2. Pekeren: ett opptrukket segment fra bokskanten og ned
    //    mot det som blir kommentert
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.MoveTo(90, 615);        // venstre kant av boksen
    Pdf.CurrentPage.LineTo(66, 546);
    Pdf.CurrentPage.Stroke;

    // 3. En fylt prikk forankrer pekeren ved målet
    Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
    Pdf.CurrentPage.Circle(64, 542, 3);
    Pdf.CurrentPage.Fill;

    // 4. Etiketten, plassert relativt til boksens nedre venstre hjørne.
    //    Tekst og former deler ett koordinatsystem, så forskyvningene
    //    er ren aritmetikk mot (90, 600)
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
    Pdf.CurrentPage.TextOut(102, 645, 0, 'Sjekk denne totalen');
    Pdf.CurrentPage.SetFont('Arial', [], 9);
    Pdf.CurrentPage.TextOut(102, 628, 0, 'Avrundingsregelen ble endret i');
    Pdf.CurrentPage.TextOut(102, 616, 0, 'juni-utgivelsen; bekreft mot v2.1');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Legg merke til hvor lite tilstandsstyring kompositten trenger. Fyllfarge, strekfarge og linjebredde settes hver for seg umiddelbart før formen som bruker dem, så hver blokk av tegningen leses som en selvstendig enhet og kan omorganiseres eller trekkes ut til en hjelper uten å dra skjult tilstand med seg. Pakk dette inn i en prosedyre som tar ankerpunktet og strengene, og du har en gjenbrukbar diagramkommentar for prisen av førti linjer

Hvor vektortegning lønner seg, og hvor det ikke gjør det

Grip til disse sti- og formkallene når geometrien er generert: diagramrutenettlinjer og -stolper, de strekede linjene i en fakturatabell, callout-bokser i et diagram, et logomerke uttrykt som en håndfull stier. Alt av det skalerer uten uskarphet og legger nesten ingenting til filstørrelsen, fordi et rektangel er noen få tall i stedet for tusenvis av piksler. Den andre siden av mynten er også ærlig. Hvis det du faktisk har er et fotografi eller et skjermbilde, tegn det som et bilde med AddImage og ShowImage i stedet; å spore en bitmap med vektorkall kjøper deg ingenting. De rette segmentene, rektanglene og sirklene ovenfor bærer de aller fleste reelle rapporteringsoppgaver, og de tre forbedringene utviklere spør om deretter, kurver, strekmønstre (dashes) og gjennomsiktighet, sitter på det samme sideobjektet

Kurver, stiplete linjer og gjennomsiktighet kort fortalt

Friform-kurver utvider det samme stimaskineriet du allerede har. CurveToC(X1, Y1, X2, Y2, X3, Y3) legger til et kubisk Bezier-segment fra det gjeldende punktet til (X3, Y3), bøyer mot de to kontrollpunktene, og stenografi-variantene CurveToV og CurveToY dekker tilfellene der ett kontrollpunkt sammenfaller med et endepunkt. En sti kan blande LineTo og CurveToC-segmenter fritt før én enkelt Stroke eller Fill forplikter det, noe som er hvordan avrundede hjørner og glatte diagramlinjer bygges

Stiplete streker er tilstand, akkurat som linjebredde. SetDash([3, 3], 0) bytter hver påfølgende strek til et tre-punkt-på, tre-punkt-av-mønster, hvor arrayen staver ut på/av-lengdene i punkter og det andre argumentet faser hvor syklusen starter; NoDash returnerer pennen til en hel linje. Still den inn, strek opp rutenettlinjene som ønsker den, og tilbakestill den før neste hele linje, ellers infiserer mønsteret i stillhet alt som følger

Gjennomsiktighet (transparency) kjører gjennom en navngitt grafisk tilstand snarere enn et fargeargument, fordi alfa i PDF er en egenskap ved grafikktilstandsordboken. Registrer en på dokumentet med RegisterExtGState, ved å sende en fyll-alfa og en strek-alfa mellom 0 og 1, og bruk deretter navnet den returnerer med CurrentPage.SetGraphicsState; fyll og streker fra det punktet males med den registrerte opasiteten. Det er en tyngre seremoni enn fargesetterne, og er verdt det første gang en fremhevingsstolpe må sitte over tekst uten å skjule den

Den gjenværende vanen som er verdt å beholde, er verifisering. Generert geometri kan passere på din maskin og svikte på en kundes, vanligvis over font-erstatning i all tekst du blander inn, eller en sidestørrelsesantagelse som ikke holder. Åpne den ferdige filen på noen få zoomnivåer for å bekrefte at kantene forblir rene, og sjekk at hver form lander inne i margboksen du hadde til hensikt. Med et deterministisk fargeskjema kan den sjekken automatiseres mot en referanse-PDF snarere enn å bli bedømt med øyet

MoveTo, LineTo, Stroke, Fill, og fargekallene vist her er en del av HotPDF-komponenten for Delphi og C++Builder