PDF-tekstin poimiminen vaikuttaa yksinkertaiselta, kunnes kohtaat asiakirjan, josta tekstikerros puuttuu, on korruptoitunut tai jaettu kymmeniin pikkuruisiin merkkijonoihin ilman loogista järjestystystä. PDFium-komponentti tarjoaa kaksi sisääntulopistettä: Character[]-taulukon raakaan, indeksipohjaiseen pääsyyn sivun jokaiseen glyyfiin, sekä ReadablePageContent-metodin rakenteelliseen näkymään, joka muodostaa kappaleet ja otsikot uudelleen PDF:n tagipuun tai heuristisen analyysin perusteella. Kumpikaan ei ole aina oikea valinta, joten niiden tarjoamien tietojen ymmärtämisellä on merkitystä
Asiakirjan avaaminen ja hiljaisen epäonnistumisen sudenkuoppa
TPdf avaa tiedoston asettamalla FileName-propertyn ja kytkemällä Active := True. Kriittinen yksityiskohta: Active := True ei koskaan heitä poikkeusta. Jos tiedosto puuttuu, on suojattu salasanalla tai vioittunut, PDFium havaitsee virheen sisäisesti ja Active pysyy yksinkertaisesti tilassa False. Tämä tarkoittaa, että jokaisen poimintasilmukan on suojauduttava tältä:
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True;
if not Pdf.Active then
begin
ShowMessage('Could not open PDF (damaged or wrong password)');
Exit;
end;
// extraction follows here
finally
Pdf.Active := False;
Pdf.Free;
end;
Salasanalla suojatut tiedostot vaativat asetuksen Pdf.Password := '...' ennen kuin Active := True suoritetaan. Toista mahdollisuutta ei ole: kun Active epäonnistuu, suljet ja avaat uudelleen oikealla salasanalla
Sivuittaisten tekstien poiminta Character[]-taulukolla
Matalimman tason lähestymistapa käy läpi jokaisen merkin kullakin sivulla. Aseta Pdf.PageNumber ladataksesi kyseisen sivun tekstikerroksen, ja käy sitten läpi CharacterCount-määrän mukaiset merkinnät Character[]-ominaisuuden avulla. Kaksi lippua jokaisessa merkinnässä on syytä tarkistaa: CharacterGenerated[i] merkitsee renderöijän lisäämiä synteettisiä glyyfejä (kuten pehmeitä tavuviivoja rivinvaihdoissa), joilla ei ole todellista Unicode-arvoa, ja CharacterMapError[i] ilmoittaa, ettei PDFium pystynyt kartoittamaan glyyfiä koodipisteeksi, mikä tapahtuu fonttikoodauksissa, joista puuttuu ToUnicode-taulukko
procedure ExtractAllText(Pdf: TPdf; Output: TStrings);
var
Page, I: Integer;
Line: string;
Ch: WideChar;
begin
for Page := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := Page;
Line := '';
for I := 0 to Pdf.CharacterCount - 1 do
begin
if Pdf.CharacterGenerated[I] or Pdf.CharacterMapError[I] then
Continue;
Ch := Pdf.Character[I];
if Ch = #13 then
Ch := #10; // normalize CR to LF
Line := Line + Ch;
end;
Output.Add(Line);
end;
end;
Tuloksena on litteä merkkijono Unicode-koodipisteitä siinä järjestyksessä, jossa PDFium luettelee ne, eli järjestyksessä, jossa ne esiintyvät sisältövirrassa – ei välttämättä vasemmalta oikealle -lukujärjestyksessä. Useimmille latinalaisilla aakkosilla kirjoitetuille asiakirjoille, jotka on tuotettu tavallisilla toimistotyökaluilla, tämä on riittävää. Skannatuille PDF-tiedostoille, jotka on OCR-käsitelty epätavallisilla glyyfijärjestyksillä, tai oikealta vasemmalle kirjoitetulle tekstille järjestys voi olla väärä. Tällöin ReadablePageContent on hyödyllisempi
Rakenteinen poiminta ReadablePageContent-metodilla
ReadablePageContent nousee yhtä tasoa ylemmäksi: se palauttaa TPdfReadableContent-tietueen, jonka Fragments-taulukko kantaa tagitettuja sisältöfragmentteja. Jokaisella fragmentilla on Kind-arvo, joka tunnistaa kappaleet, otsikot, luettelomerkit, taulukon solut ja niin edelleen. Kun PDF-tiedostossa on rakennekuvaus eli tagipuu (tarkista Pdf.IsTagged), lähde on rosStructure ja lukujärjestys on auktoritatiivinen. Tagittamattomissa tiedostoissa PDFium turvautuu arvoon rosHeuristic, joka ryhmittelee merkit niiden rajoituslaatikoiden mukaan uskottaviksi lukuyksiköiksi mutta ei voi taata tarkkuutta
procedure ExtractStructured(Pdf: TPdf; Output: TStrings);
var
Page: Integer;
Content: TPdfReadableContent;
Fragment: TPdfContentFragment;
begin
for Page := 1 to Pdf.PageCount do
begin
Content := Pdf.ReadablePageContent(Page);
for Fragment in Content.Fragments do
begin
case Fragment.Kind of
cfHeading : Output.Add('# ' + Fragment.Text);
cfParagraph : Output.Add(Fragment.Text);
cfListItem : Output.Add('- ' + Fragment.Text);
else
Output.Add(Fragment.Text);
end;
end;
end;
end;
Jos Content.Source = rosHeuristic ja tulosteesi näyttää sekavalta, asiakirjan tekstikerrosta ei todennäköisesti kirjoitettu lukujärjestystä silmällä pitäen. Siinä vaiheessa ainoa luotettava korjaus on viedä asiakirja uudelleen lähdesovelluksesta asianmukaisella tagituksella, tai ajaa jälkikäsittelyvaihe, joka lajittelee merkkien origot Y-koordinaatin ja sitten X-koordinaatin mukaan
Mitä CharacterOrigin ja CharacterRectangle tarjoavat
Molemmat propertyt palauttavat merkin sijainnin sivun avaruudessa (pisteinä, origo vasemmassa alakulmassa, Y kasvaa ylöspäin). CharacterOrigin[i] on glyyfin perusviivan ankkuripiste; CharacterRectangle[i] on koko rajoituslaatikko (bounding box). Nämä ovat rakennuspalikoita kaikelle pelkän tekstin ulkopuolelle menevälle: sarakerajojen havaitsemiselle, merkkijonojen ryhmittelylle riveiksi vertaamalla Y-koordinaatteja toleranssin rajoissa tai osumatestikartan rakentamiselle tekstin valintaa varten katseluohjelmassa. Jos sinun on löydettävä, mikä merkki on hiiren napsautuksen alla, CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) tekee tuon haun suoraan ilman, että joudut käymään suorakulmioita läpi silmukassa
DLL-kirjaston saaminen paikalleen
PDFium-komponentti delegoi kaiken PDF-jäsennyksen natiiville DLL-kirjastolle, joko pdfium32.dll- tai pdfium64.dll-tiedostolle kohdealustastasi riippuen. Komponentin mukana toimitetaan CopyDlls.bat-skripti, joka kopioi oikean tiedoston Windowsin järjestelmähakemistoon. Sen suorittaminen kerran järjestelmänvalvojana kehityskoneella riittää; jakelussa kopioit DLL-tiedoston sovelluksen ajotiedoston viereen. V8-käyttöiset variantit (pdfium32v8.dll, pdfium64v8.dll) ovat huomattavasti suurempia ja tarpeellisia vain, jos PDF-tiedostosi sisältävät suoritettavaa JavaScriptiä. Tekstin poimintaan standardiversio on oikea valinta
Jos DLL-kirjasto puuttuu ajon aikana, Active := True epäonnistuu hiljaisesti aivan kuten puuttuvan tiedoston tapauksessa, koska komponentti ottaa latausvirheen kiinni sisäisesti. Testaa aina puhtaalla koneella ennen toimitusta
FontSize[]-taulukon käyttö Character[]-taulukon ohella asetteluanalyysissä
Pelkän tekstin lisäksi merkkitasoinen API tuo esiin FontSize[i]-arvon, joka palauttaa kunkin glyyfin renderöidyn pistekoon. Yhdessä CharacterOrigin[i]- ja CharacterRectangle[i]-tietojen kanssa tämä antaa sinun erottaa leipätekstin otsikoista ilman rakennekuvaukseen turvautumista. Merkkijono, jossa fonttikoko hyppää kynnysarvon yli, on lähes varmasti otsikko tagittamattomassa asiakirjassa. Sama tekniikka pätee kuvatekstien (pieni teksti kuvan rajoituslaatikon alla) tai alaviitteiden (pieni teksti lähellä sivun alareunaa) tunnistamiseen. Mikään tästä ei vaadi renderöintiä; kaikki kolme propertyä lukevat suoraan tekstikerrosta, jonka PDFium rakentaa Active := True -vaiheessa
Eräs vivahde: FontSize[i] heijastaa kokoa sen jälkeen, kun sivun CTM-matriisi (current transformation matrix) on sovellettu, joten asiakirja, jossa tekijä on skaalannut koko sivun, raportoi suhteellisesti mukautetut koot. Jos vertailet kokoja eri sivuilla, joilla on eri sivumitat, normalisoi ne kunkin sivun MediaBox-korkeutta vasten ennen kynnyspäätösten tekemistä
Tulosteen kirjoittaminen tiedostoon
Delphin TStringList käsittelee UTF-8-tulostetta siististi XE-versiosta alkaen. Aseta WriteBOM := False, jos tarvitset BOM-vapaan tiedoston ( monet jatkokäsittelijät takertuvat alussa olevaan BOM-merkkiin):
var
Lines: TStringList;
begin
Lines := TStringList.Create;
try
ExtractAllText(Pdf, Lines);
Lines.WriteBOM := False;
Lines.SaveToFile('output.txt', TEncoding.UTF8);
finally
Lines.Free;
end;
end;
Erittäin suurissa asiakirjoissa, joissa muisti on huolenaihe, kirjoita suoraan TStreamWriter-olioon käyttäen TEncoding.UTF8-koodausta sivusilmukan sisällä sen sijaan, että keräisit kaiken ensin listaan
Tässä esitetyt Character[]-, CharacterCount-, CharacterOrigin[]-, CharacterRectangle[]-, ReadablePageContent- ja CharacterIndexAtPos-API:t ovat osa Delphille ja C++Builderille tarkoitettua PDFium-komponenttia