PDFium Component muuttaa kiinteän asettelun PDF:n semanttiseksi malliksi, jonka voi virtauttaa uudelleen, metodilla BuildReflowDocument, ja vie tuon mallin itsenäisenä HTML:nä metodilla ToHtml. Otsikot pysyvät otsikkoina, listakohteet pysyvät listakohteina, ja sivulta havaitut taulukot tulevat ulos todellisena taulukkomerkintänä, jossa otsikkosolut ja span-arvot säilyvät. Mikään tulosteessa ei viittaa ulkoiseen skriptiin tai tyylitiedostoon
Syy tämän haluamiseen on se, että PDF-sivu on joukko sijoitettuja glyyfejä, mikä on täsmälleen väärin puhelimen näytölle, ruudunlukijalle tai hakuindeksille. Jokainen yritys ratkaista se poimimalla pelkkää tekstiä menettää rakenteen, joka teki dokumentista luettavan, ja jokainen yritys ratkaista se muuntamalla sivut kuviksi menettää tekstin kokonaan. Uudelleenvirtausmalli säilyttää molemmat: sanat ja niiden väliset suhteet
Mistä semanttinen tieto tulee?
Kaikki alkaa metodista GetStructuredText, komponentin ainoasta tekstin ja semantiikan lähteestä. Kun PDF kantaa rakennepuun, merkitty PDF ISO 32000-1:n kohdan 14.7 määrittelemällä tavalla, malli noudattaa tuottajan tallentamaa loogista hierarkiaa. Kun näin ei ole, ja useimmat luonnossa esiintyvät PDF-tiedostot eivät kanna, malli palaa fyysiseen asettelujärjestykseen, joka on jo laskettu lukujärjestystarkoituksia varten
Tämä valinta säilyttää tiukan rajan: mitään toista PDF-jäsentäjää tai toista renderöintimoottoria ei oteta käyttöön vastaamaan kysymyksiin, joihin olemassa oleva jo vastaa. Taustalla oleva lukujärjestyskoneisto kuvataan artikkelissa rakenteelliset tekstilohkot ja lukujärjestys, ja uudelleenvirtausmalli on semanttinen kerros sen päällä, ei sen korvaaja
Jokainen solmu tallentaa, mistä sen tieto tuli, joten kuluttaja voi erottaa otsikon, jonka dokumentti ilmoitti, otsikosta, jonka asetteluheuristiikka päätteli. Luottamusherkkien putkien tulisi lukea tuo kenttä sen sijaan, että ne kohtelisivat kaikkia solmuja yhtä auktoritatiivisina
Litteä puu ja miksi se ei ole objektien puu
Malli on esijärjestyksessä litistetty puu: taulukko solmuja, joista jokainen kantaa arvot ParentIndex ja Depth, ei rekursiivinen tietue tai omistuksellinen objektigraafi. Sivut, otsikot, kappaleet, listat, listakohteet, kuvat, kuvatekstit, taulukot, rivit ja solut asuvat kaikki tuossa yhdessä lineaarisessa taulukossa
Tästä seuraa kaksi etua. Kuluttajat voivat suoratoistaa taulukon järjestyksessä ilman rekursiota, mikä tekee HTML:n, Markdownin tai puunäkymän tuottamisesta yksinkertaisen silmukan. Ja asettelu pysyy siirrettävänä Delphin, C++Builderin ja Free Pascalin välillä, jotka eroavat siinä, miten ne käsittelevät rekursiivisia hallittuja tyyppejä ABI-rajan yli. Dynaamisten taulukoiden rekursiivinen tietue on juuri sellainen rakenne, joka kääntyy kaikkialla ja käyttäytyy hienovaraisesti eri tavalla jokaisessa
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; // sisäinen tyylilohko, ei ulkoista tiedostoa
Options.MaxNodes := 200000; // turvallisesti sulkeutuva budjetti
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;
Miten taulukot pidetään esiintymästä kahdesti?
Taulukoiden havaitseminen tapahtuu sen jälkeen, kun rakenteellinen teksti on kerätty sivulle, mikä luo ilmeisen vaaran: sama solusisältö on olemassa sekä tekstilohkoissa että havaitussa taulukossa. Molempien tuottaminen tuottaisi HTML:n, jossa jokaista taulukkoa seuraa sen oma sisältö uudelleen irrallisina kappaleina
Sääntö, joka ratkaisee tämän, on geometrinen. Kun havaittu taulukko peittää yli puolet tekstilohkon alueesta, taulukkosolmu korvaa tuon lohkon sen sijaan, että liittyisi siihen. Solujen indeksointi rivin sisällä rakennetaan laskemalla ne koreihin, joten mallin rakentaminen pysyy lineaarisena solujen plus rivien suhteen sen sijaan, että se skannaisi jokaisen solun uudelleen jokaista riviä kohden, millä on merkitystä talousdokumenteissa, joissa yksi sivu voi kantaa satoja soluja
Havaittu rakenne on rehellinen siitä, että se on havaintoa. Taulukko, jossa on viivoitusviivoja, tunnistetaan luotettavammin kuin sellainen, joka on tasattu puhtaasti tyhjätilalla, ja solmun luottamus heijastaa tätä. Sisällölle, jossa väärä taulukko on parempi kuin ei taulukkoa lainkaan, pidä havainnointi päällä; arkistomuunnokselle, jossa väärä taulukko on huonompi, portitse luottamuksen mukaan
Itsenäisenä pysyvän HTML:n vienti
ToHtml käy läpi jo rakennetun mallin eikä koskaan käy uudelleen PDFiumissa, joten kahdesti vieminen ei maksa mitään ylimääräistä eikä voi tuottaa eri tulosta samasta mallista. Teksti- ja attribuuttiarvot koodataan yhtenäisesti, otsikkotasot rajataan h1:sta h6:een alueeseen, jonka HTML todella määrittelee, ja otsikkosolut, RowSpan ja ColumnSpan siirtyvät sellaisenaan
Valinnainen CSS on tavallinen sisäinen tyylilohko. Siinä ei ole skriptiä, verkkofonttia eikä minkäänlaista ulkoista resurssia, mikä tekee tulosteesta turvallisen upottaa sähköpostiin, ohjekatseluohjelmaan tai hiekkalaatikoituun selainohjaimeen:
var
Html: WideString;
Stream: TFileStream;
Bytes: TBytes;
begin
Options := TPdfReflowOptions.Default;
Options.FullDocument := True;
Options.IncludeCss := True;
Options.IncludePageSections := True; // pidä sivujen rajat näkyvissä
Options.PreserveLineBreaks := False; // anna selaimen rivittää kappaleet
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 on asetus, jota kannattaa eniten pohtia. PDF:n rivinvaihto on ladontapäätös, joka on tehty kiinteälle sivun leveydelle, joten sen säilyttäminen kapealla näytöllä toistaa juuri sen ongelman, jota varten uudelleenvirtaus on olemassa. Säilytä rivinvaihdot runoudelle, koodilistauksille ja osoitteille; pudota ne proosalta
Budjetit, peruutus ja sivun tila
Merkeillä, solmuilla, taulukoilla ja soluilla on kullakin katto, ja jokainen tarkistetaan ennen varausta eikä sen jälkeen, joten virheellinen tai vihamielinen dokumentti epäonnistuu siististi sen sijaan, että se kuluttaisi muistia, kunnes jokin muu tekee niin. Peruutustunniste tarkistetaan sivun, lohkon, taulukon, rivin ja solun rajoilla, mikä pitää tuhatsivuisen dokumentin peruutetun skannauksen responsiivisena
Yksi käyttäytyminen on erityisen tärkeä GUI-sovelluksille: koko dokumentin skannaus toimii laajuudessa, joka palauttaa aktiivisen sivun, joten onnistuminen, budjettivika ja peruutus jättävät kaikki kutsujan nykyisen sivun koskemattomaksi. Katseluohjelma, joka antaa käyttäjän viedä samalla, kun tämä katsoo sivua 340, huomaa olevansa yhä sivulla 340 jälkikäteen
Mihin uudelleenvirtaus sopii ja mihin ei
Uudelleenvirtauksen tuloste on erinomainen syöte hakuindeksoinnille, esteettömille lukunäkymille, mobiilinäytölle ja sisällön migraatiolle. Se ei ole uskollisuutta säilyttävä muunnin: absoluuttiset sijainnit, tarkat fontit, vektorigrafiikka ja tarkka sivugeometria ovat tarkoituksellisesti sen tarkoituksen ulkopuolella. Kun työ vaatii sivun näyttävän samalta, renderöi se; kun se vaatii sivun olevan luettavissa jossain muualla, virtauta se uudelleen
Nimenomaan avustavalle teknologialle uudelleenvirtausmalli yhdistyy artikkelissa esteettömän lukijan rakentaminen kuvattuihin lukuominaisuuksiin, ja dokumentit, jotka kantavat aidon rakennepuun, tuottavat huomattavasti parempia malleja, mikä on hyvä perustelu merkinnän validoimiselle ylävirrassa, kuten kuvataan artikkelissa PDF/UA-rakennepuun validointi
Uudelleenvirtaus, rakenteellinen teksti, merkinnän validointi ja renderöinti jakavat yhden dokumenttiobjektin Delphin, C++Builderin ja Lazaruksen välillä; täydellinen API kuvataan sivulla PDFium Component for Delphi -sivulla