HotPDF kan een gepagineerd document opbouwen vanuit een declaratieve boom in plaats van vanuit coördinaten. U stelt een THPDFDOMDocument samen uit secties, stacks, tekst, lijsten en tabellen, geeft die door aan THPDFDOMRenderer, en de renderer meet, pagineert, tekent paginaonderdelen en genereert, indien gevraagd, de PDF/UA-structuurboom die het resultaat toegankelijk maakt. De lay-outcode berekent nooit een y-coördinaat
Iedereen die ooit een coördinaatgestuurde rapportgenerator heeft onderhouden, weet waarom dit ertoe doet. De eerste versie werkt. Dan groeit een klantadres naar drie regels, krijgt een tabel meer rijen, loopt een gelokaliseerde kop over meerdere regels door, en klopt elke y-positie stroomafwaarts niet meer. De oplossingen stapelen zich op als handmatige paginabreuk-controles verspreid door de bedrijfslogica, en de eis voor getagde PDF die twee jaar later opduikt, kan niet worden ingebouwd in code die geen idee heeft wat een alinea is
Wat de boom bezit, en waarom eigendom strikt is
De DOM handhaaft op elk niveau enkelvoudig eigendom: het document bezit zijn secties, een sectie bezit zijn body, header en footer, en stacks, containers en tabellen bezitten hun kinderen. Hergebruik gebeurt via Clone of via een geregistreerde factory, nooit door hetzelfde object aan twee ouders te koppelen. Die regel is geen formaliteit. Een component die tweemaal in de boom voorkomt, zou tweemaal worden gemeten met verschillende beperkingen en tweemaal worden vrijgegeven bij afbraak
Het praktische gevolg voor de aanroepende code is dat helpers nieuwe instanties retourneren. Het registreren van een factory met RegisterComponent en het aanroepen van CreateComponent geeft u een benoemd recept dat elke keer een vers component produceert, en zo horen herhaalde onderdelen zoals een handtekeningblok of een juridische voettekst in de boom thuis
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // genereer de PDF/UA-structuurboom
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // A4 in punten
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); // gewichten, geen absolute breedtes
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // headerrij
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;
Hoe vermijdt paginering kwadratische kosten?
De naïeve manier om een boom te pagineren, is alles wat niet paste te klonen en naar de volgende pagina over te brengen. Bij een tabel met tienduizend rijen kloont dat de resterende rijen eenmaal per pagina en verandert het een lineair document in een kwadratisch document
HotPDF splitst in plaats daarvan smal. De renderer op het hoogste niveau doorloopt body-kinderen op index en kloont nooit een hele sectie of body. Alleen geneste stacks en containers die daadwerkelijk over een paginagrens heen reiken, krijgen hun betrokken subboom gekloond, en de twee zware bladtypen dragen een cursor in plaats van een kopie: een tekstvoortzetting bewaart het bronkarakterbereik dat het nog verschuldigd is, en een tabelvoortzetting bewaart de rijenreeks die het nog moet plaatsen. Lange documenten blijven lineair, en lange alinea's kosten hetzelfde ongeacht of ze eenmaal of vijfmaal breken
Meting blijft eerlijk over neveneffecten. THPDFLayoutElement.Measure moet vrij zijn van tekenneveneffecten, en daadwerkelijke plaatsing verloopt altijd via THotPDF.PlaceLayoutElement, dezelfde centrale routine die het geplaatste fragment opnieuw meet, overloopeigendom instelt en diagnostiek vastlegt. De DOM-renderer bepaalt alleen het nieuwe-paginabeleid, paginaonderdelen, spatiëring en de levensduur van voortzettingen
De tabelheaderregels die een oneindig document voorkomen
Tabelheaders herhalen over pagina's heen klinkt eenvoudig, maar verbergt twee faalmodi. HotPDF vereist dat headerrijen alleen voorkomen in de eerste reeks opeenvolgende rijen, en dat de eerste splitsing alle headerrijen plus ten minste één body-rij bevat. Zonder de tweede regel produceert een header die hoger is dan de resterende ruimte een pagina die niets anders bevat dan de header, gevolgd door nog een identieke pagina, eindeloos
Vervolgpagina's tekenen de header opnieuw, en die opnieuw getekende kopie wordt gemarkeerd als artefact in plaats van als inhoud, wat het juiste antwoord is voor zowel toegankelijkheid als tekstextractie. De oorspronkelijke headerrij blijft precies één keer in de logische tabelstructuur staan. Sla dit over en een schermlezer kondigt de kolomtitels opnieuw aan midden in de data, en een tekstextractor voegt een dubbele headerrij in tussen body-rijen
Er is ook een defensief plafond op de voortzettingsdiepte, omdat een aangepast component vrij is om Split zo te implementeren dat het altijd een equivalente staart retourneert. De renderer controleert de limiet nadat de staart is losgekoppeld en voordat de volgende pagina begint, en de huidige iteratie geeft de staart vrij in zijn eigen finally-blok, zodat een zich misdragend component van derden faalt met een diagnosticeerbare fout in plaats van een schijf te vullen
Eén logisch element, veel paginafragmenten
Automatisch taggen is waar het pagineringsmodel en het structuurmodel het met elkaar eens moeten zijn. Een alinea die over twee pagina's is verdeeld, is één logische alinea, dus moet ze één structuurelement blijven. Maar gemarkeerde-inhoud-identifiers zijn per pagina, dus heeft elk zichtbaar fragment zijn eigen MCID nodig op de pagina waarop het verschijnt
HotPDF lost dit op door één enkel structuurelement te behouden en voor elk fragment een verwijzing naar gemarkeerde inhoud toe te voegen aan de /K-array ervan, waarbij het paar /Pg en /MCID de pagina en de identifier aangeeft. De ParentTree-slot voor die MCID verwijst terug naar hetzelfde element. Dit is precies wat ISO 14289 verwacht, en het is de reden waarom voortzettingsklonen verschillen van gewone klonen: een gewone Clone betekent nieuwe logische inhoud en krijgt een nieuwe semantische identiteit, terwijl de interne voortzettingskloon de identiteit erft van het component dat het voortzet
Hergebruik van elementen wordt opgezocht via een index van semantische identiteiten, gesorteerd op componentpointer en doorzocht met binaire vergelijking, wat de zoekopdracht logaritmisch houdt bij grote bomen. De index bevat alleen niet-bezittende verwijzingen; de levensduur van de structuurobjecten zelf blijft bij de PDF-objectgraaf
Structuurregels die de renderer vooraf afdwingt
Met GenerateStructure ingeschakeld, worden verschillende PDF/UA-regels gecontroleerd terwijl de boom wordt gerenderd in plaats van nadat het bestand bestaat. Koppen beginnen op niveau 1 en mogen geen niveaus overslaan. LI mag alleen binnen L voorkomen, en Lbl en LBody alleen binnen LI. TR hoort bij een tabel, en TH en TD bij een rij. Een figuur zonder alternatieve tekst wordt in PDF/UA-modus geweigerd
Vroeg weigeren is hier de bewuste keuze. Een validator die pas na het schrijven van het document een ontbrekende alternatieve tekst rapporteert, vertelt u dat een batch van tienduizend afschriften opnieuw moet worden gegenereerd; een renderer die het component weigert, vertelt u welk component, terwijl de gegevens die het produceerden nog binnen bereik zijn. Conformiteitsverificatie hoort nog steeds als aparte stap in de pijplijn thuis, en de mechanica daarvan wordt behandeld in PDF/A-, PDF/X- en PDF/UA-validatie
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;
De statistiekenrecord is nuttiger dan hij op het eerste gezicht lijkt. Een sterk stijgende SplitCount na een sjabloonwijziging betekent meestal dat een component hoger begon te meten dan zijn container. Een geleidelijk oplopende MaximumContinuationDepth is de vroege waarschuwing voor een component waarvan Split te weinig voortgang per pagina maakt. En het vergelijken van ArtifactCount met het aantal vervolgpagina's bevestigt dat herhaalde headers daadwerkelijk als artefacten zijn getagd
Waar de DOM past naast de directe API
De DOM vervangt niet het directe tekenen; ze bouwt voort op dezelfde paginaobjecten. Alles wat de renderer plaatst, kan worden afgewisseld met directe aanroepen op THotPDF, wat ertoe doet wanneer een rapport één handmatig gepositioneerd element nodig heeft, zoals een handtekeningafbeelding op een exacte locatie. Het sluiten van pagina's blijft onder controle van AddPage en EndDoc, zodat de directe flush-modus geen voltooide pagina's in het geheugen houdt en het resident geheugen bepaald blijft door de huidige voortzettingen, lettertyperesources en de gewone documentobjectgraaf
Kies de DOM wanneer de inhoud datagedreven is en de lay-out regelgedreven, en houd direct tekenen aan voor vaste illustraties. Als uw huidige pijnpunt specifiek tabelpaginering is, is de smallere aanpak in tabellen genereren in PDF het waard om eerst te lezen, en tekstniveaugedrag zoals uitvulling wordt beschreven in tekstuitvulling
Declaratieve lay-out, automatisch taggen en de directe teken-API worden geleverd in hetzelfde component voor Delphi en C++Builder; de volledige functielijst staat op de HotPDF Delphi PDF-componentpagina