Műszaki cikk

Delphi PDF-jelentések HotPDF-fel: TextOut, betűtípusok

Egy jelentés előállítása arra fut ki, hogy három dolgot helyez el az oldalon, és eléri, hogy megegyezzenek abban, hol is vannak: szöveg ismert koordinátákon, betűtípusok, amelyek a kiszolgálón ugyanúgy renderelődnek, mint az Ön asztali gépén, és a helyükre méretezett képek. Minden más, amit egy jelentéskönyvtár csinál, e három köré rendeződik. A HotPDF, a losLab Delphihez és C++Builderhez készült PDF-előállító könyvtára mindhármat közvetlen hívásként adja az oldalobjektumon, és az egyetlen valódi súrlódás az alatta lévő koordináta-rendszer, amely az ellenkező irányba fut, mint a megszokott VCL vászon. Rendezze el először ezt a tájolást, és az elrendezési munka többi része megszűnik Ön ellen dolgozni

Szövegelhelyezés és a bal alsó origó

Szinte mindenkinek fejjel lefelé sikerül az első jelentése. A cím az alsó él közelébe kerül, az alatta lévő sorok pedig a lap teteje felé kúsznak. Semmi sem hibás. A PDF felhasználói tere, amelyet az ISO 32000-1 §8.3 határoz meg, az origót a bal alsó sarokba teszi, és az Y felfelé nő, ami a GDI vászon tükörképe, ahol az Y a bal felső saroktól lefelé nő. Az az öt perc, amíg megbékél ezzel, megspórol egy elrendezést, amelyet különben újra kellene írnia, amint a számok értelmetlenné válnak

HotPDF-ábra, amely a VCL bal felső koordináta-origóját állítja szembe a PDF bal alsó origójával, ahol a TextOut a címet egy Letter lap tetejétől 50 ponttal lejjebb, a 792 mínusz 50 értéken helyezi el
A PDF felhasználói tere a VCL vászon tükörképe, így egy Letter lap tetejétől 50pt-ra lévő cím a TextOut(50, 792 - 50, 0, 'INVOICE') hívás, és ugyanez az átváltás minden jelentéskoordinátát kézenfekvővé tesz

Az oldalobjektum központi hívása a TextOut(X, Y, Angle, Text). Az X és az Y a szöveget a bal alsó saroktól pontokban helyezi el, az Angle pedig fokban forgatja el, így rajzolódik ki minden külön támogatás nélkül egy átlós DRAFT vagy COPY bélyegző. A fogás, amelytől a VCL-en edzett megérzések tovább működnek, az, hogy az Y értéket az oldalmagasság mínusz a tetejétől kívánt távolság alakjában írja fel:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-0001.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(50, 792 - 50, 0, 'INVOICE');       // 50pt a Letter lap tetejétől
    Pdf.CurrentPage.SetFont('Arial', [], 10);
    Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
    Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY');              // elforgatott bélyegző
    Pdf.AddPage;                                                // a CurrentPage most ide mutat
    Pdf.CurrentPage.SetFont('Arial', [], 10);                   // a betűtípus-állapot nem öröklődik
    Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

A listában szereplő két állapotfüggő viselkedés felelős a legtöbb olyan hibáért, amely csak a második oldalon jelentkezik. Az AddPage az éppen létrehozott oldalra irányítja át a CurrentPage hivatkozást, tehát egy korábban eltárolt oldalhivatkozás már nem oda rajzol, ahová Ön várná. A betűtípus kiválasztása szintén oldalanként érvényes, nem dokumentumonként. Ha kihagyja a SetFont hívást egy AddPage után, akkor az új oldal első TextOut hívása arra az alapértelmezésre esik vissza, amellyel az oldal indult, nem pedig arra a félkövér címbetűtípusra, amelyet három oldallal korábban állított be. A biztonságos szokás az, hogy az „új oldal kezdése” és a „szövegállapot újbóli felállítása” a jelentésciklusban egyetlen elválaszthatatlan lépésként szerepel

Betűtípusok, amelyek a kiszolgálón is léteznek, nemcsak az Ön gépén

A legtöbb betűtípus-probléma valójában álruhába bújt telepítési probléma. A fejlesztői gépén telepítve van a céges betűtípus, így a jelentés jól néz ki a képernyőn, és kikerül a felhasználókhoz. Az éles kiszolgáló a feladatot olyan szolgáltatásfiók alatt futtatja, amelyre azt a betűtípust soha nem telepítették, a renderelő csendben behelyettesít valamit, amit talál, és erről elsőként egy ügyfél kérdésétől szerez tudomást arról, hogy miért változott meg a fejléc. A kiút az, hogy ne bízzon többé az operációs rendszer betűtípus-könyvtárában, hanem töltse be a betűtípust egy olyan fájlból, amelyet a telepítője helyez a lemezre. A HotPDF Unicode-regisztrációs hívása egy útvonalat vár, és pontosan ezt teszi:

Delphi PDF betűtípus-telepítési problémát bemutató ábra: az éles kiszolgáló csendben behelyettesíti a hiányzó betűtípust, míg a RegisterUnicodeTTF a telepített fájlból tölti be a TTF-et, és beágyazza a PDF-be
Az operációs rendszer betűtípus-könyvtárára hagyatkozni akkor törik el, amikor az éles szolgáltatásfióknak nincs meg a betűtípusa, míg a TTF telepített fájlból való betöltése beágyazza a glifákat, és minden gép azonosan renderel
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));

A TextOut közvetlenül elfogad WideString értéket, ami többet számít, mint elsőre látszik. Egy ékezetes ügyfélnév, egy német utcanév, egy lengyel városnév: ezek nem szélsőséges esetek, hanem egy ügyféltábla szokásos tartalma, és ugyanazon a híváson mennek keresztül, mint az Ön által beégetett ASCII feliratok, feltéve, hogy a regisztrált betűtípus valóban tartalmazza a glifákat. A beágyazott betűtípusokkal egy verziókorlát is jár: a dokumentumnak PDF 1.5-ösnek vagy újabbnak kell lennie, tehát ha valamilyen független követelmény régebbi verzióhoz köti, akkor éppen ez fog csendben eltörni. A jobbról balra író írásrendszerek, például az arab és a héber, valódi alakítást igényelnek egyszerű glifakeresés helyett, és ennek saját folyamata van; lásd a komplex írásrendszerű szövegek HotPDF-fel történő alakításáról szóló cikkünket

Amikor egyetlen telepített betűtípus sem tudja kifejezni, amire szüksége van — gondoljon a csekkeken lévő MICR karakterekre vagy egy saját fejlesztésű szimbólumkészletre —, a Type 3 betűtípusok töltik ki a rést. Minden glifát kis tartalomfolyamként határoz meg a RegisterType3Font és az AddType3Glyph hívásokkal. Ez az API egy specializált sarka, és ritkán fog érte nyúlni, de sokkal tisztább, mint több száz apró szimbólum-bitképet szétszórni egy oldalon

Képek: a középső argumentumok szélesség és magasság, nem sarokpont

A képkezelés két lépésre válik szét, és éppen az a lényeg, hogy külön is maradjanak. Az AddImage egy TBitmap vagy TJPEGImage objektumot vár, egyszer beágyazza, és visszaad egy indexet. A PNG grafikát ez előtt bitképpé kell dekódolni. A ShowImage ezután annyiszor és oda rajzolja ki azt az indexet, ahányszor és ahová csak szeretné. A ShowImage argumentumsorrendje az az egy hely, ahol érdemes lassítani az olvasáson:

A HotPDF képfolyamatát bemutató ábra, ahol az AddImage egyszer ágyazza be a bitképet és indexet ad vissza, a ShowImage szélesség és magasság szerint helyezi el, az argumentumsorrend pedig nem sarokpontpár
Az AddImage egyszer ágyazza be a képpontokat, és minden ShowImage hívás ugyanazt az indexet használja újra, a középső argumentumok pedig szélesség és magasság, nem pedig a szemközti sarok koordinátái
var
  Png: TPngImage;
  Logo: TBitmap;
  LogoIdx: Integer;
begin
  Png := TPngImage.Create;
  Logo := TBitmap.Create;
  try
    Png.LoadFromFile('brand-logo.png');
    Logo.Assign(Png);                       // a PNG dekódolása bitképpé
    LogoIdx := Pdf.AddImage(Logo, icFlate); // veszteségmentes a síkszínű grafikához
  finally
    Logo.Free;
    Png.Free;
  end;
  // (Index, X, Y, szélesség, magasság, szög): nem (X1, Y1, X2, Y2)
  Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;

A pozíció utáni két szám egy szélesség és egy magasság. Nem a szemközti sarok koordinátái, a záró argumentum pedig fokban megadott elforgatási szög. Olvassa a szignatúrát X1/Y1/X2/Y2 keretként, és az (50, 700) pontra helyezett 120-szor 40-es logó onnan a (120, 40) pontig nyúlik, szétterülve az oldal nagy részén. A kimenet nyilvánvalóvá teszi a hibát, miközben a forráskód teljesen ésszerűnek látszik — épp ettől visz el egy egész délutánt. A KeepImageAspectRatio alapértéke True, így a rossz arányú keret nem torzítja, hanem fekete sávokkal illeszti a képet; csak akkor állítsa False értékre, ha tényleg nyújtani akar

A regisztrálás és az elhelyezés szétválasztása hosszú futásokon térül meg. Mivel az AddImage egyszer ágyazza be a képpontokat, és az adott indexszel hívott minden ShowImage ugyanarra a beágyazott objektumra mutat vissza, a fájlméretet az dönti el, hol hívja meg az AddImage metódust. Hívja meg az oldalciklusban egy 500 oldalas kivonatnál, és ugyanaz a logó 500-szor ágyazódik be. Hívja meg egyszer a ciklus előtt, tartsa meg az indexet, és a logó egyetlen példányban tárolódik. Egy kis, az eszköz útvonalával kulcsolt szótár már elég ahhoz, hogy minden különböző kép pontosan egyszer regisztrálódjon

A kodek megválasztása a másik méretkar. A fényképszerű tartalom, például a beszkennelt mellékletek, JPEG-be való: adja át az icJpeg értéket az AddImage hívásnak, és vigye le a JpegQuality értéket 85 körülire, mivel a tulajdonság 100-ról indul, és a különbség 85-nél láthatatlan egy nyomtatott oldalon. A síkszínű grafika, például a logók, diagramok és vonalas rajzok, az icFlate kodekbe való, ahol a veszteségmentes tömörítés eleve tömör, a JPEG pedig látható gyűrűzést kenne a kemény élek köré. Egy olyan kivonatfutás, amely minden oldalra teljes minőségű fényképet tesz, gigabájtokra hízhat; ugyanaz a tartalom JPEG 85-tel nagyjából a tizedére jön ki, és egyetlen olvasó sem veszi észre

Vonalak, keretek és árnyalás útvonalelemekkel

A táblázatfejléc alatti vízszintes vonalnak és az összesítés mögötti szürke doboznak nem kell képnek lennie. Rajzolja meg őket vektorként, és bármilyen nagyításnál élesek maradnak, tisztán nyomtatódnak, és szinte semmit nem adnak a fájlhoz. A HotPDF ugyanazt a modellt követi, amelyet a nyers PDF-tartalomfolyamok használnak: felépít egy útvonalat, majd meghív egy operátort, amely kifesti

// Vízszintes vonal a táblázat fejléce alatt
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;

// Árnyalt összesítődoboz: X, Y, szélesség, magasság
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;

A sorrend nem választható: állítsa be a festési állapotot, építse fel az útvonalat, majd hívja meg a Stroke vagy a Fill metódust. Az az útvonal, amelyet felépít, de sosem fest ki, semmivel nem járul hozzá az oldalhoz, és szinte mindig ez a válasz, amikor egy vonal „nem jelenik meg”. A SetRGBFillColor egyetlen TColor értéket vár, így az ismerős VCL konstansok, mint a clNavy és a clBlack, közvetlenül beilleszthetők, a Rectangle pedig ugyanazokat a szélesség-magasság argumentumokat használja, mint a képelhelyezés, nem két sarkot. Egy intelem a vékony vonalakról: nagyjából fél pont alatt bármi elegánsnak tűnhet a monitoron, majd eltűnik egy 600 dpi-s irodai nyomtatón, tehát a 0,75pt ésszerű alsó határ minden olyan vonalnál, amelynek túl kell élnie a nyomtatást

Lapozás valódi adatokon, nem mintaadatokon

Egy részletet érdemes még az elrendezés megszilárdulása előtt eltalálni: a számoszlopokat a jobb szélükön kell igazítani, és ezt úgy éri el, hogy megméri az egyes értékek renderelt szélességét, és az oszlophatártól visszafelé helyezi el őket, nem pedig úgy, hogy vezető szóközökkel tölti fel a karakterláncot. A szóközös feltöltés csak fix szélességű betűtípusban rendeződik el, pénzügyi jelentést pedig senki nem szed fix szélességű betűtípussal. Futtassa át előbb az értékeket a Delphi területi beállításokat ismerő rutinjain, például a FormatFloat függvényen, hogy az az ezreselválasztó, amelynek a szélességét méri, ugyanaz legyen, amelyet az ügyfél területi beállítása ténylegesen megjelenít

A lapozás veszélye az, hogy a demó adathalmazra írja meg, ahol tíz rövid sor elfér egy oldalon, és a ciklusnak sosem kell törnie. Az éles működés kioszt Önnek egy ügyfelet, akinek a cégneve 140 karakter hosszú, és egy 4000 tételsoros kivonatot, és most a ciklusnak minden alkalommal helyesen kell törnie. Az a minta állja ki a próbát, amelyben egyetlen Y kurzor halad lefelé, ahogy kivonja az egyes sorok magasságát, és egy ellenőrzés új oldalt kezd abban a pillanatban, amikor a kurzor átlépné az alsó margót. A lefelé itt csökkenő Y értéket jelent, és ez az egy hely, ahol a bal alsó origó ellentmond a megérzésnek. Tartsa mindezt egyetlen rutinban, amely az új oldalon a SetFont hívást is újra kiadja, és az élőfejet is újrarajzolja, és az egy oldallal elcsúszó hibák soha nem kapnak lábat. Amikor ugyanezeknek a jelentéseknek archiválási vagy akadálymentességi szabályoknak is meg kell felelniük, éppen az itt hozott döntéseket — mely betűtípusokat ágyazza be, címkézett-e a kimenet, milyen színtereket használ — kérik számon ezek a szabványok; a HotPDF PDF/A, PDF/X és PDF/UA útmutatóját érdemes elolvasni, mielőtt a sablon megszilárdul

Minden itt bemutatott hívás — a szövegpozicionálás, a betűtípus-regisztráció, a képbeágyazás és az útvonalrajzolás — a Delphihez és C++Builderhez készült HotPDF Delphi Component részeként érkezik, amelynek referenciája a teljes kimeneti API-t dokumentálja a mellette álló űrlap-, titkosítási és aláírási képességekkel együtt