Tehnični članak

Mreže rowspan in ponavljanje glav tabel v HotPDF

HotPDF izrisuje HTML tabele skozi svoj profil HTML5 za medij s stranež (paged media) z pravo mrežo zasedenosti za rowspan in colspan, z izmerjenimi višinami vrstic namesto ocen po štetju znakov in z glavnimi vrsticami, ponovljenimi na vsaki nadaljevalni strani. Dve situaciji povzročita, da ponavljanja glave odkloni, poznavanje le-teh vnaprej pa je cenejše kot razhroščevanje podvojenih celic kasneje

Razred dokumentov, ki to vsilijo, je tisti, ki ga vsaka poročilska ekipa sčasoma izda: račun ali skladnostno poročilo, kjer je vir resnice HTML, tabela teče čez štiri strani in glava mora biti berljiva na vsaki od njih. Karkoli manj kot prava postavitev tabele da dve napaki, ki ju bralci opazijo takoj — glavo, ki se pojavi le enkrat na prvi strani, in vrstice, katerih višine so bile uganjene iz štetja znakov

Zakaj se je zmožnost tabel preselila v izrisovalnik HTML?

Ker alternativa izgubi bogato besedilo, bogato besedilo pa je razlog, da je vsebina sploh HTML. Očitni načrt izgleda kot ponovna uporaba: HotPDF že ima objekt tabele v postavitvenem DOM-u z pravo mrežo, zato priključite razčlenjevalnik HTML nanj in razpone dobite zastonj. Težava je v tem, s čimer ta objekt tabele riše. Njegove celice nosijo besedilo in slog, njegova risalna pot pa oddaja izhod golega besedila, zato je vse, kar je HTML dejansko vseboval onkraj pisave in barve — povezave, nadpisano besedilo, spremembe velikosti v vrsti, barve po delih —, do trenutka, ko pride na stran, izgubljeno

Smer, ki preživi stik z resničnimi dokumenti, je obratna. Premaknite zmožnosti tabelnega motorja — mrežo zasedenosti, pravo merjenje, ponavljanje glav in uteženje stolpcev — v izrisovalnik HTML in bogato besedilo pustite tam, kjer že deluje. To je večja sprememba kot most in je sprememba, ki hiperpovezavo znotraj celice tabele obdrži hiperpovezavo

Rowspan brez union-find

Celice z razponom ustvarjajo atomske skupine vrstic, a zaprtje nad temi skupinami ne potrebuje splošne strukture disjunktnih množic (union-find), ker je zasedenost vedno zvezni interval. Celica s rowspan="3", ki začne v vrstici K, zasede vrstice K do K+2 in nič drugega, tako da se informacija o skupini zreducira na končni označevalnik na vrstico

Algoritem sta dve vrstici namere. Ko postavite celico z razponom, ki se začne pri K in konča pri E, zabeležite GroupEnd[K] := Max(GroupEnd[K], E). Nato enkrat prehodite vrstice v obratni smeri in uporabite G[R] := G[G[R]], kar vsak konec vrstice širi nazaj skozi prekrivajoče se razpone in da tranzitivno zaprtje v enem prehodu. Kar dobite, je za vsako vrstico zadnja vrstica, ki mora ostati na isti strani z njo — natanko to, kar korak paginacije potrebuje, da odloči, kje sme prelom pasti

Razporejanje višine je druga polovica. Ko celica z razponom potrebuje več navpičnega prostora, kot ga vrstice, ki jih pokriva, trenutno dajo, odvek gre zadnji vrstici razpona in se ne razporedi enakomerno čeznje. Celice z razponi obdelajte šele, ko so višine navadnih vrstic urejene, nato dolijte zadnjo vrstico vsakega razpona. Enakomerno razporejanje odveka se zdi pravičnejše in da vidno napačen izhod: vrstice, ki vsebujejo le kratke enovrstične celice, se napihnejo, ker se je kaka nepovezana celica tri vrstice višje zgodila biti visoka

Mreža HTML tabele HotPDF, kjer ena celica z rowspan 3, ki začne v vrstici 2, zasede vrstice 2 do 4 kot en sam atomski pravokotnik, poleg vrednosti koncev skupin G[R] na vrstico, nastalih z enim obratnim prehodom, ki prikazuje vrstice 2, 3 in 4 vezane na isto stran
Zasedenost razpona je vedno zvezni interval, zato označevalci koncev na vrstico in en obratni prehod nadomestita union-find in paginaciji povesta točno, kje sme prelom pasti
var
  Pdf: THotPDF;
  Importer: THPDFHTMLImporter;
  Stats: THPDFHTMLImportStatistics;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'audit-report.pdf';
    Pdf.BeginDoc;
    Importer := THPDFHTMLImporter.Create(Pdf);
    try
      Importer.Margin := 48;
      Importer.BaseFontName := 'Arial';
      Importer.BaseFontSize := 10;
      Importer.MaxDOMNodes := 200000;
      Importer.MaxLayoutOperations := 2000000;
      if Importer.RenderHTML5(SourceHtml, PrintStyleSheet) then
      begin
        Stats := Importer.Statistics;
        Writeln('tables ', Stats.TableCount,
                '  page breaks ', Stats.PageBreakCount);
      end;
    finally
      Importer.Free;
    end;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

RenderHTML5 kot drugi argument sprejme neobvezen avtorski stilni list, tam pa je pravo mesto za tiskalna pravila. Zaslonskega stilnega lista vanj ne dajajte. Profil je verzioniran in HTML5ProfileMilestones sporoča, katere skupine zmožnosti trenutna gradnja izvaja — himParserCascade, himPagedLayout, himTablesForms in himBoundedResources —, zato se lahko aplikacija zavestno odzove z upadom zmogljivosti namesto da vrzel odkrije šele v produkciji

Merjenje se mora z risanjem ujemati, točno

Višina vrstice je pravilna le, ko koda, ki meri prelomljene vrstice, prelomi po istem pravilu kot koda, ki jih riše. To zveni očitno in je najpogostejši vir tabel, katerih robovi se ne ujemajo z vsebino. HotPDF meri s požrešnim števcem vrstic in ta števec se mora ujemati s semantiko prelomov bogatega besedila v izhodni poti v treh točkovnih pogledih: prelomi le na presledkih, nikoli ne razdeli besede in beseda, širša od stolpca, dobi vrstico zase

Druga zahteva je pisava. Merjenje mora teči s pisavo same celice, nastavljeno skozi SetFont z dejanskim imenom, slogom in velikostjo, preden pokličete funkcijo širine — ne s katero koli pisavo, ki je slučajno bila aktivna. Krepko besedilo je pri isti velikosti rutinsko več kot deset odstotkov širše od navadnega, kar zadošča, da trivrstična celica postane štirivrstična. Tabela, kjer so glavne celice krepke, telesne pa ne, izmerjena z eno samo pisavo, bo napačna natanko v vrsticah, ki jih bralci pogledajo prve

Ko je to urejeno, se spremeni, kaj lahko v testu trdite. Opazljivi učinek natančnega merjenja je razmik vrstic, ne število glifov: enovrstična vrstica je visoka približno 20 točk, ocena po štetju znakov iste vsebine pa napove dve vrstici in približno 35. Trditev postavite na navpično razdaljo med vrsticami. In pomnite, da ima uporabniški prostor PDF Y naraščajoč navzgor, zato glava, ki sede nad telesno vrstico, pomeni, da je vrednost Y glave večja — nasprotno od tega, kar piše instinkt zaslonskih koordinat

Kdaj HotPDF odkloni ponovitev glave?

V dveh primerih, ki bi oba dala vidno napačen izhod, če bi šla naprej. Prvi je blok glave, ki vsebuje celico z razponom, ki sega čez glavo v telesne vrstice. Ponovitev glave bi to vsebino celice izrisala drugič na mestu, kjer že ne sodi več, zato se glava izriše enkrat in tabela nadaljuje brez nje. Drugi je glava, višja od 90 odstotkov uporabne višine strani, kjer bi ponovitev pustila skoraj nič prostora za podatke in tabela ne bi naredila nobenega napredka

Odločitveni tok HotPDF za ponavljanje glav HTML tabel čez prelome strani: glava, katere rowspan seže v telesne vrstice, se izriše enkrat, glava, višja od 90 odstotkov uporabne višine strani, se izriše enkrat, vsaka druga glava pa se ponovi na vsaki nadaljevalni strani
Dve zavrnitvi sta namerni: ponovitev glave, ki lasti telesno celico z razponom ali zapolnjuje večino strani, bi izrisala vsebino, kjer že ne sodi, ali pustila nič prostora za podatke

Obe zavrnitvi sta po zasnovi namerni in tihi, ker je alternativa slabša. Če se vaša glava ne ponovi, čeprav ste pričakovali, da se bo, preverite označbo za rowspan, ki prečka mejo thead, preden podvomite v motor. Ta en sam vzorec označbe pojasni večino presenečenj

// Uteži stolpcev prihajajo iz označbe, zato je tiskalni stilni list
// mesto, kjer jih nadzorujete. Širine so uteži, ne piksli
const
  PrintStyleSheet =
    'table { width: 100%; }' +
    'thead th { font-weight: bold; background: #eee; }' +
    'td.amount { text-align: right; }';

// Glavna vrstica z rowspanom, ki seže prek telesa, zatira
// ponavljanje glave. Razpone hranite znotraj enega razdelka:
//   <thead><tr><th rowspan="2">Item</th>...</tr></thead>  v redu
//   <tr><th rowspan="3">Item</th>...  sega v tbody, brez ponavljanja

Širine stolpcev delujejo kot uteži in ne kot absolutne meritve — to je vedenje, ki tabelo ohranja uporabno, ko vsebina ne ustreza avtorjevi oceni. Stolpec, deklariran na 30 odstotkov, dobi približno 30 odstotkov razpoložljive širine, a razporeditev spoštuje najmanjšo širino, ki jo vsak stolpec dejansko potrebuje, zato ozek stolpec z dolgim nerazdeljivim žetonom ne prelije okvirja tabele tiho

Kje je to v dokumentnem cevovodu

Delo tabel sede znotraj širšega profila za medij s stranež in pravila paginacije, proračuni virov ter ravnanje s CSS, opisani v uvozni poti HTML5 paged-media, veljajo nespremenjeno tudi za dokumente, ki vsebujejo tabele. Če vaši podatki ne začnejo kot HTML, se neposredna pot gradnje v gradnji tabel naravnost v PDF popolnoma izogne razčlenjevalski plasti in da isto mrežno vedenje skozi API. Ker višina vrstice na koncu visi od tega, kje se vrstice prelomijo, je razprava o merjenju v ožjanju besedila in prelomu vrstic soprovod za vsakogar, ki uglašuje gosto tabelarično izhodišče

Ponovno uporaben pouk tu sploh ni o tabelah. Ko nov podsistem potrebuje zmožnost, ki je stari podsistem že ima, vprašajte, kateri od obeh lasti stvar, ki jo je najtežje znova implementirati. Mrežna aritmetika je peščica vrstic in se lahkotno preseli. Izris bogatega besedila s povezavami v vrsti, nadpisanim besedilom in stili po delih pa ni, zato se je mreža preselila in besedilo ostalo. HotPDF pošilja obe poti kot del HotPDF Delphi PDF component, tako da je izbira med vhodom HTML in neposredno gradnjo projektna odločitev in ne odločitev knjižnice