HotPDF dokáže sestavit stránkovaný dokument z deklarativního stromu namísto ze souřadnic. Sestavíte THPDFDOMDocument ze sekcí, zásobníků, textu, seznamů a tabulek, předáte jej THPDFDOMRenderer a vykreslovač změří, rozstránkuje, nakreslí vybavení stránky a na požádání vytvoří strukturální strom PDF/UA, který výsledek zpřístupní. Kód rozvržení nikdy nepočítá souřadnici y
Každý, kdo udržoval generátor sestav řízený souřadnicemi, ví, proč na tom záleží. První verze funguje. Pak adresa zákazníka naroste na tři řádky, tabulka přibere řádky, lokalizovaný nadpis se zalomí a každá následující souřadnice y je špatně. Opravy se hromadí jako ruční kontroly zalomení stránky roztroušené po obchodní logice, a požadavek na značkované PDF, který přijde o dva roky později, nelze dodatečně zabudovat do kódu, který nemá ponětí, co je odstavec
Co strom vlastní a proč je vlastnictví přísné
DOM na každé úrovni vynucuje jediné vlastnictví: dokument vlastní své sekce, sekce vlastní své tělo, hlavičku a patičku a zásobníky, kontejnery a tabulky vlastní své potomky. Opakované použití probíhá přes Clone nebo přes registrovanou továrnu, nikdy připojením stejného objektu ke dvěma rodičům. Toto pravidlo není formalita. Komponenta, která se ve stromu objeví dvakrát, by byla dvakrát změřena s odlišnými omezeními a při úklidu dvakrát uvolněna
Praktickým důsledkem pro volající kód je, že pomocné funkce vrací nové instance. Registrace továrny přes RegisterComponent a volání CreateComponent vám dá pojmenovaný recept, který pokaždé vyprodukuje čerstvou komponentu, a tak do stromu patří opakované vybavení, jako je podpisový blok nebo právní patička
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // vydat strukturální strom PDF/UA
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // A4 v bodech
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); // váhy, ne absolutní šířky
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // hlavičkový řádek
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;
Jak se stránkování vyhýbá kvadratickým nákladům?
Naivní způsob stránkování stromu je naklonovat vše, co se nevešlo, a přenést to na další stránku. U tabulky s deseti tisíci řádky to naklonuje zbývající řádky pokaždé znovu na každé stránce a promění lineární dokument v kvadratický
HotPDF místo toho dělí úzce. Vykreslovač nejvyšší úrovně prochází potomky těla podle indexu a nikdy neklonuje celou sekci nebo tělo. Naklonuje se pouze zasažený podstrom vnořených zásobníků a kontejnerů, které skutečně přesahují hranici stránky, a dva náročné listové typy nesou kurzor místo kopie: pokračování textu ukládá zdrojový rozsah znaků, který ještě dluží, a pokračování tabulky ukládá výřez řádků, které ještě čekají na umístění. Dlouhé dokumenty zůstávají lineární a dlouhé odstavce stojí stejně, ať se zalomí jednou nebo pětkrát
Měření zůstává upřímné ohledně vedlejších účinků. THPDFLayoutElement.Measure musí být bez vedlejších účinků kreslení a skutečné umístění vždy prochází přes THotPDF.PlaceLayoutElement, stejnou centrální rutinu, která umístěný fragment znovu změří, nastaví vlastnictví přetečení a zaznamená diagnostiku. Vykreslovač DOM rozhoduje pouze o politice nové stránky, vybavení stránky, rozestupech a životnosti pokračování
Pravidla hlaviček tabulek, která zabraňují nekonečnému dokumentu
Opakování hlaviček tabulky napříč stránkami zní jednoduše, ale skrývá dva režimy selhání. HotPDF vyžaduje, aby se hlavičkové řádky objevovaly pouze v prvním souvislém běhu řádků a aby se do prvního rozdělení vešly všechny hlavičkové řádky plus alespoň jeden řádek těla. Bez druhého pravidla vytvoří hlavička vyšší, než je zbývající prostor, stránku obsahující jen hlavičku, po níž následuje další identická stránka, donekonečna
Navazující stránky hlavičku překreslí a tato překreslená kopie je označena jako artefakt, nikoli jako obsah, což je správná odpověď jak pro přístupnost, tak pro extrakci textu. Původní hlavičkový řádek zůstává v logické struktuře tabulky přesně jednou. Vynechte toto a čtečka obrazovky ohlásí názvy sloupců znovu uprostřed dat a extraktor textu vloží duplicitní hlavičkový řádek mezi řádky těla
Existuje také ochranný strop na hloubku pokračování, protože vlastní komponenta může implementovat Split způsobem, který vždy vrátí ekvivalentní zbytek. Vykreslovač kontroluje limit po odpojení zbytku a před zahájením další stránky a aktuální iterace uvolní zbytek ve vlastním bloku finally, takže nesprávně se chovající komponenta třetí strany selže s diagnostikovatelnou chybou místo zaplnění disku
Jeden logický prvek, mnoho fragmentů stránky
Automatické značkování je místem, kde se model stránkování a model struktury musí shodnout. Odstavec rozdělený na dvě stránky je jeden logický odstavec, takže musí zůstat jedním strukturálním prvkem. Ale identifikátory značkovaného obsahu jsou vázané na stránku, takže každý viditelný fragment potřebuje vlastní MCID na stránce, na které se objevuje
HotPDF to řeší tak, že udržuje jediný strukturální prvek a připojuje odkaz na značkovaný obsah do jeho pole /K pro každý fragment, přičemž dvojice /Pg a /MCID identifikuje stránku a identifikátor. Slot ParentTree pro daný MCID ukazuje zpět na stejný prvek. Přesně toto očekává ISO 14289, a proto se klony pokračování liší od běžných klonů: běžný Clone znamená nový logický obsah a dostane novou sémantickou identitu, zatímco interní klon pokračování dědí identitu komponenty, kterou pokračuje
Opětovné použití prvků se vyhledává přes index sémantických identit seřazený podle ukazatele komponenty a prohledávaný binárním porovnáním, což udržuje vyhledávání logaritmické i u rozsáhlých stromů. Index obsahuje pouze nevlastnící reference; životnost samotných strukturálních objektů zůstává vázaná na graf objektů PDF
Pravidla struktury, která vykreslovač vynucuje předem
Se zapnutým GenerateStructure se několik pravidel PDF/UA kontroluje během vykreslování stromu, ne až po vzniku souboru. Nadpisy začínají na úrovni 1 a nesmí přeskakovat úrovně. LI se smí objevit pouze uvnitř L a Lbl a LBody pouze uvnitř LI. TR patří tabulce a TH a TD patří řádku. Obrázek bez alternativního textu je v režimu PDF/UA odmítnut
Odmítnutí co nejdříve je zde záměrná volba. Validátor, který nahlásí chybějící alternativní text až po zapsání dokumentu, vám řekne, že je potřeba přegenerovat dávku deseti tisíc výpisů; vykreslovač, který komponentu odmítne, vám řekne kterou, zatímco data, z nichž vznikla, jsou stále v dosahu. Ověřování shody stále patří do pipeline jako samostatný krok a jeho mechanika je popsána v článku ověřování PDF/A, PDF/X a 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;
Statistický záznam je užitečnější, než se na první pohled zdá. Prudký nárůst SplitCount po změně šablony obvykle znamená, že komponenta se začala měřit vyšší, než je její kontejner. Postupně rostoucí MaximumContinuationDepth je včasné varování před komponentou, jejíž Split dělá na stránku příliš malý pokrok. A porovnání ArtifactCount s počtem navazujících stránek potvrzuje, že opakované hlavičky byly skutečně označeny jako artefakty
Kam DOM zapadá vedle přímého API
DOM nenahrazuje přímé kreslení; sedí na stejných objektech stránky. Cokoli, co vykreslovač umístí, lze prokládat přímými voláními na THotPDF, což je důležité, když sestava potřebuje jeden ručně umístěný prvek, například obrázek podpisu na přesné poloze. Uzavírání stránek zůstává pod kontrolou AddPage a EndDoc, takže režim okamžitého vyprázdnění nedrží v paměti žádné dokončené stránky a rezidentní paměť zůstává řízena aktuálními pokračováními, prostředky fontů a běžným grafem objektů dokumentu
Zvolte DOM, když je obsah řízen daty a rozvržení pravidly, a ponechte přímé kreslení pro pevnou grafiku. Pokud je vaší aktuální bolestí konkrétně stránkování tabulek, stojí za přečtení nejprve užší přístup v článku generování tabulek v PDF, a chování na úrovni textu, jako je zarovnání do bloku, popisuje článek zarovnání textu do bloku
Deklarativní rozvržení, automatické značkování a přímé kreslicí API jsou součástí stejné komponenty pro Delphi a C++Builder; kompletní seznam funkcí je na stránce komponenty HotPDF PDF pro Delphi