Črpanje besedila iz datotek PDF se zdi preprosto, dokler ne naletite na dokument, v katerem besedilna plast manjka, je poškodovana ali razdeljena na desetine majhnih nizov znakov brez smiselnega vrstnega reda. PDFium VCL ponuja dve vstopni točki: polje Character[] za neposreden, na indeksih temelječ dostop do vsakega glifa na strani, in ReadablePageContent za strukturiran pogled, ki rekonstruira odstavke in naslove iz drevesa oznak PDF ali hevristične analize. Nobena od teh možnosti ni vedno prava izbira, zato je pomembno razumeti, kaj posamezna metoda ponuja.
Odpiranje dokumenta in past tihega neuspeha
TPdf odpre datoteko tako, da nastavi FileName in preklopi Active := True. Ključna podrobnost: nastavitev Active := True nikoli ne sproži izjeme. Če datoteka manjka, je zaščitena z geslom ali poškodovana, PDFium interno ujame napako, lastnost Active pa preprosto ostane False. To pomeni, da se mora vsaka zanka za črpanje zaščititi pred tem:
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;
Datoteke, zaščitene z geslom, zahtevajo nastavitev Pdf.Password := ‘...’ pred nastavitvijo Active := True. Druge priložnosti ni: ko Active ne uspe, morate zapreti in znova odpreti s pravilnim geslom.
Črpanje stran za stranjo s Character[]
Najbolj nizkonivojski pristop pregleda vsak znak na vsaki strani. Nastavite Pdf.PageNumber, da naložite besedilno plast za to stran, nato pa se pomaknite skozi vnose CharacterCount z uporabo lastnosti Character[]. Pri vsakem vnosu je smiselno preveriti dve zastavici: CharacterGenerated[i] označuje sintetične glife, ki jih je vstavil upodabljalnik (na primer mehke vezaje ob prelomih vrstic), ki nimajo dejanske vrednosti Unicode, in CharacterMapError[i], ki sporoča, da PDFium ni mogel preslikati glifa v kodno točko, kar se zgodi pri kodiranju pisav brez tabele ToUnicode.
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;
Rezultat je preprost niz kodnih točk Unicode v vrstnem redu, kot jih PDFium našteje, ku je vrstni red, v katerem se pojavijo v toku vsebine, in ni nujno bralni vrstni red od leve proti desni. Za večino dokumentov v latinici, ki so ustvarjeni s standardnimi pisarniškimi orodji, je to povsem primerno. Pri skeniranih dokumentih PDF, ki so bili pretvorjeni z OCR z nenavadnimi zaporedji glifov, ali pri besedilu od desne proti levi, pa je vrstni red lahko napačen. V teh primerih je bolj uporabna funkcija ReadablePageContent.
Strukturirano črpanje z ReadablePageContent
Funkcija ReadablePageContent deluje na višjem nivoju: vrne zapis TPdfReadableContent, katerega polje Fragments vsebuje označene fragmente vsebine, pri čemer vsak vsebuje Kind, ki določa odstavke, naslove, elemente seznama, celice tabel in podobno. Če PDF vsebuje strukturno drevo (preverite Pdf.IsTagged), je vir rosStructure, vrstni red branja pa je zanesljiv. Za neoznačene datoteke PDFium uporabi metodo rosHeuristic, ki združuje znake glede na njihove omejitvene okvirje v verjetne bralne enote, vendar ne more jamčiti natančnosti.
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;
Če je Content.Source = rosHeuristic in je vaš izhod popačen, besedilna plast dokumenta verjetno ni bila zapisana z mislijo na bralni vrstni red. V tem primeru je edina zanesljiva rešitev ponoven izvoz iz izvorne aplikacije s pravilnim označevanjem ali izvedba koraka poobdelave, ki razvrsti izhodišča znakov po osi Y in nato po osi X.
Kaj omogočata CharacterOrigin in CharacterRectangle
Obe lastnosti vračata položaj znaka v prostoru strani (točke, izhodišče v spodnjem levem kotu, os Y narašča navzgor). CharacterOrigin[i] je sidrna točka osnovne črte glifa; CharacterRectangle[i] je celoten omejitveni okvir. To so gradniki za vse, kar presega navadno besedilo: zaznavanje meja stolpcev, združevanje znakov v vrstice s primerjavo koordinat Y znotraj tolerance ali izdelava zemljevida klikov za izbiro besedila v pregledovalniku. Če želite ugotoviti, kateri znak se nahaja pod klikom miške, funkcija CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) opravi to poizvedbo neposredno, ne da bi vam bilo treba pregledovati pravokotnike.
Namestitev datoteke DLL
PDFium VCL prenese celotno razčlenjevanje PDF na izvorno knjižnico DLL, bodisi pdfium32.dll or pdfium64.dll, odvisno od vaše ciljne platforme. Komponenta vsebuje skript CopyDlls.bat, ki kopira pravilno datoteko v sistemski imenik Windows. Enkratni zagon kot skrbnik na razvojnem računalniku zadostuje; za namestitev pri strankah pa kopirajte knjižnico DLL poleg same izvršljive datoteke aplikacije. Različici s podporo za V8 (pdfium32v8.dll, pdfium64v8.dll) sta bistveno večji in potrebni le, če vaši PDF-ji vsebujejo kodo JavaScript, ki jo je treba izvesti. Za čisto črpanje besedila je standardna različica prava izbira.
Če knjižnica DLL ob zagonu ni prisotna, nastavitev Active := True tiho ne uspe, enako kot pri manjkajoči datoteki, saj komponenta vrne napako pri nalaganju. Pred izdajo aplikacijo vedno preizkusite na čistem računalniku.
Uporaba FontSize[] skupaj s Character[] za analizo postavitve
Poleg navadnega besedila API na ravni znakov ponuja lastnost FontSize[i], ki vrne izrisano velikost vsakega glifa v točkah. V kombinaciji s CharacterOrigin[i] in CharacterRectangle[i] vam to omogoča razlikovanje med osnovnim besedilom in naslovi brez uporabe strukturnega drevesa. Zaporedje znakov, kjer velikost pisave skoči nad določen prag, je v neoznačenem dokumentu skoraj zagotovo naslov. Isti postopek velja za zaznavanje napisov (majhno besedilo pod omejitvenim okvirjem slike) ali opomb (majhno besedilo blizu dna strani). Nič od tega ne zahteva izrisovanja; vse tri lastnosti berejo neposredno iz besedilne plasti, ki jo PDFium zgradi med izvajanjem Active := True.
Ena podrobnost: FontSize[i] odraža velikost po uporabi matrike transformacije strani (CTM), zato bo dokument, v katerem je avtor spremenil merilo celotne strani, vrnil sorazmerno prilagojene velikosti. Če primerjate velikosti na straneh z različnimi dimenzijami, jih pred odločanjem o pragovih normalizirajte glede na višino MediaBox posamezne strani.
Zapisovanje izhoda v datoteko
Delphijev razred TStringList od različice XE dalje čisto upravlja z izhodom UTF-8. Nastavite WriteBOM := False, če potrebujete datoteko brez oznake BOM (mnogi zunanji sistemi jo pričakujejo):
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;
Za zelo velike dokumente, kjer je pomemben pomnilnik, pišite neposredno v TStreamWriter s kodiranjem TEncoding.UTF8 znotraj zanke strani, namesto da bi vse najprej kopičili v seznamu.
Tukaj prikazani vmesniki API Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent in CharacterIndexAtPos so del komponente PDFium VCL Component za Delphi in C++Builder.