Een rapport genereren komt neer op drie dingen op een pagina plaatsen en ze op één lijn krijgen over waar ze staan: tekst op bekende coördinaten, fonts die op de server hetzelfde renderen als op uw desktop, en afbeeldingen passend gemaakt. Alles wat een rapportbibliotheek verder doet, is om die drie heen ingericht. HotPDF, de PDF-generatiebibliotheek van losLab voor Delphi en C++Builder, geeft u elk daarvan als een directe aanroep op het pagina-object, en de enige echte frictie is het onderliggende coördinaatsysteem, dat de tegenovergestelde kant op werkt ten opzichte van het VCL-canvas dat u gewend bent. Zet die oriëntatie eerst op een rij en de rest van het lay-outwerk werkt niet meer tegen u
Tekstplaatsing en de oorsprong linksonder
Bijna ieders eerste rapport komt ondersteboven uit. De titel belandt vlak bij de onderrand en elke regel eronder klimt naar boven. Er werkt niets fout. De PDF-gebruikersruimte, gedefinieerd in ISO 32000-1 §8.3, plaatst de oorsprong in de hoek linksonder met Y opwaarts groeiend, wat het spiegelbeeld is van het GDI-canvas waar Y van linksboven naar beneden groeit. Vijf minuten om dat te accepteren bespaart een lay-out die u anders opnieuw schrijft zodra de cijfers niet meer kloppen
De centrale aanroep van het pagina-object is TextOut(X, Y, Angle, Text). X en Y plaatsen de tekst in punten vanaf de hoek linksonder, en Angle roteert deze in graden, wat is hoe een diagonale DRAFT- of COPY-stempel zonder enige speciale ondersteuning wordt getekend. De truc die VCL-getrainde intuitie laat blijven werken, is om Y uit te drukken als de paginahoogte minus de afstand die u vanaf de bovenkant wilt:
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 vanaf de bovenkant van Letter
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY'); // geroteerde stempel
Pdf.AddPage; // CurrentPage wijst nu hierheen
Pdf.CurrentPage.SetFont('Arial', [], 10); // fontstatus wordt niet overgenomen
Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
De twee stateful gedragingen in die listing zijn verantwoordelijk voor de meeste bugs die pas op pagina twee opduiken. AddPage wijst CurrentPage opnieuw naar de pagina die het zojuist heeft gemaakt, dus een paginaverwijzing die u eerder hebt vastgelegd, tekent niet meer waar u verwacht. Fontkeuze is ook per pagina in plaats van per document. Als u de SetFont na een AddPage overslaat, valt de eerste TextOut op de verse pagina terug op de standaardwaarde waarmee de pagina begon, niet op het vetgedrukte koptekstfont dat u drie pagina's geleden instelde. De veilige gewoonte is om 'een nieuwe pagina beginnen' en 'de tekststatus opnieuw instellen' te behandelen als één onafscheidelijke stap in de rapportlus
Fonts die op de server bestaan, niet alleen op uw desktop
De meeste fontproblemen zijn in werkelijkheid implementatieproblemen in vermomming. Uw ontwikkelmachine heeft het bedrijfsfont geïnstalleerd, dus het rapport ziet er op uw scherm goed uit en wordt verzonden. De productiehost draait de taak onder een serviceaccount dat dat font nooit heeft gehad, de renderer substitueert stilletjes iets wat het kan vinden, en het eerste wat iemand ervan hoort is een klant die vraagt waarom het briefhoofd is veranderd. De uitweg is om de OS-fontmap niet meer te vertrouwen en het font te laden uit een bestand dat uw installatieprogramma op schijf zet. HotPDF's Unicode-registratieaanroep neemt een pad en doet precies dat:
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 accepteert direct een WideString, wat meer uitmaakt dan het in eerste instantie lijkt. Een klantnaam met een accent, een Duitse straat, een Poolse stad: dit zijn geen randgevallen, het is de normale inhoud van een klanttabel, en ze gaan door dezelfde aanroep als de ASCII-labels die u hardcodeert, zolang het geregistreerde font de glyphs daadwerkelijk bevat. Eén versiebeperking komt mee met ingesloten fonts: het document moet PDF 1.5 of nieuwer zijn, dus als een andere eis u aan een oudere versie bindt, is dat het ding dat stilletjes breekt. Rechts-naar-links-scripts zoals Arabisch en Hebreeuws hebben echte shaping nodig in plaats van een rechte glyph-opzoeking, en dat heeft een eigen pijplijn; zie ons artikel over complexe-script-tekstshaping met HotPDF
Als geen geïnstalleerd font kan uitdrukken wat u nodig hebt, denk aan MICR-tekens op een cheque of een eigen symboolset, dan vullen Type 3-fonts de kloof. U definieert elke glyph als een kleine contentstream via RegisterType3Font en AddType3Glyph. Het is een gespecialiseerde hoek van de API en u zult er zelden naar grijpen, maar het is veel schoner dan honderden kleine symbool-bitmaps over een pagina verspreiden
Afbeeldingen: de middelste argumenten zijn een breedte en hoogte, geen hoek
Afbeeldingsverwerking splitst zich in twee stappen, en ze gescheiden houden is precies het punt. AddImage neemt een TBitmap of TJPEGImage, sluit deze eenmaal in, en geeft een index terug. PNG-illustratie moet worden gedecodeerd naar een bitmap voordat het daar aankomt. ShowImage tekent die index vervolgens waar en zo vaak u maar wilt. De argumentvolgorde op ShowImage is de enige plek waar het de moeite loont om te vertragen en te lezen:
var
Png: TPngImage;
Logo: TBitmap;
LogoIdx: Integer;
begin
Png := TPngImage.Create;
Logo := TBitmap.Create;
try
Png.LoadFromFile('brand-logo.png');
Logo.Assign(Png); // decodeer PNG naar een bitmap
LogoIdx := Pdf.AddImage(Logo, icFlate); // lossless voor illustraties met vlakke kleuren
finally
Logo.Free;
Png.Free;
end;
// (Index, X, Y, Width, Height, Angle): niet (X1, Y1, X2, Y2)
Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
De twee getallen na de positie zijn een breedte en een hoogte. Het zijn niet de coördinaten van de tegenovergestelde hoek, en het afsluitende argument is een rotatiehoek in graden. Lees de signatuur als een X1/Y1/X2/Y2-vak en een 120-bij-40-logo geplaatst op (50, 700) rekt zich van daaruit uit naar (120, 40), en verspreidt zich over het grootste deel van de pagina. De uitvoer maakt de fout duidelijk terwijl de broncode er volkomen redelijk uitziet, wat het is wat het een middag laat verspillen. KeepImageAspectRatio staat standaard op True, dus een vak met de verkeerde verhoudingen letterboxt de afbeelding in plaats van deze te vervormen; zet het alleen op False als u werkelijk wilt uitrekken
De scheiding tussen registreren en plaatsen betaalt zich uit bij lange runs. Omdat AddImage de pixels eenmaal insluit en elke ShowImage met die index terugwijst naar hetzelfde ingesloten object, bepaalt waar u AddImage aanroept de bestandsgrootte. Roep het aan binnen de paginalus voor een afschrift van 500 pagina's en hetzelfde logo wordt 500 keer ingesloten. Roep het eenmaal aan vóór de lus, bewaar de index, en het logo wordt eenmaal opgeslagen. Een kleine dictionary geïndexeerd op het asset-pad is genoeg om ervoor te zorgen dat elke afzonderlijke afbeelding precies eenmaal wordt geregistreerd
De keuze van de codec is de andere hendel voor de grootte. Fotografische inhoud, gescande bijlagen en dergelijke, hoort in JPEG: geef icJpeg door aan AddImage en zet JpegQuality op ongeveer 85, aangezien de eigenschap op 100 begint en het verschil op 85 onzichtbaar is op een afgedrukte pagina. Illustratie met vlakke kleuren zoals logo's, grafieken en lijntekeningen hoort in icFlate, waar lossless compressie al compact is en JPEG zichtbare ringing zou uitsmeren langs de harde randen. Een afschriftrun die één foto in volle kwaliteit op elke pagina duwt, kan uitgroeien tot gigabytes; dezelfde inhoud bij JPEG 85 landt op ongeveer een tiende van de grootte, en geen lezer ziet het verschil
Lijnen, vakken en arcering met padprimitieven
De horizontale lijn onder een tabelkop en het grijze vak achter een totaalfiguur hoeven geen afbeeldingen te zijn. Teken ze als vectoren en ze blijven scherp bij elke zoom, printen helder, en voegen bijna niets toe aan het bestand. HotPDF volgt hetzelfde model dat ruwe PDF-contentstreams gebruiken: bouw een pad, roep vervolgens een operator aan die het verft
// Horizontale lijn onder de tabelkop
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;
// Gearceerd totaalvak: X, Y, breedte, hoogte
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;
De volgorde is niet optioneel: stel de verfstatus in, construeer het pad, en roep dan Stroke of Fill aan. Een pad dat u bouwt maar nooit verft, draagt niets bij aan de pagina, wat vrijwel altijd het antwoord is als een lijn 'niet verschijnt'. SetRGBFillColor neemt één TColor, dus de bekende VCL-constanten zoals clNavy en clBlack passen er direct in, en Rectangle gebruikt dezelfde breedte-en-hoogte-argumenten als afbeeldingsplaatsing in plaats van twee hoeken. Eén waarschuwing over dunne lijnen: alles onder ongeveer een halve punt kan er elegant uitzien op een monitor en dan verdwijnen op een 600 dpi-kantoorprinter, dus 0,75pt is een redelijke vloer voor elke lijn die een afdruk moet overleven
Paginering tegen echte data, niet tegen voorbeelddata
Eén detail om goed te krijgen voordat de lay-out vastligt: numerieke kolommen moeten op hun rechterrand worden uitgelijnd, en de manier om dat te doen is de gerenderde breedte van elke waarde te meten en deze terug te plaatsen vanaf de kolomgrens, niet om de string op te vullen met voorloopspaties. Spatie-opvulling lijnt alleen uit in een monospaced font, en niemand zet een financieel rapport in een monospaced font. Voer de waarden eerst door Delphi's locale-bewuste routines zoals FormatFloat, zodat het scheidingsteken voor duizendtallen waarvan u de breedte meet hetzelfde is als wat de locale van de klant daadwerkelijk zal weergeven
Het gevaar bij paginering is dat u het schrijft tegen de demodataset, waar tien korte rijen op één pagina passen en de lus nooit hoeft te breken. Productie overhandigt u een klant wiens bedrijfsnaam 140 tekens lang is en een afschrift met 4.000 regelitems, en nu moet de lus elke keer correct breken. Het patroon dat standhoudt is één Y-cursor die naar beneden beweegt terwijl u de hoogte van elke rij aftrekt, en een controle die een nieuwe pagina start zodra de cursor de onderrand zou kruisen. Naar beneden betekent hier Y verlagen, wat de enige plek is waar de oorsprong linksonder contra-intuïtief blijft. Houd al dat in één routine die ook SetFont opnieuw afgeeft en de lopende koptekst op de nieuwe pagina hertekent, en de off-by-one-pagina-bugs krijgen nooit voet aan de grond. Als dezelfde rapporten ook aan archief- of toegankelijkheidsregels moeten voldoen, zijn de keuzes die u hier precies maakt, welke fonts u insluit, of de uitvoer tagged is, welke kleurruimten u gebruikt, degene die die standaarden controleren; de HotPDF PDF/A-, PDF/X- en PDF/UA-gids is het lezen waard voordat de sjabloon hard wordt
Elke aanroep die hier wordt getoond, de tekstpositionering, fontregistratie, afbeeldingsinsluiting en padtekenen, zit in de HotPDF Delphi Component voor Delphi en C++Builder, waarvan de referentie de volledige output-API documenteert naast de formulier-, versleutelings- en ondertekeningsfuncties die ernaast staan