A PDFium Component a BuildReflowDocument segítségével egy rögzített elrendezésű PDF-et olyan szemantikus modellé alakít, amely átfolyatható, és ezt a modellt a ToHtml-en keresztül önálló HTML-ként exportálja. A címsorok címsorok maradnak, a listaelemek listaelemek maradnak, az oldalon felismert táblázatok pedig valódi táblázatjelölésként jelennek meg, megőrzött fejléccellákkal és span-ekkel. A kimenetben semmi nem hivatkozik külső szkriptre vagy stíluslapra
Az efféle igény oka az, hogy egy PDF-oldal pozicionált glifák halmaza, ami pontosan rossz egy telefon képernyőjéhez, egy képernyőolvasóhoz vagy egy keresőindexhez. Minden olyan próbálkozás, amely egyszerű szöveg kinyerésével oldja meg a problémát, elveszíti azt a struktúrát, amely olvashatóvá tette a dokumentumot, minden olyan próbálkozás pedig, amely az oldalak képpé alakításával oldja meg, teljesen elveszíti a szöveget. Egy átfolyatási modell mindkettőt megtartja: a szavakat és a köztük lévő kapcsolatokat
Honnan származik a szemantikai információ?
Minden a GetStructuredText-ből indul ki, amely a komponens egyetlen szöveg- és szemantikaforrása. Amikor a PDF struktúrafát hordoz, azaz jelölt PDF az ISO 32000-1 14.7. szakaszának definíciója szerint, a modell a producer által rögzített logikai hierarchiát követi. Amikor nem, és a valóságban a legtöbb PDF nem, a modell visszaesik az olvasási sorrend céljára már kiszámított fizikai elrendezési sorrendre
Ez a választás egy szigorú határt tart fenn: nem vezet be sem második PDF-elemzőt, sem második renderelőmotort olyan kérdések megválaszolására, amelyeket a meglévő is meg tud válaszolni. A mögöttes olvasási-sorrend gépezetét a strukturált szövegblokkok és olvasási sorrend ismerteti, az átfolyatási modell pedig egy szemantikus réteg efölött, nem annak helyettesítője
Minden csomópont rögzíti, honnan származik az információja, így egy fogyasztó meg tudja különböztetni a dokumentum által deklarált címsort az elrendezési heurisztika által kikövetkeztetett címsortól. A megbízhatóságra érzékeny folyamatoknak ezt a mezőt kell olvasniuk ahelyett, hogy minden csomópontot egyformán mérvadónak kezelnének
Egy lapos fa, és miért nem objektumfa
A modell egy előrendben ellaposított fa: csomópontok tömbje, ahol minden csomópont egy ParentIndex-et és egy Depth-et hordoz, nem pedig egy rekurzív rekord vagy egy tulajdonlással rendelkező objektumgráf. Az oldalak, címsorok, bekezdések, listák, listaelemek, ábrák, feliratok, táblázatok, sorok és cellák mind ebben az egyetlen lineáris tömbben élnek
Ebből két előny következik. A fogyasztók rekurzió nélkül, sorrendben streamelhetik a tömböt, ami egyszerű ciklussá teszi a HTML, Markdown vagy fanézet előállítását. Az elrendezés emellett hordozható marad Delphi, C++Builder és Free Pascal között, amelyek eltérően kezelik a rekurzív felügyelt típusokat egy ABI-határon át. Egy dinamikus tömbökből álló rekurzív rekord pontosan az a fajta konstrukció, amely mindenhol lefordul, és mindegyikben finoman másképp viselkedik
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; // soron belüli stílusblokk, nincs külső fájl
Options.MaxNodes := 200000; // biztonságosan záró költségkeret
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;
Hogyan kerülik el a táblázatok, hogy kétszer jelenjenek meg?
A táblázatfelismerés azután fut le, hogy a strukturált szöveget összegyűjtötték egy oldalhoz, ami egy nyilvánvaló veszélyt teremt: ugyanaz a cellatartalom létezik a szövegblokkokban is és a felismert táblázatban is. Mindkettő kiadása olyan HTML-t eredményez, amelyben minden táblázatot újra követ a saját tartalma, laza bekezdésekként
A megoldó szabály geometriai. Amikor egy felismert táblázat egy szövegblokk területének felénél többet fed le, a táblázatcsomópont lecseréli azt a blokkot, ahelyett hogy csatlakozna hozzá. A soron belüli cellaindexelés vödrökbe számolással épül fel, így a modell felépítése lineáris marad a cellák plusz sorok függvényében, ahelyett hogy minden sorhoz újra végigpásztázna minden cellát, ami pénzügyi dokumentumoknál számít, ahol egyetlen oldal több száz cellát is hordozhat
A felismert struktúra őszinte afelől, hogy felismerés. Egy vonalakkal rendelkező táblázatot megbízhatóbban ismer fel a rendszer, mint egyet, amely kizárólag üres helyekkel van igazítva, és a csomópont megbízhatósága ezt tükrözi. Olyan tartalomnál, ahol egy rossz táblázat jobb, mint egyáltalán semmilyen, tartsd bekapcsolva a felismerést; archív konverziónál, ahol egy rossz táblázat rosszabb, kapuzz a megbízhatóság alapján
Önmagában megálló HTML exportálása
A ToHtml végigjárja a már felépített modellt, és soha nem tér vissza a PDFiumhoz, így a kétszeri exportálás semmi extrát nem kerül, és nem is adhat eltérő eredményt ugyanabból a modellből. A szöveg- és attribútumértékek egységesen escape-elve vannak, a címsorszintek a HTML által ténylegesen definiált h1-től h6-ig terjedő tartományra vannak korlátozva, a fejléccellák, a RowSpan és a ColumnSpan pedig változatlanul kerülnek át
Az opcionális CSS egy egyszerű soron belüli stílusblokk. Nincs szkript, nincs webes betűtípus és nincs semmilyen külső erőforrás, és pontosan ez teszi biztonságossá a kimenet beágyazását egy e-mailbe, egy súgónézőbe vagy egy homokozózott böngészővezérlőbe:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // az oldalhatárok láthatók maradnak
Options.PreserveLineBreaks := False; // a böngésző tördeli a bekezdéseket
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;
A PreserveLineBreaks az az opció, amelyen a leginkább érdemes elgondolkodni. Egy PDF-beli sortörés egy fix oldalszélességre hozott tördelési döntés, így egy keskeny képernyőn való megőrzése pontosan azt a problémát reprodukálja, amelynek megoldására az átfolyatás létezik. Őrizd meg a töréseket versnél, kódlistáknál és címeknél; hagyd el őket prózánál
Költségkeretek, megszakítás és oldalállapot
A karaktereknek, csomópontoknak, táblázatoknak és celláknak mindegyiknek van felső határa, mindegyiket a lefoglalás előtt ellenőrzi, nem utána, így egy rosszul formázott vagy rosszindulatú dokumentum tisztán bukik el, ahelyett hogy addig fogyasztana memóriát, amíg valami más nem áll le. A megszakítási tokent oldal-, blokk-, táblázat-, sor- és cellahatároknál ellenőrzi, ami reszponzívan tartja egy ezeroldalas dokumentum megszakított pásztázását
Egy viselkedés kifejezetten a GUI-alkalmazásoknál számít: a teljes dokumentumpásztázás egy olyan hatókörön belül fut, amely visszaállítja az aktív oldalt, így a siker, a költségkeret-hiba és a megszakítás egyaránt érintetlenül hagyja a hívó aktuális oldalát. Egy megjelenítő, amely lehetővé teszi a felhasználónak az exportálást a 340. oldal nézése közben, utána is a 340. oldalon találja magát
Mire jó az átfolyatás, és mire nem?
Az átfolyatás kimenete kiváló bemenet a keresőindexeléshez, a hozzáférhető olvasónézetekhez, a mobilmegjelenítéshez és a tartalommigrációhoz. Nem hűségmegőrző konverter: az abszolút pozíciók, a pontos betűtípusok, a vektorgrafika és a pontos oldalgeometria tervezésénél fogva kívül esik a céljain. Ha egy feladatnak arra van szüksége, hogy az oldal ugyanúgy nézzen ki, rendereld; ha arra van szüksége, hogy az oldal máshol is olvasható legyen, folyasd át
Kifejezetten az akadálymentesítési technológiákhoz az átfolyatási modell párosul a akadálymentes olvasó építése cikkben leírt olvasási funkciókkal, a valódi struktúrafát hordozó dokumentumok pedig érezhetően jobb modelleket adnak, ami jó érv amellett, hogy a jelölést a folyamat elején validáld, ahogy azt a PDF/UA struktúrafa-validáció ismerteti
Az átfolyatás, a strukturált szöveg, a jelölésvalidáció és a renderelés egyetlen dokumentumobjektumon osztozik Delphiben, C++Builderben és Lazarusban; a teljes API-t a PDFium Component for Delphi oldala ismerteti