PDFium Component pretvara PDF sa fiksnim rasporedom u semantički model koji se može ponovo tokovati, koristeći BuildReflowDocument, i izvozi taj model kao samostalan HTML kroz ToHtml. Naslovi ostaju naslovi, stavke liste ostaju stavke liste, a tabele otkrivene na stranici izlaze kao pravi tabelarni markup sa sačuvanim ćelijama zaglavlja i rasponima. Ništa u izlazu ne referencira eksternu skriptu ili stilski list
Razlog da se ovo želi jeste što je PDF stranica skup pozicioniranih glifova, što je tačno pogrešno za ekran telefona, čitač ekrana ili indeks pretrage. Svaki pokušaj da se ovo reši izvlačenjem čistog teksta gubi strukturu koja je činila dokument čitljivim, a svaki pokušaj da se ovo reši pretvaranjem stranica u slike potpuno gubi tekst. Reflow model čuva oboje: reči i odnose između njih
Odakle dolaze semantičke informacije?
Sve počinje od GetStructuredText, jedinog izvora teksta i semantike u komponenti. Kada PDF nosi strukturno stablo, označen PDF kao što je definisano u ISO 32000-1 klauzuli 14.7, model prati logičku hijerarhiju koju je proizvođač zabeležio. Kada ne nosi, a većina PDF-ova u divljini ne nosi, model se vraća na fizički redosled rasporeda koji je već izračunat u svrhu redosleda čitanja
Taj izbor čuva čvrstu granicu: nijedan drugi PDF parser i nijedan drugi renderer se ne uvodi da bi odgovorio na pitanja koja postojeći već može odgovoriti. Mehanika redosleda čitanja ispod je opisana u strukturiranim tekstualnim blokovima i redosledu čitanja, a reflow model je semantički sloj na vrhu toga, ne zamena
Svaki čvor beleži odakle njegova informacija potiče, tako da potrošač može razlikovati naslov koji je dokument deklarisao od naslova koji su heuristike rasporeda zaključile. Pipeline-ovi osetljivi na pouzdanost trebalo bi da čitaju to polje umesto da tretiraju sve čvorove kao podjednako merodavne
Ravno stablo, i zašto to nije stablo objekata
Model je pre-order spljošteno stablo: niz čvorova gde svaki čvor nosi ParentIndex i Depth, umesto rekurzivnog zapisa ili grafa objekata sa vlasništvom. Stranice, naslovi, pasusi, liste, stavke liste, figure, natpisi, tabele, redovi i ćelije svi žive u tom jednom linearnom nizu
Dve koristi slede iz toga. Potrošači mogu strimovati niz po redosledu bez rekurzije, što emitovanje HTML-a, Markdown-a ili prikaza stabla čini jednostavnom petljom. A raspored ostaje prenosiv preko Delphija, C++Buildera i Free Pascala, koji se razlikuju u tome kako rukuju rekurzivnim upravljanim tipovima preko ABI granice. Rekurzivan zapis dinamičkih nizova je tačno vrsta konstrukcije koja se kompajlira svuda, a ponaša se suptilno drugačije u svakom
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; // ugrađen blok stila, bez eksternog fajla
Options.MaxNodes := 200000; // budžet koji zatvara pri prekoračenju
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 tabele sprečavaju da se pojave dvaput?
Detekcija tabela se pokreće nakon što je strukturirani tekst prikupljen za stranicu, što stvara očiglednu opasnost: isti sadržaj ćelije postoji i u tekstualnim blokovima i u otkrivenoj tabeli. Emitovanje oba proizvodi HTML gde svaku tabelu prati njen sopstveni sadržaj ponovo kao rastresiti pasusi
Pravilo koje ovo rešava je geometrijsko. Kada otkrivena tabela pokriva više od polovine površine tekstualnog bloka, čvor tabele zamenjuje taj blok umesto da mu se pridružuje. Indeksiranje ćelija unutar reda se gradi brojanjem u kante, tako da izgradnja modela ostaje linearna po ćelijama plus redovima umesto ponovnog skeniranja svake ćelije za svaki red, što je bitno na finansijskim dokumentima gde jedna stranica može nositi stotine ćelija
Otkrivena struktura je iskrena o tome da je detekcija. Tabela sa linijama razgraničenja se pouzdanije prepoznaje od one poravnate isključivo razmacima, a pouzdanost čvora to odražava. Za sadržaj gde pogrešna tabela pobeđuje nikakvu tabelu, ostavite detekciju uključenu; za arhivsku konverziju gde je pogrešna tabela gora, ograničite na osnovu pouzdanosti
Izvoz HTML-a koji ostaje samostalan
ToHtml obilazi model koji je već izgrađen i nikada se ne vraća PDFium-u, tako da izvoz dvaput ne košta ništa dodatno i ne može proizvesti drugačiji rezultat iz istog modela. Tekst i vrednosti atributa se izbegavaju ujednačeno, nivoi naslova se ograničavaju na opseg h1 do h6 koji HTML zapravo definiše, a ćelije zaglavlja, RowSpan i ColumnSpan prolaze onako kako su napisani
Opcioni CSS je običan ugrađen blok stila. Nema skripte, nema veb fonta i nema eksternog resursa bilo koje vrste, što čini izlaz bezbednim za ugrađivanje u imejl, pregledač pomoći ili peskovnik pregledača:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // zadrži granice stranica vidljivim
Options.PreserveLineBreaks := False; // dozvoli pregledaču da prelama pasuse
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 opcija o kojoj je najviše vredno razmisliti. Prelom reda u PDF-u je tipografska odluka doneta za fiksnu širinu stranice, tako da njegovo čuvanje na uskom ekranu ponovo stvara upravo onaj problem koji reflow postoji da reši. Čuvajte prelome za poeziju, listinge koda i adrese; odbacite ih za prozu
Budžeti, otkazivanje i stanje stranice
Karakteri, čvorovi, tabele i ćelije svaki imaju plafon, a svaki se proverava pre dodele, ne posle, tako da malformisan ili neprijateljski dokument čisto otkaže umesto da troši memoriju dok nešto drugo ne otkaže. Token otkazivanja se proverava na granicama stranice, bloka, tabele, reda i ćelije, što drži otkazano skeniranje dokumenta od hiljadu stranica odzivnim
Jedno ponašanje je bitno posebno za GUI aplikacije: ceo sken dokumenta se izvodi unutar opsega koji vraća aktivnu stranicu, tako da uspeh, neuspeh budžeta i otkazivanje svi ostavljaju trenutnu stranicu pozivaoca netaknutom. Pregledač koji dozvoli korisniku da izveze dok gleda stranicu 340 nalazi se i dalje na stranici 340 nakon toga
Za šta je reflow dobar, a za šta nije
Reflow izlaz je odličan ulaz za indeksiranje pretrage, pristupačne prikaze čitanja, mobilni prikaz i migraciju sadržaja. Nije konvertor koji čuva vernost: apsolutne pozicije, tačni fontovi, vektorska umetnost i precizna geometrija stranice su van njegove svrhe po dizajnu. Kada posao treba da stranica izgleda isto, renderujte je; kada treba da stranica bude čitljiva negde drugde, reflow-ujte je
Za asistivnu tehnologiju konkretno, reflow model se uparuje sa funkcijama čitanja opisanim u izgradnji pristupačnog čitača, a dokumenti koji nose pravo strukturno stablo proizvode primetno bolje modele, što je dobar argument za validaciju označavanja uzvodno kao što je opisano u validaciji PDF/UA strukturnog stabla
Reflow, strukturiran tekst, validacija označavanja i renderovanje dele jedan objekat dokumenta preko Delphija, C++Buildera i Lazarusa; kompletan API je opisan na stranici PDFium Component za Delphi