HotPDF lahko paginiran dokument zgradi iz deklarativnega drevesa namesto iz koordinat. THPDFDOMDocument sestavite iz odsekov, skladov, besedila, seznamov in tabel, ga predate THPDFDOMRenderer, upodabljalnik pa izmeri, paginira, nariše opremo strani in, kadar je zahtevano, izpiše strukturno drevo PDF/UA, ki naredi rezultat dostopen. Koda postavitve nikoli ne izračuna koordinate y
Vsak, kdor je vzdrževal generator poročil, voden s koordinatami, ve, zakaj je to pomembno. Prva različica deluje. Nato naslov stranke naraste na tri vrstice, tabela dobi vrstice, lokaliziran naslov se prelomi, in vsak nadaljnji položaj y je napačen. Popravki se kopičijo kot ročne preverbe preloma strani, raztresene po poslovni logiki, zahteva po označenem PDF, ki pride dve leti pozneje, pa se ne da naknadno vgraditi v kodo, ki nima pojma, kaj je odstavek
Kaj drevo lasti, in zakaj je lastništvo strogo
DOM na vsaki ravni uveljavlja enojno lastništvo: dokument lasti svoje odseke, odsek lasti svoje telo, glavo in nogo, skladi, vsebniki in tabele pa lastijo svoje podrejene elemente. Ponovna uporaba poteka prek Clone ali prek registrirane tovarne, nikoli tako, da bi isti objekt pripeli na dva starša. To pravilo ni zgolj formalnost. Komponenta, ki bi se v drevesu pojavila dvakrat, bi bila izmerjena dvakrat z različnimi omejitvami in ob razgradnji sproščena dvakrat
Praktična posledica za klicno kodo je, da pomožne funkcije vračajo nove instance. Registracija tovarne z RegisterComponent in klic CreateComponent vam da poimenovan recept, ki vsakič ustvari svežo komponento, kar je način, kako ponavljajoča se oprema, kot je blok za podpis ali pravna noga strani, sodi v drevo
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // izpiši strukturno drevo PDF/UA
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // A4 v pikah
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); // uteži, ne absolutne širine
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // vrstica glave
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 preprečuje kvadratni strošek?
Naiven način paginacije drevesa je kloniranje vsega, kar se ni prilegalo, in prenos na naslednjo stran. Pri tabeli z deset tisoč vrsticami to na vsaki strani klonira preostale vrstice in linearen dokument spremeni v kvadratnega
HotPDF namesto tega deli ozko. Upodabljalnik na najvišji ravni podrejene elemente telesa obiskuje po indeksu in nikoli ne klonira celotnega odseka ali telesa. Klonirano je prizadeto poddrevo samo pri gnezdenih skladih in vsebnikih, ki dejansko segajo čez mejo strani, dva težka listna tipa pa namesto kopije nosita kazalec: nadaljevanje besedila shrani izvorni razpon znakov, ki ga še dolguje, nadaljevanje tabele pa shrani rezino vrstic, ki jih še mora postaviti. Dolgi dokumenti ostanejo linearni, dolgi odstavki pa stanejo enako, ne glede na to, ali se prelomijo enkrat ali petkrat
Merjenje ostaja pošteno glede stranskih učinkov. THPDFLayoutElement.Measure mora biti brez stranskih učinkov risanja, dejanska postavitev pa vedno poteka prek THotPDF.PlaceLayoutElement, iste osrednje rutine, ki ponovno izmeri postavljeni fragment, vzpostavi lastništvo prekoračitve in beleži diagnostiko. Upodabljalnik DOM odloča samo o politiki nove strani, opremi strani, razmiku in življenjski dobi nadaljevanj
Pravila glave tabele, ki preprečujejo neskončen dokument
Ponavljanje glav tabel čez strani zveni preprosto, a skriva dva načina odpovedi. HotPDF zahteva, da se vrstice glave pojavijo samo v prvem zaporednem sklopu vrstic in da se ob prvi delitvi prilega vsem vrsticam glave plus vsaj eni vrstici telesa. Brez drugega pravila glava, ki je višja od preostalega prostora, ustvari stran, ki ne vsebuje ničesar razen glave, ki ji sledi še ena enaka stran, v nedogled
Nadaljevalne strani glavo ponovno narišejo, ta ponovno narisana kopija pa je označena kot artefakt in ne kot vsebina, kar je pravilen odgovor tako za dostopnost kot za izvlečenje besedila. Izvirna vrstica glave ostane v logični strukturi tabele natanko enkrat. Če to izpustite, bralnik zaslona naslove stolpcev ponovno napove sredi podatkov, izvlekalnik besedila pa med vrstice telesa vstavi podvojeno vrstico glave
Obstaja tudi obrambna zgornja meja globine nadaljevanj, ker lahko komponenta po meri Split implementira tako, da vedno vrne enakovreden rep. Upodabljalnik mejo preveri po odcepitvi repa in pred začetkom naslednje strani, trenutna iteracija pa rep sprosti v svojem lastnem bloku finally, tako da komponenta tretje strani, ki se slabo obnaša, spodleti z diagnosticirljivo napako namesto da zapolni disk
En logični element, veliko fragmentov strani
Samodejno označevanje je mesto, kjer se morata model paginacije in strukturni model ujemati. Odstavek, razdeljen na dve strani, je en logični odstavek, zato mora ostati en strukturni element. Vendar so identifikatorji označene vsebine vezani na stran, zato vsak vidni fragment potrebuje svoj lasten MCID na strani, na kateri se pojavi
HotPDF to razreši tako, da ohrani en sam strukturni element in za vsak fragment v njegovo polje /K doda sklic na označeno vsebino, pri čemer par /Pg in /MCID identificira stran in identifikator. Mesto v ParentTree za ta MCID kaže nazaj na isti element. Točno to zahteva ISO 14289, in prav zato so nadaljevalni kloni drugačni od navadnih klonov: navaden Clone pomeni novo logično vsebino in dobi novo semantično identiteto, medtem ko interni nadaljevalni klon podeduje identiteto komponente, ki jo nadaljuje
Ponovna uporaba elementa se poišče prek indeksa semantičnih identitet, razvrščenega po kazalcu komponente in preiskovanega z binarno primerjavo, kar iskanje na velikih drevesih ohranja logaritemsko. Indeks vsebuje samo sklice brez lastništva; življenjska doba samih strukturnih objektov ostaja pri grafu objektov PDF
Strukturna pravila, ki jih upodabljalnik uveljavi vnaprej
Ko je GenerateStructure omogočen, se več pravil PDF/UA preveri med upodabljanjem drevesa in ne šele po tem, ko datoteka že obstaja. Naslovi se začnejo na ravni 1 in ne smejo preskočiti ravni. LI se lahko pojavi samo znotraj L, Lbl in LBody pa samo znotraj LI. TR pripada tabeli, TH in TD pa vrstici. Slika brez nadomestnega besedila je v načinu PDF/UA zavrnjena
Zgodnja zavrnitev je tu namerna izbira. Validator, ki manjkajoče nadomestno besedilo javi šele po tem, ko je dokument zapisan, vam pove, da je treba ponovno ustvariti paket desettisoč izpiskov; upodabljalnik, ki zavrne komponento, pa vam pove, katero komponento, medtem ko so podatki, ki so jo ustvarili, še vedno na dosegu. Preverjanje skladnosti še vedno sodi v cevovod kot ločen korak, mehanika tega pa je opisana v preverjanju skladnosti PDF/A, PDF/X in 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;
Statistični zapis je uporabnejši, kot je videti na prvi pogled. Če SplitCount po spremembi predloge strmo naraste, to običajno pomeni, da je komponenta začela meriti višje od svojega vsebnika. Postopno naraščanje MaximumContinuationDepth je zgodnje opozorilo za komponento, katere Split na stran naredi premalo napredka. Primerjava ArtifactCount s številom nadaljevalnih strani pa potrdi, da so bile ponovljene glave res označene kot artefakti
Kam DOM sodi ob neposrednem API
DOM ne nadomesti neposrednega risanja; sedi na istih objektih strani. Karkoli upodabljalnik postavi, se lahko prepleta z neposrednimi klici na THotPDF, kar je pomembno, kadar poročilo potrebuje en ročno postavljen element, na primer sliko podpisa na natančnem mestu. Zapiranje strani ostaja pod nadzorom AddPage in EndDoc, zato način takojšnjega praznjenja v pomnilniku ne hrani nobenih dokončanih strani, rezidenčni pomnilnik pa še naprej upravljajo trenutna nadaljevanja, viri pisav in običajen graf objektov dokumenta
DOM izberite, kadar je vsebina vodena s podatki, postavitev pa s pravili, neposredno risanje pa obdržite za fiksno grafiko. Če je vaša trenutna težava specifično paginacija tabel, je vredno najprej prebrati ožji pristop v ustvarjanju tabel v PDF, vedenje na ravni besedila, kot je poravnava, pa je opisano v poravnavi besedila
Deklarativna postavitev, samodejno označevanje in API za neposredno risanje so na voljo v isti komponenti za Delphi in C++Builder; celoten seznam funkcij je na strani komponente HotPDF PDF za Delphi