Műszaki cikk

HotPDF vászon (canvas) rajzolás Delphi-ben: vektoros útvonalak és szín

A HotPDF úgy rajzol vektorgrafikákat (vector graphics), hogy felépít egy útvonalat (path) az aktuális oldalon, majd kéri annak kifestését (painted). Nincs közbeeső bittérképes (bitmap) lépés. Egy olyan vonal (line), amelyet a MoveTo és LineTo segítségével rajzol, PDF útvonal-operátorként (PDF path operators) végzi a tartalomfolyamban (content stream), így valódi vektor (true vector) marad: éles (crisp) 50%-os nagyításnál (zoom), éles 1600%-nál, és annak a méretnek a töredékébe (fraction) kerül, amibe egy raszterizált (rasterized) verzió kerülne. Diagramokhoz (diagrams), táblázatvonalakhoz (table rules), diagramtengelyekhez (chart axes) és űrlapdíszítésekhez (form decorations) pontosan ez az, amit Ön szeretne, és a mögötte lévő API elég kicsi ahhoz, hogy egy ültő helyében (in one sitting) megtanulja

A teljes rajzolási felület (drawing surface) a THotPDF.CurrentPage-en él. A BeginDoc és az EndDoc között Ön beállítja a színt és a vonalvastagságot (line width) ezen az oldalobjektumon (page object), lefekteti a geometriát, és meghív egy festési operátort (painting operator) annak rögzítésére (commit). A négy primitív (primitives), amelyet a legtöbbet fog használni, a MoveTo és a LineTo a tetszőleges (arbitrary) útvonalakhoz (paths), a Rectangle a dobozokhoz (boxes), a Circle a korongokhoz (disks), valamint a két festési operátor, a Stroke és a Fill

A koordináta-rendszer bal alsó

Ez az az egyetlen dolog, ami mindenkit megzavar (trips up), aki a VCL felől érkezik. A vezérlőket (controls) festő TCanvas az origót (origin) a bal felső sarokba teszi, az Y pedig lefelé növekszik. A PDF pont az ellenkezőjét teszi. A HotPDF az oldal bal alsó sarkától (bottom-left corner) mér pontokban (points) (1/72 hüvelyk), az Y pedig felfelé haladva növekszik. Egy Y := 720 pont a US Letter oldal teteje közelében (near the top) található, amely 792 pont magas, a Y := 50 pedig az alja közelében helyezkedik el. Ha az első rajza függőlegesen tükrözve (mirrored vertically) jelenik meg, ez az oka: a képernyőgrafikáról (screen graphics) portolt kód rossz irányt feltételez, és az alsó szélen (bottom edge) túlra fut (runs off)

Ugyanez a konvenció szabályozza a TextOut-ot, így a szöveg és az alakzatok (shapes) egyetlen mentális modellt osztanak meg (share one mental model), amint belsővé teszi azt. Tervezze meg az elrendezést (layout) úgy, hogy eldönti, hol helyezkedik el az egyes elemek alja (bottom), nem pedig a teteje (top), és a többi már következik ebből

Útvonalak: MoveTo, LineTo, Stroke

Egy körvonalazott útvonal (stroked path) egy felemelt (lifted), elhelyezett (placed) és húzott (dragged) toll (pen). A MoveTo felemeli a tollat, és beállítja a kezdőpontot anélkül, hogy bármit is megjelölne. Minden LineTo kiterjeszti az aktuális útvonalat egy új pontra. Semmi sem jelenik meg az oldalon (page), amíg meg nem hívja a Stroke-ot, amely az aktuális körvonalszínnel (stroke color) és vonalvastagsággal (line width) megrajzolja a felhalmozott (accumulated) útvonalat, majd törli az útvonalat, hogy a következő MoveTo tisztán kezdődjön

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;

Két részlet valós hibakeresési (debugging) időt takarít meg. A vonalvastagság (Line width) állapot (state), nem egy argumentum: a SetLineWidth egyszer beállítja, és minden további (subsequent) Stroke ezt az értéket használja, amíg meg nem változtatja, ezért a fenti vonallánc (polyline) vastagabb, mint az egyenes (rule). Az útvonal pedig minden Stroke után visszaáll (resets), így egy elfelejtett Stroke azt jelenti, hogy az a geometria, amit olyan gondosan elrendezett, soha nem is renderelődik. Ha egy alakzat hiányzik a kimenetből, a festési hívás (painting call) az első hely, amit meg kell vizsgálni (look)

A koordináták pontok (points), a pontok pedig tört számok (fractional). A MoveTo és a LineTo elfogad Single értékeket, így egy 0.5 pontos (points) hajszálvékony vonal (hairline) vagy egy 72.25-ös pozíció szabályos (legal) és értelmes (meaningful), nincs legközelebbi egész (whole) egységre kerekítve (rounded away). Ez a precizitás (precision) két ellentétes irányban is számít. A kb. 0.5 alatti vonalvastagság eszközfüggő, lehető legvékonyabb (thinnest-possible) vonalként renderelődhet, amely eltűnik a képernyőn, és nyomtatáskor (printed) újra megjelenik (reappears), így egy látható egyenes (visible rule) olyan vastagságot kíván, amelyet Ön szándékosan állít be, nem pedig az alapértelmezést. A másik oldalon, a táblázatvonalak (table rules) és rácsvonalak (gridlines) egész pont (whole-point) koordinátákra illesztése (snapping) megakadályozza, hogy egy sűrű rács (dense grid) enyhén egyenetlennek (slightly uneven) tűnjön ott, ahol a szomszédos vonalak (adjacent lines) másként kerekítenek (round differently). Döntse el előre a rácstérközt (grid spacing) pontokban, és az elrendezés többi része (rest of the layout) örökli azt

Kitöltött alakzatok és szín

A zárt (Closed) primitívek körvonalazás (outlined) helyett kitölthetők (filled). A Rectangle egy pozíciót és egy méretet vesz fel, a Circle egy középpontot (center) és egy sugarat (radius) vesz fel, és bármelyiket rögzíthetjük a Fill-lel, amely a belső teret az aktuális kitöltési színnel (fill color) festi be, vagy a Stroke-kal, ha csak körvonalat (outline only) szeretnénk. A kitöltési szín (Fill color) és a körvonalszín (stroke color) különálló állapotok (pieces of state), a SetRGBFillColor és a SetRGBStrokeColor segítségével állíthatók be, melyek mindegyike egyetlen TColor-t fogad. Ez azt jelenti, hogy közvetlenül (directly) újra felhasználhatja a Delphi színkonstansait (color constants) és az RGB segédet (helper)

// 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;

Figyelje a Rectangle argumentumának formáját (argument shape). Ez pozíció-plusz-méret, X, Y, Width, Height (X, Y, Szélesség, Magasság), nem pedig két ellentétes sarok (opposite corners). A Delphi fejlesztők által ismert TCanvas.Rectangle a (Left, Top, Right, Bottom) paramétereket veszi fel, így az izommemória (muscle memory) egy második sarkot fog átadni a HotPDF-nek ott, ahol az egy szélességet és egy magasságot vár, és a doboz rossz méretű (wrong size) lesz. Az (X, Y) pár az alsó bal sarok (lower-left corner), összhangban az oldal origójával. Egy kör (circle) esetében az (X, Y) a középpont (center), a harmadik argumentum pedig a sugár (radius) pontokban (points)

Egy színválasztás, amit az eredeti minta elrontott

Ennek a példának egy régebbi verziója (older version) minden alakzatnál Random($FFFFFF) segítségével állította be (seeded) a színeket. Élénknek (lively) tűnik, és ez egy rossz ösztön a generált dokumentumoknál. Egy kódból felépített PDF általában olyan dolog, amit tesztelni is akar, és a véletlenszerű kitöltési színek (random fill colors) lehetetlenné teszik a kimenet összehasonlítását futásról futásra (run to run): egy bájtról bájtra (byte-for-byte) diffelés egy ismerten jó (known-good) fájlhoz képest minden alkalommal megbukik, minden valós ok nélkül. Válasszon explicit színeket. Amikor változatosságra (variety) vágyik alakzatok (shapes) egy sorozatán (series) keresztül, irányítsa (drive it) az adataiból vagy egy rögzített palettatömbből (fixed palette array), így ugyanaz a bemenet (input) mindig ugyanazt a fájlt fogja eredményezni. A determinizmus többet ér az újdonságnál (novelty), amikor az artefaktum (artifact) egy kiadási folyamaton (release pipeline) halad keresztül

Ahol a vektoros rajzolás kifizetődik, és ahol nem

Nyúljon ezekhez az útvonal- (path) és alakzathívásokhoz (shape calls), amikor a geometria generált (generated): diagram rácsvonalak (chart gridlines) és oszlopok (bars), egy számlatáblázat (invoice table) vonalai (ruled lines), figyelemfelhívó dobozok (callout boxes) egy diagramon (diagram), egy logó (logo mark) néhány útvonalként (handful of paths) kifejezve. Mindez elmosódás (blur) nélkül skálázódik (scales), és szinte semmit sem ad hozzá a fájlmérethez, mert egy téglalap (rectangle) néhány szám, nem pedig pixelek ezrei. A másik oldal is őszinte. Ha Önnek valójában egy fényképe (photograph) vagy egy képernyőképe (screenshot) van, rajzolja azt képként (image) az AddImage és a ShowImage segítségével; egy bittérkép (bitmap) lekövetése (tracing) vektoros hívásokkal (vector calls) semmit sem nyer (buys you nothing). Összetett görbék (Complex curves) szintén kívül esnek a hatókörön (out of scope). A fenti primitívek (primitives) egyenes szakaszok (straight segments), téglalapok (rectangles) és körök (circles), amelyek a valódi jelentéskészítési (reporting) munka túlnyomó részét (large majority) elviszik; minden, ami szabad formájú Bezier görbéket (freeform Bezier curves) igényel, az API egy külön része (separate part)

A megőrzendő (keeping) további szokás (habit) az ellenőrzés (verification). A generált geometria (Generated geometry) átmehet az Ön gépén, de elbukhat egy ügyfélén (customer's), általában az Ön által bekevert szövegek (text you mix in) betűtípus-helyettesítésén (font substitution) vagy egy olyan oldalméret-feltételezésen (page-size assumption), amely nem állja meg a helyét (does not hold). Nyissa meg a befejezett fájlt néhány nagyítási szinten (zoom levels), hogy meggyőződjön (confirm) arról, hogy az élek tiszták (clean) maradnak, és ellenőrizze (check), hogy minden alakzat az Ön által tervezett margódobozon (margin box) belül landol-e (lands). Egy determinisztikus színsémával (deterministic color scheme) ez az ellenőrzés egy referencia PDF-fel szemben automatizálható (automated) a szemrevételezés (eyeballed) helyett

A MoveTo, LineTo, Stroke, Fill, és a szín hívások, amik itt láthatók, a HotPDF Component részei Delphi és C++Builder esetében

A frissített rajzolási példa a bal alsó koordináta-rendszert, a MoveTo/LineTo/Stroke útvonalakat, a kitöltött alakzatokat, a javított színt, a görbéket, a szaggatást és az átlátszóságot kapcsolja össze