PDFium Component paverčia fiksuoto maketo PDF failą semantiniu modeliu, kurį galima pertvarkyti (reflow), naudodama BuildReflowDocument, ir eksportuoja tą modelį kaip savarankišką HTML per ToHtml. Antraštės lieka antraštėmis, sąrašo elementai lieka sąrašo elementais, o puslapyje aptiktos lentelės virsta tikru lentelės žymėjimu, išsaugant antraštės langelius ir apjungimus (span). Nė vienoje rezultato vietoje nėra nuorodos į išorinį scenarijų ar stilių lapą
Priežastis, kodėl to reikia, yra ta, kad PDF puslapis yra pozicionuotų glifų rinkinys, o tai visiškai netinka telefono ekranui, ekrano skaitytuvui ar paieškos indeksui. Kiekvienas bandymas tai išspręsti ištraukiant grynąjį tekstą prarandą struktūrą, dėl kurios dokumentas buvo skaitomas, o kiekvienas bandymas tai išspręsti konvertuojant puslapius į vaizdus visiškai praranda tekstą. Pertvarkymo modelis išsaugo abu dalykus: žodžius ir ryšius tarp jų
Iš kur ateina semantinė informacija?
Viskas prasideda nuo GetStructuredText – vienintelio teksto ir semantikos šaltinio komponente. Kai PDF failas neša struktūros medį – žymėtą PDF, kaip apibrėžta ISO 32000-1 14.7 punkte – modelis seka loginę hierarchiją, kurią įrašė kūrėjas. Kai jo nėra, o dauguma realaus pasaulio PDF failų jo neturi, modelis persijungia į fizinę maketo tvarką, jau apskaičiuotą skaitymo tvarkos tikslams
Šis pasirinkimas išlaiko griežtą ribą: nediegiamas antras PDF analizatorius ir antras atvaizdavimo variklis, kad atsakytų į klausimus, į kuriuos jau gali atsakyti esamas. Skaitymo tvarkos mechanika, esanti apačioje, aprašyta straipsnyje struktūrizuoti teksto blokai ir skaitymo tvarka, o pertvarkymo modelis yra semantinis sluoksnis virš jos, o ne jos pakaitalas
Kiekvienas mazgas įrašo, iš kur atsirado jo informacija, todėl naudotojas gali atskirti antraštę, kurią deklaravo dokumentas, nuo antraštės, kurią išvedė maketo euristika. Konvejeriai, jautrūs pasitikėjimo lygiui, turėtų skaityti šį lauką, o ne traktuoti visus mazgus kaip vienodai patikimus
Plokščias medis, ir kodėl tai ne objektų medis
Modelis yra plokščias, prieš-tvarkos (pre-order) būdu suplotas medis: mazgų masyvas, kur kiekvienas mazgas neša ParentIndex ir Depth, o ne rekursyvus įrašas ar objektų grafas su nuosavybe. Puslapiai, antraštės, pastraipos, sąrašai, sąrašo elementai, iliustracijos, antraštės po iliustracijomis, lentelės, eilutės ir langeliai – visi jie gyvena tame viename linijiniame masyve
Iš to seka du privalumai. Naudotojai gali srautu apeiti masyvą tvarka be rekursijos, todėl HTML, Markdown ar medžio vaizdo generavimas tampa paprastu ciklu. Ir maketas išlieka perkeliamas tarp Delphi, C++Builder ir Free Pascal, kurie skiriasi tuo, kaip apdoroja rekursyvius valdomus tipus per ABI ribą. Rekursyvus dinaminių masyvų įrašas yra būtent tokia konstrukcija, kuri kompiliuojasi visur, bet kiekvienoje aplinkoje elgiasi subtiliai kitaip
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; // vidinis stiliaus blokas, be išorinio failo
Options.MaxNodes := 200000; // biudžetas, kuris uždaro vykdymą pasiekus ribą
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;
Kaip lentelės apsaugomos nuo dvigubo pasirodymo?
Lentelių aptikimas vyksta po to, kai puslapiui jau surinktas struktūrizuotas tekstas, o tai sukuria akivaizdų pavojų: tas pats langelio turinys egzistuoja ir teksto blokuose, ir aptiktoje lentelėje. Išvedus abu, gaunamas HTML, kuriame po kiekvienos lentelės dar kartą seka jos pačios turinys kaip laisvos pastraipos
Tai išsprendžianti taisyklė yra geometrinė. Kai aptikta lentelė užima daugiau nei pusę teksto bloko ploto, lentelės mazgas pakeičia tą bloką, o ne prisijungia prie jo. Langelių indeksavimas eilutės viduje sudaromas skaičiuojant į kibirus (buckets), todėl modelio kūrimas išlieka linijinis langelių ir eilučių atžvilgiu, o ne peržiūri kiekvieną langelį iš naujo kiekvienai eilutei – tai svarbu finansiniuose dokumentuose, kur vienas puslapis gali nešti šimtus langelių
Aptikta struktūra sąžiningai pripažįsta esanti aptikimo rezultatas. Lentelė su linijomis atpažįstama patikimiau nei lentelė, sulygiuota vien tarpais, ir mazgo pasitikėjimo reikšmė tai atspindi. Turiniui, kur neteisinga lentelė geriau nei jos nebuvimas, palikite aptikimą įjungtą; archyvavimo konvertavimui, kur neteisinga lentelė yra blogiau, filtruokite pagal pasitikėjimą
HTML eksportavimas, kuris išlieka savarankiškas
ToHtml apeina jau sukurtą modelį ir niekada nesikreipia atgal į PDFium, todėl eksportavimas du kartus nekainuoja nieko papildomo ir negali duoti kitokio rezultato iš to paties modelio. Teksto ir atributų reikšmės nuosekliai apsaugomos (escaped), antraščių lygiai apribojami iki h1–h6 diapazono, kurį iš tikrųjų apibrėžia HTML, o antraštės langeliai, RowSpan ir ColumnSpan perkeliami taip, kaip parašyti
Nebūtinas CSS yra paprastas vidinis stiliaus blokas. Nėra jokio scenarijaus, jokio žiniatinklio šrifto ir jokio išorinio resurso – būtent tai daro rezultatą saugų įterpti į el. laišką, pagalbos peržiūros priemonę ar izoliuotą naršyklės valdiklį:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // išlaikyti matomas puslapių ribas
Options.PreserveLineBreaks := False; // leisti naršyklei pačiai laužyti pastraipas
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 yra parinktis, apie kurią labiausiai verta pagalvoti. PDF eilutės lūžis yra spausdinimo sprendimas, priimtas fiksuotam puslapio pločiui, todėl jo išsaugojimas siaurame ekrane atkuria būtent tą problemą, kurią pertvarkymas ir turi išspręsti. Išsaugokite lūžius poezijai, kodo sąrašams ir adresams; atmeskite juos prozai
Biudžetai, atšaukimas ir puslapio būsena
Simboliai, mazgai, lentelės ir langeliai kiekvienas turi savo lubą, ir kiekviena patikrinama prieš paskirstant, o ne po to, todėl sugadintas ar priešiškas dokumentas sužlunga tvarkingai, o ne naudoja atmintį tol, kol tai padaro kas nors kitas. Atšaukimo žymuo tikrinamas puslapio, bloko, lentelės, eilutės ir langelio ribose, todėl atšauktas tūkstančio puslapių dokumento skenavimas išlieka reaguojantis
Viena elgsena konkrečiai svarbi GUI programoms: visas dokumento skenavimas vyksta apimtyje, kuri atkuria aktyvų puslapį, todėl sėkmė, biudžeto sužlugimas ir atšaukimas visi palieka kviečiančiojo dabartinį puslapį nepaliestą. Peržiūros priemonė, leidžianti vartotojui eksportuoti žiūrint į 340 puslapį, po to vis dar randasi 340 puslapyje
Kam pertvarkymas tinka, o kam ne
Pertvarkymo rezultatas yra puikus įėjimas paieškos indeksavimui, prieinamiems skaitymo vaizdams, mobiliam rodymui ir turinio migracijai. Tai ne tikslumą išsaugantis konverteris: absoliučios pozicijos, tikslūs šriftai, vektorinė grafika ir tiksli puslapio geometrija sąmoningai nepatenka į jo paskirtį. Kai darbui reikia, kad puslapis atrodytų taip pat, atvaizduokite jį; kai reikia, kad puslapis būtų skaitomas kitur, pertvarkykite jį
Konkrečiai pagalbinėms technologijoms pertvarkymo modelis dera su skaitymo funkcijomis, aprašytomis straipsnyje prieinamo skaitytuvo kūrimas, o dokumentai, nešantys tikrą struktūros medį, duoda pastebimai geresnius modelius – tai geras argumentas patikrinti žymėjimą anksčiau, kaip aprašyta straipsnyje PDF/UA struktūros medžio validavimas
Pertvarkymas, struktūrizuotas tekstas, žymėjimo validavimas ir atvaizdavimas dalijasi vienu dokumento objektu Delphi, C++Builder ir Lazarus aplinkose; pilnas API aprašytas PDFium Component Delphi puslapyje