HotPDF konverterer HTML til PDF-sider via THPDFHTMLImporter.RenderHTML5, en versioneret paged-media-profil med et rigtigt DOM, en rigtig CSS-cascade, tabeller, sideskift og valgfri AcroForm-generering ud fra HTML-formularelementer. Den kører helt in-process: ingen browsermotor, ingen Chromium, ingen ekstern konverter, og som standard slet ingen fil- eller netværksadgang
Grunden til, at der findes et separat indgangspunkt i stedet for en forbedret RenderHTML, er kompatibilitet. Den ældre metode har gengivet fakturaer og breve i produktionsapplikationer i årevis, og et mere tilgivende DOM med en fuld cascade ændrer output for dokumenter, der er finjusteret mod den gamle adfærd. Derfor bevarer den gamle metode sin nøjagtige semantik, og alt det nye ligger bag RenderHTML5, hvis profilversion rapporteres af HTML5ProfileVersion, og hvis understøttede milepæle er angivet af HTML5ProfileMilestones
Hvad tilføjer HTML5-profilen egentlig?
Fire egenskaber betyder noget i praksis. Et strukturelt DOM, hvor html og body er containere frem for tekstnoder. En CSS-cascade med korrekt præcedens. Tabellayout. Og eksplicit kontrol over sideskift, hvilket er, hvad "paged media" betyder: HTML blev designet til et scrollende viewport, og at gøre det til sider kræver, at man beslutter, hvor indhold må bryde
Pointen om containere lyder som en implementeringsdetalje og er i virkeligheden forskellen på, om det virker eller ej. Hvis rodelementerne behandles som tekstcontainere, kollapser hele DOM'et til én tekstblok ved parsing, og enhver efterfølgende regel i stylesheetet, sideskift, tabel og formularundertræ anvendes på ingenting. Enhver HTML-renderer, der producerer én udifferentieret tekstmur fra et struktureret dokument, har begået præcis denne fejl
Cascade-rækkefølgen, forklaret trin for trin
CSS-præcedens er ikke én enkelt sortering. HotPDF anvender normale deklarationer fra matchende regler i rækkefølge efter specificitet og derefter kildeorden, anvender dernæst inline normale deklarationer, så !important-deklarationerne fra matchende regler, og til sidst inline !important. Fire gennemløb, i den rækkefølge
Implementationer, der reducerer dette til "den sidste vinder", får de almindelige tilfælde rigtige og de interessante forkerte, hvilket er værre end at få dem ensartet forkerte, fordi fejlen kun viser sig i skabeloner, der bruger !important til at tilsidesætte et base-stylesheet — netop de skabeloner, der er skrevet af folk, som forventer, at cascaden virker:
uses
HPDFDoc, HPDFHTMLImport;
var
Pdf: THotPDF;
Importer: THPDFHTMLImporter;
Html, Css: WideString;
begin
Pdf := THotPDF.Create(nil);
Importer := THPDFHTMLImporter.Create(Pdf);
try
Pdf.FileName := 'statement.pdf';
Pdf.BeginDoc;
Importer.Margin := 56; // punkter
Importer.BaseFontName := 'Arial';
Importer.BaseFontSize := 10;
Importer.CreateAcroForms := False; // gengiv kontrolelementer, opret ikke felter
Css := '@page { margin: 56pt } ' +
'table { border-collapse: collapse } ' +
'tr.total { page-break-inside: avoid; font-weight: bold }';
Html := LoadTemplate('statement.html');
if not Importer.RenderHTML5(Html, Css) then
raise Exception.Create('nothing was emitted - check the markup');
Pdf.EndDoc;
Writeln(Format('profile=%d nodes=%d rules=%d layout ops=%d',
[Importer.Statistics.ProfileVersion,
Importer.Statistics.ParsedNodeCount,
Importer.Statistics.CSSRuleCount,
Importer.Statistics.LayoutOperationCount]));
finally
Importer.Free;
Pdf.Free;
end;
end;
Det andet argument er et forfatter-stylesheet, der anvendes sammen med, hvad dokumentet i forvejen bærer, hvilket er den rene måde at håndhæve husregler såsom sidemargener og enkelinjekontrol uden at redigere hver eneste skabelon
Hvorfor henter importeren aldrig noget?
En HTML-til-PDF-konverter, der opløser eksterne referencer, er en server-side request forgery-motor forklædt som dokumentgenerering. Peg den mod brugerleveret markup, og den vil villigt hente http://169.254.169.254/ eller en fil-URL fra din applikationsserver og indlejre resultatet i en PDF, som nogen downloader
HotPDF læser derfor hverken filer eller netværk som standard. Billeder og andre ressourcer ankommer på præcis to måder: som data:-URI'er inde i markuppen, eller via OnResource-callbacket, hvor din kode afgør, hvad en URI betyder, og leverer byttene:
procedure TReportForm.SupplyResource(Sender: TObject;
const URI: WideString; ResourceKind: THPDFHTMLResourceKind;
var Data: TBytes; var ContentType: string; var Handled: boolean);
begin
Handled := False;
// Server kun fra en allowlistet lokal asset-mappe. Send aldrig
// URI'en til en HTTP-klient, og sammenføj den aldrig med en sti
// uden først at validere den
if StartsText('asset:', URI) then
begin
Data := LoadAssetBytes(Copy(URI, 7, MaxInt));
ContentType := 'image/png';
Handled := Length(Data) > 0;
end;
end;
Hver ressource, der rent faktisk ankommer, tælles stadig op mod tre separate lofter: hvor mange ressourcer, hvor mange bytes hver, og hvor mange bytes i alt. Et callback, der trofast returnerer, hvad det bliver bedt om, kan ikke lokkes til at udtømme hukommelsen af et dokument, der refererer det samme 50 MB-billede to hundrede gange
Fire budgetter, én fejltilstand
Parsernoder, CSS-regler, layoutoperationer og ressourcer måles uafhængigt af hinanden, fordi de fejler uafhængigt af hinanden. Et dokument kan være lille og patologisk dyrt at layoute; et andet kan være overfladisk med et stylesheet designet til at eksplodere selector-matching. Udtømmes nogen af dem, kaster det EHPDFHTMLImportBudget, en dedikeret exception-klasse, så budgetudtømning kan fanges separat fra fejlformet input
Statistikken nulstilles ved starten af hvert RenderHTML5-kald, så tallene beskriver én import frem for importer-objektets levetid. I en tjeneste, der gengiver utroværdige skabeloner, bør du logge ParsedNodeCount, LayoutOperationCount og ResourceRejectCount pr. dokument: et stigende reject-antal betyder som regel, at en skabelonforfatter refererer assets, dit callback ikke genkender, og et layoutoperationsantal tæt på loftet betyder, at det næste, lidt større dokument vil fejle
Valget mellem HTML-import og layout-API'en
HTML-import vinder, når indholdet er forfattet som HTML af folk, der ikke er Delphi-udviklere, når skabeloner ændres oftere end kode, eller når det samme indhold også skal sendes som e-mail. Det deklarative layout-træ, beskrevet i deklarativt PDF-layout med tagged output, vinder, når dokumentet genereres ud fra data af din egen kode, når du har brug for, at PDF/UA-struktur udsendes automatisk, eller når præcis kontrol over paginering betyder mere end bekvem forfatning
De udelukker ikke hinanden. En almindelig opbygning er HTML-import til brødteksten i en rapport, med header, footer og signaturblokke tegnet direkte, da disse er fast geometri, som ingen skabelon bør eje. Tabeller er det ene område, hvor det er værd at måle begge dele: komplekse finansielle tabeller paginerer nogle gange bedre gennem den dedikerede tabelsti, beskrevet i generering af tabeller i PDF
Sæt CreateAcroForms bevidst. Slået fra gengives HTML-formularelementer som statisk grafik, hvilket er, hvad en udskrevet kontoudskrift ønsker. Slået til bliver de rigtige interaktive AcroForm-felter, hvilket er, hvad en udfyldelig formular ønsker, og den efterfølgende feltadfærd er dækket i tilføjelse af formularfelter til en indlæst PDF
HTML-import, deklarativt layout og det direkte tegne-API rammer alle det samme dokumentobjekt i Delphi og C++Builder; den komplette funktionsliste findes på HotPDF Delphi PDF component-siden