HotPDF ritar vektorgrafik genom att bygga en väg (path) på den aktuella sidan och sedan be om att den ska målas (painted). Det finns inget bitmap-steg däremellan. En linje du ritar med MoveTo och LineTo hamnar som PDF-vägoperatörer (path operators) i innehållsströmmen (content stream), så den förblir en sann vektor: skarp (crisp) vid 50% zoom, skarp vid 1600%, och en bråkdel av storleken en rastrerad version skulle kosta. För diagram, tabellinjer (table rules), diagramaxlar och formulärdekorationer är det exakt vad du vill ha, och API:et bakom det är tillräckligt litet för att lära sig i en sittning
Hela ritytan (drawing surface) lever på THotPDF.CurrentPage. Mellan BeginDoc och EndDoc ställer du in färg och linjebredd på det sidobjektet, lägger ner geometri och anropar en målaroperatör för att bekräfta det. De fyra primitiver du kommer att använda mest är MoveTo och LineTo för godtyckliga vägar (arbitrary paths), Rectangle för lådor (boxes), Circle för cirklar, och de två målaroperatörerna Stroke och Fill
Koordinatsystemet är nedre vänster (bottom-left)
Detta är det enda som sätter krokben för alla som kommer från VCL. Den TCanvas du målar kontroller med lägger origon i det övre vänstra hörnet med Y-axeln växande nedåt. PDF gör det motsatta. HotPDF mäter från det nedre vänstra hörnet av sidan i punkter (1/72 tum), med Y som ökar när du går uppåt. En punkt på Y := 720 sitter nära toppen av en US Letter-sida, som är 792 punkter hög, och Y := 50 sitter nära botten. Om din första ritning (drawing) blir vertikalt speglad är det här anledningen: kod som porterats från skärmgrafik antar fel riktning och löper utanför (runs off) den nedre kanten
Samma konvention styr TextOut, så text och former delar en mental modell när du väl har internaliserat den. Planera en layout genom att bestämma var botten av varje element sitter, inte toppen, och resten följer med
Vägar (Paths): MoveTo, LineTo, Stroke
En dragen (stroked) väg är en penna som lyfts, placeras och dras. MoveTo lyfter pennan och sätter startpunkten utan att markera något. Varje LineTo förlänger den aktuella vägen till en ny punkt. Inget dyker upp på sidan förrän du anropar Stroke, som ritar den ackumulerade vägen med den aktuella dragningsfärgen (stroke color) och linjebredden (line width), rensar sedan vägen så att nästa MoveTo börjar på nytt
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;
Två detaljer sparar verklig felsökningstid (debugging time). Linjebredd (Line width) är ett tillstånd (state), inte ett argument: SetLineWidth ställer in det en gång och varje efterföljande Stroke använder det värdet tills du ändrar det igen, vilket är anledningen till att polylinjen (polyline) ovan är tjockare än regeln (rule). Och vägen återställs efter varje Stroke, så en glömd Stroke innebär att geometrin du noggrant la ut aldrig renderas (renders) alls. Om en form saknas i utdatan är målningsanropet (painting call) den första platsen att leta på
Koordinaterna är punkter, och punkter är fraktionella. MoveTo och LineTo accepterar Single-värden, så en hårlinje på 0.5 punkter eller en position på 72.25 är giltig och meningsfull, och avrundas inte bort till närmaste hela enhet. Den precisionen har betydelse i två motsatta riktningar. En linjebredd under cirka 0.5 kan renderas som en enhetsberoende tunnast möjliga linje som försvinner på skärmen och dyker upp igen när den skrivs ut, så en synlig regel vill ha en bredd som du ställer in medvetet istället för standardvärdet. I andra änden, att fästa (snapping) tabellinjer och rutnätslinjer (gridlines) vid hel-punkt-koordinater förhindrar ett tätt rutnät från att se något ojämnt ut där intilliggande linjer avrundas olika. Bestäm rutnätsavståndet i punkter från början och resten av layouten ärver (inherits) det
Fyllda former (Filled shapes) och färg
Stängda primitiver kan fyllas istället för kontureras (outlined). Rectangle tar en position och en storlek, Circle tar ett centrum och en radie (radius), och var och en bekräftas med Fill, som målar det inre i den aktuella fyllningsfärgen (fill color), eller med Stroke för enbart en kontur. Fyllningsfärg och dragningsfärg (stroke color) är separata tillståndsdelar (pieces of state), som ställs in med SetRGBFillColor och SetRGBStrokeColor, vilka båda tar en enda TColor. Det betyder att du kan återanvända Delphis färgkonstanter och RGB-hjälparen (helper) direkt
// 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;
Titta noga på argumentsstrukturen för Rectangle. Det är position-plus-storlek, X, Y, Width, Height, inte två motsatta hörn. TCanvas.Rectangle som Delphi-utvecklare känner till tar (Left, Top, Right, Bottom), så muskelminnet (muscle memory) kommer att ge HotPDF ett andra hörn där den förväntar sig en bredd och en höjd, och rutan kommer ut i fel storlek. Paret (X, Y) är det nedre vänstra hörnet, konsekvent med sidans origo. För en cirkel är (X, Y) centrumet (center) och det tredje argumentet är radien i punkter
Ett färgval där det ursprungliga exemplet hade fel
En äldre version av det här exemplet gav (seeded) färger med Random($FFFFFF) på varje form. Det ser livligt ut, och det är fel instinkt för genererade dokument. En PDF du bygger från kod är vanligtvis något du också vill testa, och slumpmässiga fyllningsfärger (random fill colors) gör utdatan omöjlig att jämföra mellan körningar: en byte-för-byte diff mot en fil som man vet fungerar misslyckas varje gång, utan någon verklig anledning. Välj explicita färger. När du vill ha variation (variety) i en serie av former, driv den från din data eller en fast palett-array (palette array), så att samma inmatning (input) alltid producerar samma fil. Deterministism (Determinism) är värt mer än nyhet när artefakten rör sig genom en release-pipeline
Att sätta ihop primitiverna: en anropsruta (callout box)
Varje primitiv är enkel i sig själv; utdelningen (payoff) visar sig när en handfull av dem kombineras till något som en rapport faktiskt behöver. En anropsruta (callout), den kommenterade rutan som pekar på en figur och förklarar den, använder allt som har täckts hittills: en fylld rektangel med en kant, en ritad peklinje (stroked pointer line), en punkt (dot) för att förankra (anchor) pekaren, och text som läggs ut inuti rutan med samma nedre vänster-koordinater som formerna använder. FillAndStroke förtjänar sin plats här, och målar inre och kontur (outline) av en väg (path) i en enda körning (commit) istället för att bygga rektangeln två gånger
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;
Notera hur lite tillståndshantering (state management) sammansättningen behöver. Fyllningsfärg, dragningsfärg och linjebredd ställs var och en in omedelbart före formen som använder dem, så varje block av ritningen läses som en fristående enhet (self-contained unit) och kan omordnas eller extraheras till en hjälpare (helper) utan att dra med sig dolda tillstånd (hidden state). Slå in detta i en procedur som tar ankarpunkten och strängarna, och du har en återanvändbar diagramanteckning för priset av fyrtio rader
Var vektorritning lönar sig (pays off), och var det inte gör det
Sträck dig efter dessa väg- och form-anrop (path and shape calls) när geometrin är genererad: diagrammets rutnätslinjer och staplar (gridlines and bars), en fakturatabells linjer, anropsrutor (callout boxes) på ett diagram, ett logotypmärke (logo mark) uttryckt som en handfull vägar. Allt skalar utan oskärpa (blur) och lägger nästan ingenting till filstorleken, eftersom en rektangel är några siffror istället för tusentals pixlar (pixels). Baksidan är också ärlig. Om vad du faktiskt har är ett fotografi eller en skärmdump, rita det som en bild med AddImage och ShowImage istället; att spåra (tracing) en bitmap med vektoranrop ger dig ingenting. De raka segmenten, rektanglarna och cirklarna ovan bär den stora majoriteten av verkligt rapporteringsarbete, och de tre förfiningarna (refinements) som utvecklare frågar om härnäst, kurvor, streckmönster (dash patterns) och transparens (transparency), sitter på samma sidobjekt
Kurvor, streckmönster (dashes) och transparens (transparency) i korthet
Fritidskurvor (Freeform curves) utökar samma vägmaskineri som du redan har. CurveToC(X1, Y1, X2, Y2, X3, Y3) lägger till ett kubiskt Bezier-segment från den aktuella punkten till (X3, Y3), böjt mot de två kontrollpunkterna, och kortvarianterna CurveToV och CurveToY täcker de fall där en kontrollpunkt sammanfaller med en slutpunkt. En väg (path) kan blanda LineTo- och CurveToC-segment fritt innan en enda Stroke eller Fill bekräftar den, vilket är hur rundade hörn och jämna diagramlinjer (smooth chart lines) byggs
Streckmönster (Dashed strokes) är tillstånd, exakt som linjebredd. SetDash([3, 3], 0) växlar varje efterföljande stroke till ett tresteg-på, tresteg-av-mönster (three-point-on, three-point-off pattern), där arrayen (array) stavar ut längderna för på/av (on/off run lengths) i punkter och det andra argumentet fastställer var cykeln startar; NoDash återställer pennan till en solid linje. Ställ in den, rita rutnätslinjerna (stroke the gridlines) som vill ha den, och återställ den innan nästa solida linje (solid rule), annars smittar streckmönstret (dash) tyst ner allt som följer
Transparens (Transparency) körs genom ett namngivet grafiktillstånd (graphics state) snarare än ett färgargument, eftersom alfa (alpha) i PDF är en egenskap hos grafiktillståndsordlistan (graphics state dictionary). Registrera en på dokumentet med RegisterExtGState, genom att skicka in en fyllnings-alfa (fill alpha) och dragnings-alfa (stroke alpha) mellan 0 och 1, och tillämpa sedan namnet den returnerar med CurrentPage.SetGraphicsState; fyllningar och dragningar (fills and strokes) från den punkten målar med den registrerade opaciteten (opacity). Det är en tyngre ceremoni än färginställarna, och värt det första gången ett markeringsfält (highlight bar) måste sitta över text utan att dölja den
Den återstående vanan värd att behålla är verifiering (verification). Genererad geometri (Generated geometry) kan fungera på din maskin och misslyckas hos en kund, vanligtvis på grund av typsnittsbyte (font substitution) i någon text du blandar in eller ett sidstorleksantagande som inte håller. Öppna den färdiga filen i några zoomnivåer för att bekräfta att kanterna förblir rena, och kontrollera att varje form (shape) hamnar inuti den marginalruta (margin box) du avsåg. Med ett deterministiskt färgschema kan den kontrollen automatiseras mot en referens-PDF istället för att bedömas med blotta ögat (eyeballed)
Anropen MoveTo, LineTo, Stroke, Fill, och färg som visas här är en del av HotPDF-komponenten för Delphi och C++Builder