Tekninen artikkeli

Delphi PDF-raportit HotPDF-komponentilla

Raportin luonti tarkoittaa kolmen asian sijoittamista samalle sivulle: tekstin tunnetuille koordinaateille, palvelimella samalla tavalla renderöityvät fontit ja sopiviksi mitoitetut kuvat. HotPDF, losLabin PDF generation library Delphille ja C++Builderille, tarjoaa ne suorina page object -kutsuina. Keskeinen ero VCL canvas -malliin on vastakkaissuuntainen koordinaatisto

Tekstin sijoittaminen ja vasemman alakulman origo

PDF user space käyttää ISO 32000-1 §8.3:n mukaan vasenta alakulmaa origona ja kasvattaa Y-arvoa ylöspäin. GDI canvas aloittaa vasemmasta yläkulmasta ja kasvattaa Y-arvoa alaspäin. Siksi ensimmäisen raportin otsikko voi päätyä alareunaan ja seuraavat rivit nousta ylöspäin ilman ohjelmavirhettä

Page object -rajapinnan pääkutsu on TextOut(X, Y, Angle, Text). X ja Y ovat pisteitä vasemmasta alakulmasta, ja Angle kiertää tekstiä asteina esimerkiksi DRAFT- tai COPY-leimaa varten. VCL-ajattelua voi käyttää laskemalla Y-arvon sivun korkeudesta vähennettynä yläreunan etäisyydellä:

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 from top of Letter
    Pdf.CurrentPage.SetFont('Arial', [], 10);
    Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
    Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY');              // rotated stamp
    Pdf.AddPage;                                                // CurrentPage now points here
    Pdf.CurrentPage.SetFont('Arial', [], 10);                   // font state does not carry over
    Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

AddPage siirtää CurrentPage-viittauksen juuri luotuun sivuun, joten aiemmin välimuistiin tallennettu page reference ei enää piirrä odotettuun paikkaan. Fonttivalinta on sivukohtainen, ei asiakirjakohtainen. Ilman uutta SetFont-kutsua AddPage-kutsun jälkeen uuden sivun ensimmäinen TextOut käyttää sivun oletusta. Käsittele uuden sivun aloitusta ja tekstitilan palauttamista yhtenä report loop -vaiheena

Fontit palvelimella, eivät vain työpöydällä

Fonttiongelmat ovat usein deployment-ongelmia. Kehityskoneen corporate font puuttuu service account -tilillä ajavalta tuotantopalvelimelta, jolloin renderer korvaa sen toisella fontilla. Lataa fontti käyttöjärjestelmän fonttihakemiston sijasta tiedostosta, jonka installer sijoittaa levylle. HotPDFin Unicode-rekisteröinti ottaa suoraan polun:

Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));

TextOut hyväksyy WideString-arvon, joten aksentoidut nimet ja saksan- tai puolankieliset osoitteet kulkevat samalla kutsulla kuin ASCII-otsikot, kun rekisteröity fontti sisältää glyphs-merkit. Upotetut fontit vaativat PDF 1.5:n tai uudemman; vanhempaan versioon lukitseva vaatimus rikkoo tämän hiljaisesti. Arabic- ja Hebrew-kaltaiset right-to-left scripts vaativat shaping-prosessin suoran glyph lookup -toiminnon sijaan; katso HotPDFin complex script text shaping -artikkeli

Kun asennettu fontti ei sisällä tarvittavia MICR-merkkejä tai omaa symbolijoukkoa, Type 3 fonts täyttävät aukon. Jokainen glyph määritetään pienenä content stream -rakenteena kutsuilla RegisterType3Font ja AddType3Glyph. Tämä erikoistapaus on selkeämpi kuin satojen pienten symbolibittikarttojen sijoittaminen sivulle

Kuvat: keskimmäiset argumentit ovat leveys ja korkeus

AddImage ottaa TBitmap- tai TJPEGImage-kuvan, upottaa sen kerran ja palauttaa indeksin. PNG-kuva puretaan bitmap-muotoon ennen kutsua. ShowImage piirtää indeksin haluttuun paikkaan niin monta kertaa kuin tarvitaan. Tarkista seuraavan ShowImage-kutsun argumenttijärjestys:

var
  Png: TPngImage;
  Logo: TBitmap;
  LogoIdx: Integer;
begin
  Png := TPngImage.Create;
  Logo := TBitmap.Create;
  try
    Png.LoadFromFile('brand-logo.png');
    Logo.Assign(Png);                       // decode PNG to a bitmap
    LogoIdx := Pdf.AddImage(Logo, icFlate); // lossless for flat-color art
  finally
    Logo.Free;
    Png.Free;
  end;
  // (Index, X, Y, Width, Height, Angle): not (X1, Y1, X2, Y2)
  Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;

Sijainnin jälkeiset arvot ovat width ja height, eivät vastakkaisen kulman koordinaatit, ja viimeinen argumentti on rotation angle asteina. X1/Y1/X2/Y2-tulkinta venyttää kohdassa (50, 700) olevan 120-by-40-logon lähes koko sivulle. KeepImageAspectRatio on oletuksena True, joten vääränmuotoinen laatikko letterbox-käsittelee kuvan venyttämisen sijaan. Aseta se False-arvoon vain tarkoituksellista venytystä varten

AddImage upottaa pikselit kerran ja kaikki saman indeksin ShowImage-kutsut viittaavat samaan objektiin. Jos AddImage kutsutaan 500 sivun loop-silmukan sisällä, sama logo upotetaan 500 kertaa. Rekisteröi se kerran ennen silmukkaa ja säilytä indeksi. Asset path -avaiminen pieni dictionary varmistaa, että kukin kuva rekisteröidään vain kerran

Photographic content ja scanned attachments kuuluvat JPEG-muotoon: välitä icJpeg AddImage-kutsulle ja laske oletusarvoltaan 100 oleva JpegQuality noin arvoon 85. Tulostuksessa eroa ei huomaa. Logos, charts ja line drawings kuuluvat häviöttömään icFlate-pakkaukseen, koska JPEG tekee koville reunoille ringing-virheitä. Täysilaatuinen kuva jokaisella sivulla voi kasvattaa raportin gigatavuihin; JPEG 85 voi pienentää sen noin kymmenesosaan

Viivat, laatikot ja varjostus path primitives -kutsuilla

Taulukko-otsikon viiva ja totals figure -taustan harmaa laatikko kannattaa piirtää vektoreina. Ne pysyvät terävinä kaikilla zoom-tasoilla, tulostuvat tarkasti ja lisäävät tiedostokokoa vain vähän. HotPDF käyttää raw PDF content streams -mallia: rakenna path ja kutsu sen maalaavaa operaattoria

// Horizontal rule under the table header
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;

// Shaded totals box: X, Y, width, height
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;

Järjestys on pakollinen: aseta paint state, rakenna path ja kutsu Stroke tai Fill. Maalaamaton path ei näy. SetRGBFillColor ottaa yhden TColor-arvon, joten VCL-vakiot kuten clNavy ja clBlack toimivat suoraan. Rectangle käyttää width- ja height-arvoja, ei kahta kulmaa. Alle noin puolen pisteen viiva voi kadota 600 dpi office printer -tulostuksessa, joten 0.75pt on järkevä minimi tulostettaville viivoille

Sivutus oikealla datalla, ei näytedatalla

Kohdista numerocolumns oikeasta reunasta mittaamalla kunkin renderöidyn arvon leveys ja sijoittamalla se column boundary -rajasta taaksepäin. Leading spaces toimii vain monospaced font -fontilla. Muotoile arvot ensin Delphin locale-aware-rutiinilla kuten FormatFloat, jotta mitattu thousands separator vastaa asiakkaalle näytettävää

Kymmenen lyhyen demorivin sivutus ei paljasta 140-merkkisen yritysnimen tai 4,000 line items -aineiston ongelmia. Käytä yhtä Y cursor -arvoa, josta vähennetään row height, ja aloita uusi sivu heti kun cursor ylittäisi bottom margin -rajan. Alaspäin tarkoittaa tässä pienenevää Y-arvoa. Pidä sivunvaihto, SetFont-tilan palautus ja running header -otsikon uudelleenpiirto samassa rutiinissa. Arkisto- ja saavutettavuusvaatimukset tarkistavat juuri tässä tehdyt font embedding-, tagging- ja color spaces -valinnat; lue HotPDF PDF/A-, PDF/X- ja PDF/UA -opas ennen templaten vakiinnuttamista

Kaikki esitetyt text positioning-, font registration-, image embedding- ja path drawing -kutsut sisältyvät Delphin ja C++Builderin HotPDF Component -tuotteeseen. Sen viite kuvaa koko output API -rajapinnan sekä rinnakkaiset forms-, encryption- ja signing-ominaisuudet