De fleste Delphi-koder som berører PDF behandler formatet som en beholder for to ting: tekstsekvenser og noen få plasserte punktgrafikkbilder. Det synet er riktig så langt det går, men det etterlater den mest kapable delen av formatet ubrukt. En PDF-side er et oppløsningsuavhengig 2D-lerret bygget på samme bildemodell som PostScript. Det kan tegne linjer, kurver, fylte områder, gradienter og repeterende mønstre, alt som vektorer som forblir skarpe ved enhver zoom og skrives ut med enhetens fulle oppløsning. Hvis du tegner en logo, et diagram, et vannmerke eller en sertifikatramme, er vektorstien nesten alltid det rette primitive elementet, og den er mindre og skarpere enn det rasteriserte bildet mange programmer velger i stedet.
Denne artikkelen går gjennom vektormodellen slik ISO 32000-1 definerer den, og viser de matchende PDFlibPas-kallene. Målet er å gjøre spesifikasjonen konkret, fordi API-et samsvarer tett med den, og å forstå det ene lærer deg det andre.
Siden er en sti-maskin
ISO 32000-1 §8.5 beskriver grafikk i to faser som aldri overlapper. Først konstruer du en sti, som er ren geometri uten synlig resultat. Deretter maler du stien i en enkelt operasjon som tegner omrisset, fyller det indre, eller gjør begge deler. Ingenting vises på siden under konstruksjonen. Stien er en abstrakt sekvens av punkter og segmenter som holdes i grafikkstatusen til en malingsoperator forbruker den, og på det tidspunktet blir den gjengitt og forkastet.
En sti består av én eller flere understier (subpaths). En understi begynner i et punkt og vokser ved å legge til segmenter: rette linjer, kubiske Bezier-kurver, og på noen plattformer hele rektangler lagt til som deres egen lukkede understi. I PDFlibPas åpner du en sti med StartPath, som setter startpunktet, og utvider den deretter med AddLineToPath og AddCurveToPath. Hvert kall flytter et implisitt gjeldende punkt fremover, slik at det neste segmentet fortsetter der det forrige sluttet. ClosePath tegner et siste rett segment tilbake til understiens start, noe som er viktig for omriss-tegning fordi det produserer en ekte linjesammenføyning i det lukkede hjørnet i stedet for to løse ender.
// A closed quadrilateral, stroked then filled
PDF.SetLineColor(0, 0, 0);
PDF.SetFillColor(0.6, 0.8, 1.0);
PDF.SetLineWidth(1.5);
PDF.StartPath(150, 100); // open the path at the first vertex
PDF.AddLineToPath(220, 140);
PDF.AddLineToPath(180, 210);
PDF.AddLineToPath(110, 170);
PDF.ClosePath; // straight segment back to (150, 100)
PDF.DrawPath(2); // 2 = fill and stroke; path is consumed
Kurver bruker AddCurveToPath, som tar to Bezier-kontrollpunkter og et endepunkt: AddCurveToPath(CtAX, CtAY, CtBX, CtBY, EndX, EndY). Kurven går fra det gjeldende punktet til (EndX, EndY), trukket mot de to kontrollpunktene underveis. Sirkulære buer er tilgjengelige via AddArcToPath(CenterX, CenterY, TotalAngle), der radien tas fra avstanden mellom det gjeldende punktet og senteret, og motoren sender ut buen som en kjede av Bezier-segmenter. Rektangler har en snarvei, AddBoxToPath(Left, Top, Width, Height), som legger til et komplett lukket rektangel som sin egen understi uten en forutgående StartPath.
To fylleregler, og hvorfor de er uenige
Når du fyller en sti som krysser seg selv eller inneholder en indre løkke, trenger gjengivelsesprogrammet en regel for å bestemme hvilke områder som er innenfor formen og hvilke som er hull. ISO 32000-1 §8.5.3.3 definerer to, og de kan male den samme geometrien forskjellig. Den ikke-null vinklingsregelen (nonzero winding rule) teller de signerte kryssingene av en stråle sendt fra et testpunkt til uendelig, og legger til én for hvert segment som krysser fra venstre til høyre og trekker fra én for hvert som krysser den andre veien; punktet er på innsiden når summen ikke er null. Partall-oddetall-regelen (even-odd rule) ignorerer retning og teller ganske enkelt kryssinger, og kaller punktet for innside når antallet er oddetall.
Det klassiske tilfellet der de spriker er en form med et hull, som en smultring eller en skive. Tegn en ytre grense og en indre grense inni den. Under even-odd-regelen skjærer den indre løkken alltid ut et hull, fordi ethvert punkt mellom de to grensene krysses én gang og ethvert punkt inne i den indre løkken krysses to ganger. Under nonzero-regelen vises hullet bare hvis den indre løkken går i motsatt retning av den ytre; går de i samme retning, forsterkes retningene i stedet for å utligne hverandre, og det indre området fylles helt. En femtagget stjerne tegnet som et enkelt selvskjærende omriss viser det samme skillet: even-odd etterlater den sentrale femkanten tom, mens nonzero fyller den.
PDFlibPas velger regelen ut fra kallet du gjør for å male, ikke ut fra et flagg. DrawPath fyller med nonzero-regelen; DrawPathEvenOdd fyller med even-odd-regelen. Begge tar samme heltallsmodus: 0 tegner bare omrisset, 1 bare fyller, og 2 fyller og tegner omriss. Even-odd-regelen er det enkleste verktøyet for utstansede hull nettopp fordi den ikke krever at du styrer understiens retning.
// Same two boxes, two fill rules, two different results.
// Nonzero winding: both boxes wind the same way, so the inner one
// does NOT cut a hole and the whole outer box fills solid.
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 100, 200, 120); // outer
PDF.AddBoxToPath(140, 130, 120, 60); // inner
PDF.DrawPath(1); // 1 = fill, nonzero winding
// Even-odd: the inner box is crossed an even number of times,
// so it punches a clean rectangular hole through the outer box.
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 300, 200, 120); // outer
PDF.AddBoxToPath(140, 330, 120, 60); // inner cut-out
PDF.DrawPathEvenOdd(1); // 1 = fill, even-odd
Aksiale gradienter varierer farge langs en linje
En flat fyllfarge er én verdi over hele regionen. A gradient varierer fargen kontinuerlig, og den enkleste typen er den aksiale, eller lineære, gradienter. ISO 32000-1 §8.7.4.5 spesifiserer den som en Type 2 aksial skyggelegging: du oppgir to punkter som definerer en akse, en startfarge ved det første punktet og en endefarge ved det andre, og gjengiveren interpolerer fargen langs denne aksen. Hvert punkt i det fylte området tar fargen til sin vinkelrette projeksjon på aksen, slik at gradienten løper i bånd vinkelrett på linjen mellom de to punktene.
I PDFlibPas er en gradient en navngitt dokumentressurs du oppretter én gang og deretter velger som gjeldende maling. NewRGBAxialShader registrerer den. Signaturen er NewRGBAxialShader(ShaderName, StartX, StartY, StartRed, StartGreen, StartBlue, EndX, EndY, EndRed, EndGreen, EndBlue, Extend): de to endepunktene for aksen, RGB-triplene i hver ende som verdier i området 0 til 1, og et Extend-flagg. Med Extend satt til 1 fortsetter endefargene som solid fyll utover aksens endepunkter, noe som vanligvis er det du ønsker for at hjørnene av et område utenfor aksen ikke skal forbli umalt; 0 etterlater dem uberørt. Når skyggeleggingen eksisterer, binder du den med SetFillShader for fylte områder, SetLineShader for tegnede omriss, eller SetTextShader for tekst. Bindingen forblir aktiv for tegningskallene som følger, slik at stien du maler neste gang tar gradienten i stedet for en flat farge.
// Define a vertical gradient once: blue at the bottom to white at the top.
PDF.NewRGBAxialShader('panelGrad',
0, 100, 0.10, 0.25, 0.55, // start point and start RGB
0, 260, 1.00, 1.00, 1.00, // end point and end RGB
1); // 1 = extend ends as solid color
// Select the gradient as the fill, then paint a rectangle with it.
PDF.SetFillShader('panelGrad');
PDF.AddBoxToPath(80, 100, 300, 160);
PDF.DrawPath(1); // 1 = fill, now filled by the shader
Aksen her er vertikal, fra y=100 til y=260 ved en fast x, så fargebåndene løper horisontalt og rektangelet toner ut fra blått ved bunnen til hvitt på toppen. Fordi skyggeleggingen er identifisert med navn, kan én definisjon fylle et hvilket som helst antall former på siden, og å bytte tilbake to en flat farge er bare et nytt SetFillColor-kall før neste sti.
Flismønstre gjentar en celle
Der en gradient varierer en enkelt farge jevnt, gjentar et flismønster (tiling pattern) et lite kunstverk over et område. ISO 32000-1 §8.7.3.1 definerer et flismønster som en mønstercelle, et uavhengig innholdsstykke, som gjengiveren replikerer på et fast rutenett for å flislegge området som males. Dette er hvordan du bygger skravering for ingeniørfyll, et repeterende merkevaremotiv bak et topphode, eller en strukturert bakgrunn som forblir vektorskarp og ikke veier nesten noe uansett hvor stort området er, fordi cellen lagres én gang og refereres overalt.
PDFlibPas bygger mønstercellen fra fanget sideinnhold. Du fanger en side eller et område med CapturePage, gjør fangsten om til et navngitt mønster med NewTilingPatternFromCapturedPage(PatternName, CaptureID), og velger deretter dette mønsteret som gjeldende fyll med SetFillTilingPattern(PatternName). Fra det tidspunktet blir enhver sti du fyller, malt med den repeterende cellen i stedet for en flat farge, akkurat slik et skyggeleggingsfyll fungerer, men med en flislagt celle som malingskilde. Sekvensen er mer kompleks enn et enkelt kall, så hvis fangsttrinnet er ukjent, kan du behandle mønsteret som en to-trinns operasjon: opprett den fangede cellen først, og bind den deretter som et fyll med navn før du tegner området du vil flislegge.
Sette sammen de primitive elementene
Bitene settes sammen direkte. En fylt Bezier-klump er en sti av kurver malt med DrawPath. Det samme omrisset malt med DrawPathEvenOdd etter å ha lagt til en indre løkke viser et hull som nonzero-fyllet ville ha lukket. Et gradientfylt rektangel er en boks bundet til en skyggelegging. Eksempelet nedenfor tegner alle tre i rekkefølge slik at forskjellen mellom de to fyllereglene er synlig på én side, og legger deretter et gradientpanel under dem.
// 1. A filled Bezier shape (nonzero winding).
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 480);
PDF.AddCurveToPath(160, 560, 240, 560, 280, 480); // top lobe
PDF.AddCurveToPath(240, 420, 160, 420, 120, 480); // bottom lobe
PDF.ClosePath;
PDF.DrawPath(1); // 1 = fill
// 2. The same outline, plus an inner loop, filled even-odd to show a hole.
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 300);
PDF.AddCurveToPath(160, 380, 240, 380, 280, 300);
PDF.AddCurveToPath(240, 240, 160, 240, 120, 300);
PDF.ClosePath;
PDF.MovePath(180, 300); // new subpath: the hole
PDF.AddArcToPath(200, 300, 360); // a full circle
PDF.ClosePath;
PDF.DrawPathEvenOdd(1); // hole is punched out
// 3. A rectangle filled with an axial gradient.
PDF.NewRGBAxialShader('footerGrad',
60, 100, 0.95, 0.55, 0.10,
60, 200, 0.20, 0.10, 0.40,
1);
PDF.SetFillShader('footerGrad');
PDF.AddBoxToPath(60, 100, 340, 100);
PDF.DrawPath(1);
To detaljer er verdt å merke seg. Malingskallet bestemmer fyllregelen, så valget mellom DrawPath og DrawPathEvenOdd is valget mellom nonzero og even-odd, og for former med hull sparer even-odd-regelen deg for å måtte tenke på understiens retning. Og grafikkstatusen samples i det øyeblikket du maler: sett farger, linjebredde og skyggeleggingsbinding før malingskallet, fordi det er den statusen motoren leser. Konstruer først, konfigurer statusen, mal til slutt, så oppfører vektormodellen seg forutsigbart hver gang.
From here, the natural next steps are reading vectors and text back out of an existing document, covered in our article on text, image, and font extraction, and rendering the same drawing model to a Windows device context for on-screen preview and printing, covered in the print and preview walkthrough. The path, shader, and pattern calls described here ship as part of the Delphi PDF Library alongside the text, image, form, and signing APIs covered elsewhere on this blog.