PDF Library for Delphi renderar HTML till en PDF-sida med riktig tvådimensionell layout: display: flex och display: grid mäts och placeras snarare än degraderas till staplade block, och fotnoter reserveras längst ner i den ruta som bär deras referens, med numrering som förblir löpande över kolumner och sidor. Ingångspunkterna är de välbekanta, DrawHTMLTextBox för en enda ruta och DrawHTMLStory för flerkolumnigt flöde
Detta spelar roll eftersom HTML är hur det mesta rapportinnehåll anländer numera. Mallar skrivs av personer som skriver CSS, instrumentpaneler designas som kort, och en renderare som tyst kollapsar en flex-rad till fyra staplade block producerar ett dokument som inte alls liknar designen. Innan denna förmåga fanns var den enda tvådimensionella behållare motorn mätte tabellen, så varje kortlayout måste omauktoreras som en tabell för hand
Vad ändrades i layoutmodellen?
Den tidigare huvudloopen underhöll en enda radruta och avancerade nedåt på sidan. Den modellen hanterar inline-innehåll och staplade block perfekt och kan inte uttrycka en behållare vars barn storleksbestäms i förhållande till varandra. Tabeller var det enda undantaget, med sin egen tvåpassmätning
Flex och grid lägger var och en till ett begränsat mätpass över en behållares barn, och det viktiga ordet är begränsat. En flex-behållare mäter upp till 256 direkta barn in i en fast array. Ett grid använder en beläggningsmatris på högst 64 gånger 64 celler för deterministisk automatisk placering. De taken finns så att en fientlig eller genererad stilmall inte kan driva obegränsad rekursion eller kvadratiskt placeringsminne, vilket är en verklig oro när HTML:en kommer från en mall en kund redigerar
Hur flex-objekt får sina storlekar
I radriktningen summerar behållaren varje objekts bas tillsammans med dess växt- och krympvikter, och fördelar sedan det överblivna utrymmet, positivt eller negativt, enligt dessa vikter. Med flex-wrap löses varje rad oberoende, så en rad som bryts till två rader tilldelar ledigt utrymme per rad snarare än över hela behållaren. I kolumnriktningen körs samma huvudaxelfördelning mot antingen en explicit höjd eller innehållshöjden
justify-content, align-items, gap och de omvända riktningarna verkar på geometri som redan har mätts. De flyttar rutor; de utlöser aldrig ommätning av objektinnehåll. Den separationen är det som hindrar en komplex instrumentpanel från att mäta sina barn flera gånger om
uses
PDFlibrary;
var
Lib: TPDFlib;
Html, Remainder: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
Html :=
'<div style="display:flex; gap:12px;">' +
' <div style="flex:2 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Revenue</b><br/>EUR 4,182,300</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Margin</b><br/>18.4%</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Backlog</b><br/>92 days</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 40, 515, 120, Html);
if Remainder <> '' then
Log('content did not fit - carry the remainder to the next box');
Lib.SaveToFile('dashboard.pdf');
finally
Lib.Free;
end;
end;
Returvärdet är fortsättningssträngen, vilket är hur varje HTML-ritningsingångspunkt rapporterar vad som inte fick plats. Skicka den till nästa ruta eller nästa sida och flödet återupptas där det stannade
Grid-placering, och vad ett spår kan vara
Grid-spår accepterar fasta längder, procentsatser, fr-enheten, enkla repeat()-uttryck och minmax(). Automatisk placering fyller beläggningsmatrisen deterministiskt, så samma HTML producerar alltid samma arrangemang. Explicita koordinater tillåts överlappa, vilket är avsiktligt: en design som lägger en badge ovanpå ett kort uttrycker en avsikt, inte ett fel. När bara en axel anges explicit söker placeringen bara på den andra axeln
Objekt som sträcker sig över flera rader bidrar med sin uppmätta höjd tillbaka till de rader de täcker, genomsnittligt fördelat över dem, vilket hindrar ett högt spännande objekt från att klämma ihop en enda rad medan dess grannar lämnas låga:
Html :=
'<div style="display:grid; grid-template-columns:repeat(3, 1fr); ' +
' gap:10px;">' +
' <div style="grid-row:span 2; background:#eef;">Site plan</div>' +
' <div>Inspector</div>' +
' <div>Date</div>' +
' <div style="grid-column:2 / span 2;">Findings summary</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 180, 515, 260, Html);
Flex- och grid-barn renderas genom samma HTML-renderare som allt annat, vilket är egenskapen som gör funktionen användbar snarare än en separat värld. Typsnitt, CSS-kaskaden, länkar, bilder, tabeller och ytterligare nästlade flex- eller grid-behållare beter sig alla inuti ett flex-objekt precis som de gör på toppnivå, och den yttre layoutplanen registrerar de slutliga text- och rektangelkommandona så att upprepad ritning återanvänder den befintliga mätningscachen
Varför är fotnoter ett pagineringsproblem?
En fotnot är inte innehåll som flödar efter stycket som innehåller dess referens; det är innehåll som måste förekomma längst ner i samma ruta som dess referens. Det vänder på den vanliga mätordningen, eftersom utrymmet tillgängligt för brödtext nu beror på innehåll som ännu inte lagts ut
Renderaren mäter därför noten när den möter referensen, och drar av notens area från brödtextens höjdbudget för den aktuella begränsade rutan. Om referensen, brödtexten hittills och noten inte alla får plats flyttas fotnotsmarkören och allt efter den tillsammans in i fortsättningssträngen. Den regeln är det som förhindrar de två klassiska felen: en not som skriver över brödtexten, och en not strandad på en sida vars referens är på den föregående
I en begränsad ruta fästs notens area vid botten med en avgränsningslinje ovanför den. Vid obegränsad mätning, där det inte finns någon rutthöjd att fästa vid, följer notens area omedelbart efter brödtexten. Numrering bärs i ett tilläggsfält på fortsättningsstacken, så DrawHTMLTextBox och DrawHTMLStory håller sekvensen löpande över kolumner och sidor, och en fortsättningssträng producerad innan det fältet existerade återupptas fortfarande korrekt
// Fotnoter inuti en flerkolumnig story håller en löpande sekvens
Html := LoadTemplate('chapter.html'); // använder float:footnote-markörer
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
2, // kolumner
16, // mellanrum i punkter
20, // maximalt antal sidor för denna story
Html);
if Remainder <> '' then
Log('story exceeded its page budget');
Praktisk vägledning för mallförfattare
Designa inom de dokumenterade taken. En flex-behållare med fler än 256 direkta barn är nästan alltid en datatabell klädd i flex-kostym, och tabellvägen mäter den bättre ändå. Ett grid större än 64 gånger 64 är ett kalkylblad, och samma råd gäller. För flerkolumnig brödtext styr kolumn- och avstavningsbeteendet som beskrivs i avstavning och balanserade textkolumner hur flödet ser ut inuti varje kolumn
Mät innan du ritar när en layout måste passa. GetHTMLTextHeight rapporterar den höjd en given bredd skulle behöva, vilket är det billiga sättet att välja mellan en layout och en annan innan du binder dig till bläck. Och behandla en icke-tom fortsättningssträng som normalt snarare än exceptionellt: det är mekanismen genom vilken långt innehåll pagineras, inte en felsignal
Där HTML:en kommer från en rapportmotor snarare än från handskrivna mallar samspelar den datamängdsdrivna vägen i rapportmotorn för datamängder väl med detta, och genererar den markup som flex och grid sedan arrangerar. Och när samma innehåll också måste lämna PDF:en igen sluter den semantiska exportvägen i export av PDF till Markdown och DOCX rundturen
HTML-layout, rapportgenerering och semantisk export är delar av ett bibliotek för Delphi, C++Builder och Free Pascal; den fullständiga funktionslistan finns på sidan för PDF Library for Delphi