HotPDF može izgraditi paginirani dokument iz deklarativnog stabla umjesto iz koordinata. Sastavljate THPDFDOMDocument od odjeljaka, hrpa (stack), teksta, popisa i tablica, predajete ga THPDFDOMRenderer-u, a renderer mjeri, paginira, crta elemente stranice i, na zahtjev, generira PDF/UA strukturno stablo koje rezultat čini pristupačnim. Kod rasporeda nikada ne izračunava y koordinatu
Svatko tko je održavao generator izvještaja utemeljen na koordinatama zna zašto je ovo važno. Prva verzija radi. Zatim adresa kupca naraste na tri retka, tablica dobije nove retke, lokalizirani naslov se prelama u novi red i svaka daljnja y pozicija postane pogrešna. Popravci se gomilaju kao ručne provjere prijeloma stranice razbacane po poslovnoj logici, a zahtjev za označenim (tagged) PDF-om koji stigne dvije godine kasnije ne može se naknadno ugraditi u kod koji nema pojma što je odlomak
Što stablo posjeduje i zašto je vlasništvo strogo
DOM na svakoj razini provodi jedinstveno vlasništvo: dokument posjeduje svoje odjeljke, odjeljak posjeduje svoje tijelo, zaglavlje i podnožje, a hrpe, kontejneri i tablice posjeduju svoju djecu. Ponovna uporaba odvija se putem Clone ili putem registrirane tvornice, nikada pripajanjem istog objekta dvama roditeljima. To pravilo nije formalnost. Komponenta koja se u stablu pojavi dvaput bila bi izmjerena dvaput s različitim ograničenjima i oslobođena dvaput pri rušenju
Praktična posljedica za pozivajući kod jest da pomoćne funkcije vraćaju nove instance. Registriranje tvornice pomoću RegisterComponent i pozivanje CreateComponent daje vam imenovani recept koji svaki put proizvodi novu komponentu, a upravo tako ponovljeni elementi poput bloka za potpis ili pravnog podnožja pripadaju u stablo
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // generiraj PDF/UA strukturno stablo
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // A4 u točkama
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); // težine, ne apsolutne širine
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // redak zaglavlja
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;
Kako paginacija izbjegava kvadratni trošak?
Naivan način paginacije stabla jest kloniranje svega što nije stalo i prenošenje toga na sljedeću stranicu. Na tablici s deset tisuća redaka to klonira preostale retke jednom po stranici i pretvara linearan dokument u kvadratni
HotPDF umjesto toga dijeli usko. Renderer najviše razine prolazi kroz djecu tijela po indeksu i nikada ne klonira cijeli odjeljak ili tijelo. Samo se ugniježđenim hrpama i kontejnerima koji doista sežu preko granice stranice klonira pogođeno podstablo, a dva teška lista tipa nose kursor umjesto kopije: nastavak teksta pohranjuje raspon izvornih znakova koje još duguje, a nastavak tablice pohranjuje isječak redaka koje tek treba postaviti. Dugi dokumenti ostaju linearni, a dugi odlomci koštaju jednako bez obzira lome li se jednom ili pet puta
Mjerenje ostaje pošteno prema nuspojavama. THPDFLayoutElement.Measure mora biti bez nuspojava crtanja, a stvarno postavljanje uvijek prolazi kroz THotPDF.PlaceLayoutElement, istu središnju rutinu koja ponovno mjeri postavljeni fragment, uspostavlja vlasništvo nad prelijevanjem i bilježi dijagnostiku. DOM renderer odlučuje samo o politici nove stranice, elementima stranice, razmacima i životnom vijeku nastavaka
Pravila zaglavlja tablice koja sprječavaju beskonačan dokument
Ponavljanje zaglavlja tablice preko stranica zvuči jednostavno, a skriva dva načina otkazivanja. HotPDF zahtijeva da se redovi zaglavlja pojavljuju samo u prvom nizu uzastopnih redaka i da prvo dijeljenje stane sve retke zaglavlja plus barem jedan redak tijela. Bez ovog drugog pravila, zaglavlje više od preostalog prostora proizvodi stranicu koja ne sadrži ništa osim zaglavlja, praćenu drugom identičnom stranicom, zauvijek
Stranice nastavka ponovno crtaju zaglavlje, a ta ponovno nacrtana kopija označena je kao artefakt, a ne kao sadržaj, što je ispravan odgovor i za pristupačnost i za izdvajanje teksta. Izvorni redak zaglavlja ostaje u logičnoj strukturi tablice točno jednom. Preskočite ovo i čitač zaslona ponovno najavljuje nazive stupaca usred podataka, a alat za izdvajanje teksta umeće dvostruki redak zaglavlja između redaka tijela
Postoji i obrambeni gornji limit na dubinu nastavaka, jer prilagođena komponenta može implementirati Split na način koji uvijek vraća ekvivalentni rep. Renderer provjerava ograničenje nakon odvajanja repa, a prije početka sljedeće stranice, a trenutna iteracija oslobađa rep u vlastitom bloku finally, tako da komponenta treće strane koja se loše ponaša zakaže s dijagnosticiranom greškom umjesto da napuni disk
Jedan logički element, mnogo fragmenata stranica
Automatsko označavanje mjesto je gdje se model paginacije i model strukture moraju usuglasiti. Odlomak razdijeljen preko dvije stranice jedan je logički odlomak, pa mora ostati jedan strukturni element. No identifikatori označenog sadržaja vrijede po stranici, pa svaki vidljivi fragment treba vlastiti MCID na stranici na kojoj se pojavljuje
HotPDF ovo rješava zadržavanjem jedinstvenog strukturnog elementa i dodavanjem reference na označeni sadržaj u njegovo polje /K za svaki fragment, pri čemu par /Pg i /MCID identificira stranicu i identifikator. Mjesto ParentTree za taj MCID pokazuje natrag na isti element. Upravo to zahtijeva ISO 14289, i to je razlog zašto se klonovi nastavka razlikuju od običnih klonova: obični Clone znači novi logički sadržaj i dobiva novi semantički identitet, dok interni klon nastavka nasljeđuje identitet komponente koju nastavlja
Ponovna uporaba elementa traži se kroz indeks semantičkih identiteta poredanih prema pokazivaču komponente i pretražuje binarnom usporedbom, čime pretraga ostaje logaritamska na velikim stablima. Indeks sadrži samo reference bez vlasništva; životni vijek samih strukturnih objekata ostaje vezan uz graf PDF objekata
Pravila strukture koja renderer provodi unaprijed
Kada je GenerateStructure uključen, nekoliko pravila PDF/UA provjerava se dok se stablo renderira, a ne nakon što datoteka već postoji. Naslovi počinju na razini 1 i ne smiju preskakati razine. LI se smije pojaviti samo unutar L, a Lbl i LBody samo unutar LI. TR pripada tablici, a TH i TD retku. Slika bez alternativnog teksta odbacuje se u PDF/UA načinu rada
Rano odbacivanje ovdje je namjerni izbor. Validator koji nakon što je dokument napisan prijavi nedostajući alternativni tekst govori vam da je paket od deset tisuća izvoda potrebno ponovno generirati; renderer koji odbije komponentu govori vam koju komponentu, dok su podaci koji su je proizveli još u dosegu. Provjera usklađenosti i dalje pripada pipelineu kao zaseban korak, a mehanika toga opisana je u provjeri usklađenosti PDF/A, PDF/X i 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;
Zapis statistike korisniji je nego što se čini na prvi pogled. Naglo povećanje SplitCount nakon promjene predloška obično znači da je komponenta počela mjeriti veću visinu od svog spremnika. Postupno rastuća vrijednost MaximumContinuationDepth rano je upozorenje za komponentu čiji Split ostvaruje premalo napretka po stranici. A usporedba ArtifactCount s brojem stranica nastavka potvrđuje da su ponovljena zaglavlja doista označena kao artefakti
Kako se DOM uklapa uz izravni API
DOM ne zamjenjuje izravno crtanje; sjedi na vrhu istih objekata stranice. Sve što renderer postavi može se isprepletati s izravnim pozivima na THotPDF, što je važno kada izvještaj treba jedan ručno pozicioniran element, poput slike potpisa na točnoj lokaciji. Zatvaranje stranice ostaje pod kontrolom AddPage i EndDoc, tako da način neposrednog pražnjenja ne drži dovršene stranice u memoriji, a rezidentna memorija ostaje pod kontrolom trenutnih nastavaka, resursa fontova i uobičajenog grafa objekata dokumenta
Odaberite DOM kada je sadržaj vođen podacima, a raspored pravilima, a izravno crtanje zadržite za fiksnu grafiku. Ako je vaš trenutni problem konkretno paginacija tablica, isplati se prvo pročitati uži pristup u generiranju tablica u PDF-u, a ponašanje na razini teksta poput poravnanja opisano je u poravnanju teksta
Deklarativni raspored, automatsko označavanje i izravni API za crtanje isporučuju se u istoj komponenti za Delphi i C++Builder; cjelokupni popis značajki nalazi se na stranici HotPDF Delphi PDF komponente