PDF Library for Delphi randează HTML într-o pagină PDF cu layout real bidimensional: display: flex și display: grid sunt măsurate și plasate, nu degradate la blocuri stivuite, iar notele de subsol sunt rezervate în partea de jos a casetei care poartă referința lor, cu o numerotare care rămâne continuă pe coloane și pagini. Punctele de intrare sunt cele familiare, DrawHTMLTextBox pentru o singură casetă și DrawHTMLStory pentru flux pe mai multe coloane
Asta contează deoarece HTML este modul în care sosește acum cea mai mare parte a conținutului de raport. Șabloanele sunt create de oameni care scriu CSS, tablourile de bord sunt proiectate ca și carduri, iar un randor care restrânge tăcut un rând flex la patru blocuri stivuite produce un document care nu seamănă deloc cu designul. Până la existența acestei capacități, singurul container bidimensional pe care motorul îl măsura era tabelul, așa că fiecare layout de card trebuia recreat manual ca tabel
Ce s-a schimbat în modelul de layout?
Bucla principală anterioară menținea o singură casetă de linie și avansa în josul paginii. Acel model gestionează perfect conținutul inline și blocurile stivuite și nu poate exprima un container ai cărui copii sunt dimensionați unul în raport cu celălalt. Tabelele erau singura excepție, cu propria lor măsurare în două treceri
Flex și grid adaugă fiecare o trecere de măsurare mărginită peste copiii unui container, iar cuvântul important este mărginită. Un container flex măsoară până la 256 de copii direcți într-un array fix. Un grid folosește o matrice de ocupare de cel mult 64 pe 64 celule pentru plasare automată deterministă. Acele plafoane există astfel încât o foaie de stil ostilă sau pur și simplu generată să nu poată conduce recursivitate nemărginită sau memorie de plasare pătratică, ceea ce e o preocupare reală când HTML-ul provine dintr-un șablon pe care un client îl editează
Cum primesc elementele flex dimensiunile lor
În direcția rândului, containerul însumează baza fiecărui element împreună cu ponderile lui de creștere și micșorare, apoi distribuie spațiul rămas, pozitiv sau negativ, conform acelor ponderi. Cu flex-wrap, fiecare linie este rezolvată independent, așa că un rând care se rupe în două linii alocă spațiu liber per linie, nu peste tot containerul. În direcția coloanei, aceeași distribuție de axă principală rulează fie față de o înălțime explicită, fie față de înălțimea conținutului
justify-content, align-items, gap și direcțiile inverse operează pe geometrie deja măsurată. Ele mută casete; nu declanșează niciodată remăsurarea conținutului elementului. Acea separare este ceea ce împiedică un tablou de bord complex să-și măsoare copiii de mai multe ori
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;
Valoarea de retur este șirul de continuare, care e modul în care fiecare punct de intrare de desenare HTML raportează ce nu a încăput. Predă-l următoarei casete sau următoarei pagini, iar fluxul reia de unde s-a oprit
Plasarea grid, și ce poate fi o pistă
Pistele grid acceptă lungimi fixe, procente, unitatea fr, expresii simple repeat() și minmax(). Plasarea automată umple matricea de ocupare deterministic, așa că același HTML produce întotdeauna același aranjament. Coordonatele explicite au voie să se suprapună, ceea ce e deliberat: un design care stratifică o insignă peste un card exprimă o intenție, nu o eroare. Când doar o axă este dată explicit, plasarea caută doar pe cealaltă axă
Elementele care se întind pe mai multe rânduri contribuie cu înălțimea lor măsurată înapoi la rândurile pe care le acoperă, calculată ca medie pe ele, ceea ce împiedică un element întins înalt să comprime un singur rând, lăsând vecinii scunzi:
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);
Copiii flex și grid sunt randați prin același randor HTML ca orice altceva, ceea ce e proprietatea care face funcția utilizabilă, nu o lume separată. Fonturile, cascada CSS, linkurile, imaginile, tabelele și alte containere flex sau grid imbricate se comportă toate în interiorul unui element flex exact ca la nivelul superior, iar planul de layout exterior înregistrează comenzile finale de text și dreptunghi, astfel încât desenarea repetată reutilizează cache-ul de măsurare existent
De ce sunt notele de subsol o problemă de paginare?
O notă de subsol nu este conținut care curge după paragraful care conține referința ei; este conținut care trebuie să apară în partea de jos a aceleiași casete ca referința sa. Asta inversează ordinea obișnuită de măsurare, deoarece spațiul disponibil pentru textul corpului depinde acum de conținut care nu a fost încă plasat
Randorul, prin urmare, măsoară nota când întâlnește referința, și scade aria notei din bugetul de înălțime a corpului al casetei mărginite curente. Dacă referința, textul corpului de până acum și nota nu pot încăpea toate, marcatorul de notă de subsol și tot ce urmează după se mută împreună în șirul de continuare. Acea regulă este ceea ce previne cele două eșecuri clasice: o notă suprapusă peste textul corpului, și o notă rămasă pe o pagină a cărei referință este pe cea anterioară
Într-o casetă mărginită, aria notei este fixată la fund cu o linie separatoare deasupra ei. În măsurarea nemărginită, unde nu există o înălțime de casetă la care să fie fixată, aria notei urmează imediat după corp. Numerotarea este purtată într-un câmp de extensie pe stiva de continuare, așa că DrawHTMLTextBox și DrawHTMLStory mențin secvența continuă pe coloane și pagini, iar un șir de continuare produs înainte ca acel câmp să existe încă reia corect
// Notele de subsol în interiorul unei povești (story) pe mai multe coloane
// păstrează o singură secvență continuă
Html := LoadTemplate('chapter.html'); // folosește marcatori float:footnote
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
2, // coloane
16, // interspațiu (gutter) în puncte
20, // numărul maxim de pagini pentru această poveste
Html);
if Remainder <> '' then
Log('story exceeded its page budget');
Îndrumări practice pentru autorii de șabloane
Proiectează în interiorul plafoanelor documentate. Un container flex cu mai mult de 256 de copii direcți este aproape întotdeauna un tabel de date deghizat în flex, iar calea de tabel îl măsoară oricum mai bine. Un grid mai mare de 64 pe 64 este o foaie de calcul, și se aplică același sfat. Pentru text de corp pe mai multe coloane, comportamentul de coloană și despărțire în silabe descris în despărțirea în silabe și coloanele de text echilibrate guvernează cum arată fluxul în interiorul fiecărei coloane
Măsoară înainte de a desena atunci când un layout trebuie să încapă. GetHTMLTextHeight raportează înălțimea de care ar avea nevoie o lățime dată, ceea ce e modul ieftin de a alege între un layout și altul înainte de a te angaja cu cerneala. Și tratează un șir de continuare non-gol ca normal, nu excepțional: este mecanismul prin care conținutul lung se paginează, nu un semnal de eroare
Unde HTML-ul provine dintr-un motor de rapoarte, nu din șabloane scrise manual, ruta condusă de set de date din motorul de rapoarte pe bază de set de date se combină bine cu asta, generând marcajul pe care flex și grid îl aranjează apoi. Iar când același conținut trebuie și el să părăsească din nou PDF-ul, calea de export semantic din exportul PDF în Markdown și DOCX închide bucla dus-întors
Layout-ul HTML, generarea de rapoarte și exportul semantic fac parte dintr-o singură bibliotecă pentru Delphi, C++Builder și Free Pascal; lista completă de funcții este pe pagina PDF Library for Delphi