PDFium Component z uporabo BuildReflowDocument PDF s fiksno postavitvijo spremeni v semantični model, ki ga je mogoče preoblikovati, ta model pa prek ToHtml izvozi kot samostojen HTML. Naslovi ostanejo naslovi, elementi seznama ostanejo elementi seznama, tabele, zaznane na strani, pa pridejo ven kot prava tabelarna oznaka z ohranjenimi celicami glave in razponi. Nič v izhodu se ne sklicuje na zunanjo skripto ali slogovno datoteko
Razlog za to željo je, da je stran PDF nabor postavljenih znakov, kar je natanko narobe za zaslon telefona, bralnik zaslona ali iskalni indeks. Vsak poskus rešitve z izvlečenjem golega besedila izgubi strukturo, ki je dokument naredila berljiv, vsak poskus rešitve s pretvorbo strani v slike pa besedilo v celoti izgubi. Model za preoblikovanje ohrani oboje: besede in odnose med njimi
Od kod prihajajo semantične informacije?
Vse se začne pri GetStructuredText, edinem viru besedila in semantike v komponenti. Kadar PDF nosi strukturno drevo, torej označen PDF, kot je definiran v členu 14.7 ISO 32000-1, model sledi logični hierarhiji, ki jo je zabeležil izdelovalec. Kadar je ne nosi, in večina PDF-jev v naravi je ne, se model prevesi na fizični vrstni red postavitve, ki je že izračunan za namene vrstnega reda branja
Ta izbira ohranja trdno mejo: za odgovarjanje na vprašanja, na katera lahko odgovori že obstoječi razčlenjevalnik PDF in upodabljalni mehanizem, se ne uvaja noben drugi. Spodaj ležeča mehanika vrstnega reda branja je opisana v strukturiranih besedilnih blokih in vrstnem redu branja, model za preoblikovanje pa je semantična plast na njej in ne njena zamenjava
Vsako vozlišče zabeleži, od kod prihajajo njegove informacije, tako da lahko porabnik razlikuje naslov, ki ga je deklariral dokument, od naslova, ki so ga sklepale hevristike postavitve. Cevovodi, občutljivi na zaupanje, naj berejo to polje, namesto da bi vsa vozlišča obravnavali kot enako verodostojna
Ploščato drevo, in zakaj ni drevo objektov
Model je sploščeno drevo v vrstnem redu pred-obiska: polje vozlišč, kjer vsako vozlišče nosi ParentIndex in Depth, namesto rekurzivnega zapisa ali grafa objektov z lastništvom. Strani, naslovi, odstavki, seznami, elementi seznama, slike, napisi, tabele, vrstice in celice — vsi živijo v tem enem samem linearnem polju
Iz tega izhajata dve prednosti. Porabniki lahko polje pretakajo po vrstnem redu brez rekurzije, kar izpis HTML, Markdown ali pogleda drevesa naredi preprosto zanko. Postavitev pa ostaja prenosljiva med Delphijem, C++Builderjem in Free Pascalom, ki se razlikujejo v tem, kako obravnavajo rekurzivne upravljane tipe čez mejo ABI. Rekurziven zapis dinamičnih polj je natanko vrsta konstrukta, ki se povsod prevede, a se v vsakem okolju obnaša rahlo drugače
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfReflowOptions;
Doc: TPdfReflowDocument;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.LoadDocument;
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.DetectTables := True;
Options.IncludeCss := True; // vgrajen blok sloga, brez zunanje datoteke
Options.MaxNodes := 200000; // proračun, ki ob preseganju zapre izvajanje
Options.MaxCharacters := 4000000;
Doc := Pdf.BuildReflowDocument(Options);
for I := 0 to High(Doc.Nodes) do
case Doc.Nodes[I].Kind of
prnkHeading:
Writeln(Format('%sH%d: %s', [StringOfChar(' ', Doc.Nodes[I].Depth),
Doc.Nodes[I].HeadingLevel, Doc.Nodes[I].Text]));
prnkParagraph:
Writeln(Format('%sp: %s', [StringOfChar(' ', Doc.Nodes[I].Depth),
Copy(Doc.Nodes[I].Text, 1, 60)]));
prnkTable:
Writeln(Format('table on page %d', [Doc.Nodes[I].PageNumber]));
end;
Writeln(Format('%d node(s), %d table(s), %d character(s)',
[Length(Doc.Nodes), Doc.TableCount, Doc.CharacterCount]));
finally
Pdf.Free;
end;
end;
Kako se prepreči, da bi se tabele pojavile dvakrat?
Zaznavanje tabel se izvede po tem, ko je strukturirano besedilo za stran že zbrano, kar ustvari očitno nevarnost: ista vsebina celice obstaja tako v besedilnih blokih kot v zaznani tabeli. Izpis obeh ustvari HTML, kjer vsaki tabeli sledi njena lastna vsebina še enkrat kot ohlapni odstavki
Pravilo, ki to razreši, je geometrijsko. Kadar zaznana tabela pokrije več kot polovico površine besedilnega bloka, vozlišče tabele nadomesti ta blok, namesto da bi se mu pridružilo. Indeksiranje celic znotraj vrstice je zgrajeno s štetjem v predale, tako da gradnja modela ostane linearna glede na celice plus vrstice, namesto da bi za vsako vrstico ponovno preiskala vsako celico, kar je pomembno pri finančnih dokumentih, kjer lahko ena sama stran nosi na stotine celic
Zaznana struktura je odkrita glede tega, da je zaznava. Tabela z ločilnimi črtami je prepoznana bolj zanesljivo kot taka, poravnana zgolj z belim prostorom, in zaupanje vozlišča to odraža. Za vsebino, kjer je napačna tabela boljša od nobene tabele, pustite zaznavanje vklopljeno; za arhivsko pretvorbo, kjer je napačna tabela slabša, zaporite na podlagi zaupanja
Izvoz HTML, ki ostane samostojen
ToHtml prehodi že zgrajen model in se nikoli ne vrne k PDFium, zato dvojni izvoz ne stane nič dodatnega in iz istega modela ne more dati drugačnega rezultata. Vrednosti besedila in atributov so pobegnjene enotno, ravni naslovov so omejene na razpon h1 do h6, ki ga HTML dejansko definira, celice glave, RowSpan in ColumnSpan pa gredo skozi tako, kot so zapisane
Izbirni CSS je preprost vgrajen blok sloga. Ni nobene skripte, spletne pisave ali zunanjega vira kakršne koli vrste, kar naredi izhod varen za vdelavo v e-pošto, pregledovalnik pomoči ali peskovniško nadzorno komponento brskalnika:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // ohrani meje strani vidne
Options.PreserveLineBreaks := False; // prepusti brskalniku prelom odstavkov
Html := Pdf.BuildReflowDocument(Options).ToHtml;
Bytes := TEncoding.UTF8.GetBytes(string(Html));
Stream := TFileStream.Create('report.html', fmCreate);
try
if Length(Bytes) > 0 then
Stream.WriteBuffer(Bytes[0], Length(Bytes));
finally
Stream.Free;
end;
end;
PreserveLineBreaks je možnost, o kateri se najbolj splača premisliti. Prelom vrstice v PDF je stavčna odločitev, sprejeta za fiksno širino strani, zato njena ohranitev na ozkem zaslonu ponovno ustvari prav tisto težavo, zaradi katere preoblikovanje sploh obstaja. Prelome ohranite za poezijo, izpise kode in naslove; za prozo jih opustite
Proračuni, preklic in stanje strani
Znaki, vozlišča, tabele in celice imajo vsak svojo zgornjo mejo, vsaka pa je preverjena pred dodelitvijo in ne po njej, tako da popačen ali sovražen dokument čisto spodleti, namesto da bi porabljal pomnilnik, dokler ne odpove nekaj drugega. Žeton za preklic se preveri na mejah strani, bloka, tabele, vrstice in celice, kar prekinjen pregled dokumenta s tisoč stranmi ohranja odzivnega
Eno vedenje je pomembno posebej za aplikacije z grafičnim vmesnikom: celoten pregled dokumenta teče znotraj obsega, ki obnovi aktivno stran, tako da uspeh, neuspeh proračuna in preklic vsi pustijo trenutno stran klicatelja nedotaknjeno. Pregledovalnik, ki uporabniku dovoli izvoz, medtem ko gleda stran 340, se nato znajde še vedno na strani 340
Za kaj je preoblikovanje dobro, in za kaj ni
Izhod preoblikovanja je odličen vhod za indeksiranje iskanja, dostopne bralne poglede, prikaz na mobilnih napravah in migracijo vsebine. Ni pretvornik, ki bi ohranjal zvestobo: absolutni položaji, natančne pisave, vektorska grafika in natančna geometrija strani so po zasnovi zunaj njegovega namena. Kadar opravilo zahteva, da stran izgleda enako, jo upodobite; kadar zahteva, da je stran berljiva nekje drugje, jo preoblikujte
Posebej za podporno tehnologijo se model za preoblikovanje poveže z bralnimi funkcijami, opisanimi v gradnji dostopnega bralnika, dokumenti, ki nosijo pravo strukturno drevo, pa dajo občutno boljše modele, kar je dober argument za predhodno preverjanje označevanja, kot je opisano v preverjanju strukturnega drevesa PDF/UA
Preoblikovanje, strukturirano besedilo, preverjanje označevanja in upodabljanje si delijo en objekt dokumenta v Delphiju, C++Builderju in Lazarusu; celoten API je opisan na strani PDFium Component za Delphi