HotPDF kan bygge et paginert dokument fra et deklarativt tre i stedet for fra koordinater. Du setter sammen en THPDFDOMDocument av seksjoner, stabler, tekst, lister og tabeller, gir den til THPDFDOMRenderer, og gjengiveren måler, paginerer, tegner sideinnretning, og produserer, når det blir bedt om det, PDF/UA-strukturtreet som gjør resultatet tilgjengelig. Layout-koden regner aldri ut en y-koordinat
Alle som har vedlikeholdt en koordinatstyrt rapportgenerator, vet hvorfor dette betyr noe. Den første versjonen fungerer. Så vokser en kundeadresse til tre linjer, en tabell får flere rader, en lokalisert overskrift brytes over flere linjer, og hver eneste y-posisjon nedstrøms blir feil. Rettelsene hoper seg opp som manuelle sideskift-sjekker spredt gjennom forretningslogikken, og kravet om tagget PDF som dukker opp to år senere, kan ikke ettermonteres på kode som ikke aner hva et avsnitt er
Hva treet eier, og hvorfor eierskapet er strengt
DOM-en håndhever enkelt eierskap på hvert nivå: dokumentet eier sine seksjoner, en seksjon eier sin brødtekst, topptekst og bunntekst, og stabler, beholdere og tabeller eier sine barn. Gjenbruk skjer gjennom Clone eller gjennom en registrert factory, aldri ved å feste det samme objektet til to foreldre. Den regelen er ikke bare formalitet. En komponent som opptrer to ganger i treet, ville blitt målt to ganger med forskjellige begrensninger og frigjort to ganger ved nedrivning
Den praktiske konsekvensen for kallende kode er at hjelpefunksjoner returnerer nye instanser. Å registrere en factory med RegisterComponent og kalle CreateComponent gir deg en navngitt oppskrift som produserer en fersk komponent hver gang, og det er slik gjentatt innretning som en signaturblokk eller en juridisk bunntekst hører hjemme i treet
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // produser PDF/UA-strukturtreet
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // A4 i punkter
Section.PageHeight := 842;
Section.MarginLeft := 56;
Section.MarginTop := 56;
Section.MarginRight := 56;
Section.MarginBottom := 56;
Section.Style.FontName := 'Helvetica';
Section.Style.FontSize := 10;
Section.Body.AddHeading('Annual maintenance report', 1);
Section.Body.AddText('Every asset inspected during the reporting ' +
'period is listed below, grouped by site.');
Section.Body.AddSpacer(12);
Table := THPDFDOMTable.Create('assets');
Table.AddColumn(3); // vekter, ikke absolutte bredder
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // header-rad
Row[0].Text := 'Asset';
Row[1].Text := 'Last service';
Row[2].Text := 'Status';
for I := 0 to High(Assets) do
begin
Row := Table.AddRow(16);
Row[0].Text := Assets[I].Name;
Row[1].Text := Assets[I].ServiceDate;
Row[2].Text := Assets[I].Status;
end;
Section.Body.Add(Table);
end;
Hvordan unngår pagineringen kvadratisk kostnad?
Den naive måten å paginere et tre på, er å klone det som ikke fikk plass, og føre det over til neste side. På en tabell med ti tusen rader kloner det de gjenværende radene én gang per side og gjør et lineært dokument om til et kvadratisk et
HotPDF deler i stedet smalt. Toppnivå-gjengiveren går gjennom brødtekstens barn etter indeks og kloner aldri en hel seksjon eller brødtekst. Bare nøstede stabler og beholdere som virkelig strekker seg over en sidegrense, får sitt berørte deltre klonet, og de to tunge bladtypene bærer en peker i stedet for en kopi: en tekstfortsettelse lagrer kildeteksten sitt gjenværende tegnintervall den fremdeles skylder, og en tabellfortsettelse lagrer radskiven den ennå ikke har plassert. Lange dokumenter forblir lineære, og lange avsnitt koster det samme enten de brytes én gang eller fem ganger
Målingen forblir ærlig om sideeffekter. THPDFLayoutElement.Measure er påkrevd å være fri for tegne-sideeffekter, og den faktiske plasseringen går alltid gjennom THotPDF.PlaceLayoutElement, den samme sentrale rutinen som måler det plasserte fragmentet på nytt, setter opp overflyt-eierskap og registrerer diagnostikk. DOM-gjengiveren avgjør bare policyen for nye sider, sideinnretning, avstand og levetiden til fortsettelser
Tabellhode-reglene som forhindrer et uendelig dokument
Å gjenta tabelloverskrifter på tvers av sider høres enkelt ut og skjuler to feiltilstander. HotPDF krever at overskriftsrader bare opptrer i den første sammenhengende raden av rader, og at den første delingen får plass til alle overskriftsradene pluss minst én brødtekstrad. Uten den andre regelen produserer en overskrift som er høyere enn den gjenværende plassen, en side som ikke inneholder annet enn overskriften, etterfulgt av enda en identisk side, i det uendelige
Fortsettelsessider tegner overskriften på nytt, og den nytegnede kopien merkes som et artefakt snarere enn som innhold, noe som er det riktige svaret både for tilgjengelighet og tekstuthenting. Den opprinnelige overskriftsraden forblir i den logiske tabellstrukturen nøyaktig én gang. Hopp over dette, og en skjermleser kunngjør kolonnetitlene på nytt midt i dataene, og en tekstuthenter setter inn en duplikat overskriftsrad mellom brødtekstrader
Det finnes også et defensivt tak på fortsettelsesdybden, fordi en tilpasset komponent står fritt til å implementere Split på en måte som alltid returnerer en tilsvarende hale. Gjengiveren sjekker grensen etter at halen er koblet fra og før neste side starter, og den nåværende iterasjonen frigjør halen i sin egen finally-blokk, slik at en tredjepartskomponent som oppfører seg dårlig, feiler med en diagnostiserbar feil i stedet for å fylle opp en disk
Ett logisk element, mange sidefragmenter
Automatisk tagging er der pagineringsmodellen og strukturmodellen må stemme overens. Et avsnitt delt over to sider er ett logisk avsnitt, så det må forbli ett strukturelement. Men markert-innhold-identifikatorer gjelder per side, så hvert synlige fragment trenger sin egen MCID på siden det opptrer på
HotPDF løser dette ved å beholde ett enkelt strukturelement og legge til en markert-innhold-referanse i dets /K-array for hvert fragment, der paret /Pg og /MCID identifiserer siden og identifikatoren. ParentTree-plassen for den MCID-en peker tilbake til det samme elementet. Dette er nøyaktig hva ISO 14289 forventer, og det er grunnen til at fortsettelseskloner skiller seg fra vanlige kloner: en vanlig Clone betyr nytt logisk innhold og får en ny semantisk identitet, mens den interne fortsettelseskloningen arver identiteten til komponenten den fortsetter
Elementgjenbruk slås opp gjennom en indeks over semantiske identiteter sortert etter komponentpeker og søkt gjennom med binær sammenligning, noe som holder oppslaget logaritmisk på store trær. Indeksen inneholder bare ikke-eiende referanser; levetiden til selve strukturobjektene forblir hos PDF-objektgrafen
Strukturregler gjengiveren håndhever på forhånd
Med GenerateStructure aktivert blir flere PDF/UA-regler sjekket mens treet blir gjengitt, snarere enn etter at filen finnes. Overskrifter starter på nivå 1 og kan ikke hoppe over nivåer. LI kan bare opptre inni L, og Lbl og LBody bare inni LI. TR tilhører en tabell, og TH og TD tilhører en rad. En figur uten alternativ tekst avvises i PDF/UA-modus
Å avvise tidlig er det bevisste valget her. En validator som rapporterer manglende alternativ tekst etter at dokumentet er skrevet, forteller deg at en batch på ti tusen kontoutskrifter må genereres på nytt; en gjengiver som avviser komponenten, forteller deg hvilken komponent, mens dataene som produserte den, fremdeles er i scope. Samsvarsverifisering hører fortsatt hjemme i pipelinen som et eget trinn, og mekanikken bak det er dekket i validering av PDF/A, PDF/X og PDF/UA
var
Pdf: THotPDF;
Renderer: THPDFDOMRenderer;
Stats: THPDFDOMRenderStatistics;
begin
Pdf := THotPDF.Create(nil);
Renderer := THPDFDOMRenderer.Create;
try
Pdf.FileName := 'maintenance-report.pdf';
Pdf.BeginDoc;
Stats := Renderer.Render(Doc, Pdf);
Pdf.EndDoc;
Writeln(Format('%d page(s), %d placement(s), %d split(s)',
[Stats.PageCount, Stats.PlacementCount, Stats.SplitCount]));
Writeln(Format('structure elements=%d marked content=%d artifacts=%d',
[Stats.StructureElementCount, Stats.MarkedContentCount,
Stats.ArtifactCount]));
Writeln(Format('deepest continuation chain: %d',
[Stats.MaximumContinuationDepth]));
finally
Renderer.Free;
Doc.Free;
Pdf.Free;
end;
end;
Statistikkposten er mer nyttig enn den ser ut ved første øyekast. At SplitCount stiger kraftig etter en malendring, betyr som regel at en komponent begynte å måle høyere enn beholderen sin. At MaximumContinuationDepth kryper oppover, er det tidlige varselet for en komponent hvis Split gjør for lite fremgang per side. Og å sammenligne ArtifactCount mot antallet fortsettelsessider bekrefter at gjentatte overskrifter virkelig ble tagget som artefakter
Hvor DOM-en passer inn ved siden av det direkte API-et
DOM-en erstatter ikke direkte tegning; den ligger oppå de samme sideobjektene. Alt gjengiveren plasserer, kan flettes sammen med direkte kall på THotPDF, noe som betyr noe når en rapport trenger ett håndplassert element, som et signaturbilde på et eksakt sted. Sidelukking forblir under kontrollen til AddPage og EndDoc, så den umiddelbare tømmemodusen holder ingen ferdige sider i minnet, og det resident minnet forblir styrt av de gjeldende fortsettelsene, fontressursene og den vanlige dokumentobjektgrafen
Velg DOM-en når innholdet er datadrevet og layouten er regeldrevet, og behold direkte tegning for fast grafikk. Hvis din nåværende smerte spesifikt er tabellpaginering, er den smalere tilnærmingen i å generere tabeller i PDF verdt å lese først, og tekstnivå-oppførsel som blokkjustering er beskrevet i tekstjustering
Deklarativ layout, automatisk tagging og det direkte tegne-API-et leveres i den samme komponenten for Delphi og C++Builder; den komplette funksjonslisten finnes på HotPDF Delphi PDF-komponentsiden