PDFium Component gjør om en fastlayout-PDF til en semantisk modell som kan reflowes, ved bruk av BuildReflowDocument, og eksporterer den modellen som selvstendig HTML gjennom ToHtml. Overskrifter forblir overskrifter, listeelementer forblir listeelementer, og tabeller oppdaget på siden kommer ut som ekte tabellmarkup med overskriftsceller og spenn bevart. Ingenting i utdataen refererer til et eksternt skript eller stilark
Grunnen til å ønske dette er at en PDF-side er et sett med posisjonerte glyfer, noe som er nøyaktig feil for en telefonskjerm, en skjermleser eller en søkeindeks. Hvert forsøk på å løse det ved å hente ut ren tekst mister strukturen som gjorde dokumentet lesbart, og hvert forsøk på å løse det ved å konvertere sider til bilder mister teksten helt. En reflow-modell beholder begge deler: ordene og relasjonene mellom dem
Hvor kommer den semantiske informasjonen fra?
Alt starter fra GetStructuredText, den eneste kilden til tekst og semantikk i komponenten. Når PDF-en bærer et strukturtre, tagget PDF som definert i ISO 32000-1 punkt 14.7, følger modellen det logiske hierarkiet produsenten registrerte. Når den ikke gjør det, og de fleste PDF-er man møter i praksis gjør ikke det, faller modellen tilbake på den fysiske layout-rekkefølgen som allerede er beregnet for leserekkefølge-formål
Det valget holder en fast grense: ingen ny PDF-parser og ingen ny gjengivelsesmotor innføres for å svare på spørsmål den eksisterende allerede kan svare på. Leserekkefølge-mekanikken under er beskrevet i strukturerte tekstblokker og leserekkefølge, og reflow-modellen er et semantisk lag oppå den snarere enn en erstatning
Hver node registrerer hvor informasjonen sin kom fra, slik at en forbruker kan skille en overskrift dokumentet erklærte, fra en overskrift layout-heuristikken utledet. Konfidenssensitive pipeliner bør lese det feltet i stedet for å behandle alle noder som like autoritative
Et flatt tre, og hvorfor det ikke er et tre av objekter
Modellen er et pre-order flatet tre: et array av noder der hver node bærer en ParentIndex og en Depth, snarere enn en rekursiv post eller en objektgraf med eierskap. Sider, overskrifter, avsnitt, lister, listeelementer, figurer, bildetekster, tabeller, rader og celler bor alle i det ene lineære arrayet
To fordeler følger av det. Forbrukere kan strømme arrayet i rekkefølge uten rekursjon, noe som gjør det å produsere HTML, Markdown eller en trevisning til en enkel løkke. Og layouten forblir portabel på tvers av Delphi, C++Builder og Free Pascal, som skiller seg i hvordan de håndterer rekursive administrerte typer over en ABI-grense. En rekursiv post med dynamiske arrayer er nøyaktig den typen konstruksjon som kompilerer overalt og oppfører seg subtilt forskjellig i hver
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; // inline stilblokk, ingen ekstern fil
Options.MaxNodes := 200000; // fail-closed budsjett
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;
Hvordan hindres tabeller fra å opptre to ganger?
Tabelldeteksjon kjøres etter at strukturert tekst er samlet inn for en side, noe som skaper en åpenbar felle: det samme celleinnholdet finnes både i tekstblokkene og i den oppdagede tabellen. Å skrive ut begge produserer HTML der hver tabell etterfølges av sitt eget innhold igjen som løse avsnitt
Regelen som løser det, er geometrisk. Når en oppdaget tabell dekker mer enn halvparten av arealet til en tekstblokk, erstatter tabellnoden den blokken i stedet for å slutte seg til den. Celleindeksering inni en rad bygges ved å telle inn i bøtter, slik at bygging av modellen forblir lineær i celler pluss rader i stedet for å skanne hver celle på nytt for hver rad, noe som betyr noe på finansdokumenter der en enkelt side kan bære hundrevis av celler
Oppdaget struktur er ærlig om å være deteksjon. En tabell med rettelinjer gjenkjennes mer pålitelig enn en justert bare med mellomrom, og nodens konfidens gjenspeiler det. For innhold der en gal tabell slår ingen tabell, la deteksjon stå på; for arkivkonvertering der en gal tabell er verre, sperr på konfidens
Eksportere HTML som forblir selvstendig
ToHtml går gjennom modellen som allerede er bygget og går aldri tilbake til PDFium, så å eksportere to ganger koster ingenting ekstra og kan ikke produsere et annet resultat fra den samme modellen. Tekst- og attributtverdier escapes ensartet, overskriftsnivåer klemmes til h1-til-h6-intervallet HTML faktisk definerer, og overskriftsceller, RowSpan og ColumnSpan går gjennom som skrevet
Den valgfrie CSS-en er en ren inline stilblokk. Det finnes ikke noe skript, ingen webfont og ingen ekstern ressurs av noe slag, noe som er det som gjør utdataen trygg å bygge inn i en e-post, en hjelpevisning eller en sandboksert nettleserkontroll:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // hold sidegrenser synlige
Options.PreserveLineBreaks := False; // la nettleseren bryte avsnitt
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 er alternativet mest verdt å tenke over. Et PDF-linjeskift er en typografisk beslutning tatt for en fast sidebredde, så å bevare det på en smal skjerm gjenskaper nøyaktig det problemet reflow finnes for å løse. Bevar linjeskift for poesi, kodeoppføringer og adresser; dropp dem for løpende tekst
Budsjetter, kansellering og sidetilstand
Tegn, noder, tabeller og celler har hver et tak, og hver sjekkes før allokering snarere enn etterpå, slik at et feilformet eller fiendtlig dokument feiler rent i stedet for å bruke opp minne til noe annet gjør det. Kanselleringstokenet sjekkes ved side-, blokk-, tabell-, rad- og cellegrenser, noe som holder en kansellert skanning av et tusen-siders dokument responsiv
Én oppførsel betyr noe spesielt for GUI-applikasjoner: hele dokumentskanningen kjøres inni et scope som gjenoppretter den aktive siden, slik at suksess, budsjettfeil og kansellering alle lar den kallende partens gjeldende side være urørt. En visningsklient som lar brukeren eksportere mens de ser på side 340, finner seg selv fremdeles på side 340 etterpå
Hva reflow er god for, og hva den ikke er
Reflow-utdata er utmerket inndata for søkeindeksering, tilgjengelige lesevisninger, mobilvisning og innholdsmigrering. Det er ikke en troskapsbevarende konverterer: absolutte posisjoner, eksakte fonter, vektorgrafikk og presis sidegeometri er utenfor formålet dens ved design. Når en jobb trenger at siden skal se lik ut, gjengi den; når den trenger at siden skal være lesbar et annet sted, reflow den
For hjelpeteknologi spesifikt kobler reflow-modellen sammen med lesefunksjonene beskrevet i å bygge en tilgjengelig leser, og dokumenter som bærer et ekte strukturtre, produserer merkbart bedre modeller, noe som er et godt argument for å validere tagging tidligere i kjeden, som beskrevet i validering av PDF/UA-strukturtre
Reflow, strukturert tekst, taggingvalidering og gjengivelse deler ett dokumentobjekt på tvers av Delphi, C++Builder og Lazarus; hele API-et er beskrevet på PDFium Component for Delphi-siden