Ekstrakcija teksta iz PDF-a izgleda jednostavno sve dok ne naiđete na dokument u kojem je tekstualni sloj odsutan, oštećen ili podijeljen na desetke sitnih blokova znakova bez ikakvog smislenog redoslijeda. PDFium VCL vam pruža dvije pristupne točke: polje Character[] za izravan pristup svakom glifu na stranici na temelju indeksa i ReadablePageContent za strukturirani prikaz koji rekonstruira odlomke i naslove iz PDF-ovog stabla oznaka (tag tree) ili heurističke analize. Nijedna od njih nije uvijek jedini ispravan izbor, stoga je važno razumjeti što koja izlaže.
Otvaranje dokumenta i zamka s tihim neuspjehom
TPdf otvara datoteku postavljanjem svojstva FileName i prebacivanjem Active := True. Ključni detalj: Active := True nikada ne podiže iznimku. Ako datoteka nedostaje, zaštićena je lozinkom ili je oštećena, PDFium interno hvata pogrešku i Active jednostavno ostaje na False. To znači da se svaka petlja za ekstrakciju mora zaštititi od ovoga:
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štićene lozinkom zahtijevaju da se postavi Pdf.Password := '...' prije prebacivanja na Active := True. Nema druge prilike: kada Active jednom ne uspije, zatvarate i ponovno otvarate dokument s ispravnom lozinkom.
Ekstrakcija stranicu po stranicu pomoću Character[]
Pristup najniže razine prolazi kroz svaki znak na svakoj stranici. Postavite Pdf.PageNumber kako biste učitali tekstualni sloj za tu stranicu, a zatim prođite kroz unose do CharacterCount pomoću svojstva Character[]. Dvije zastavice na svakom unosu vrijedi provjeriti: CharacterGenerated[i] označava sintetičke glifove koje je umetnuo renderer (primjerice, meke crtice kod prijeloma retka) koji nemaju stvarnu Unicode vrijednost, i CharacterMapError[i] koja signalizira da PDFium nije mogao mapirati glif u kodnu točku, što se događa s kodiranjima fontova kojima nedostaje tablica 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 jednostavan niz Unicode kodnih točaka redoslijedom kojim ih PDFium nabraja, a to je redoslijed kojim se pojavljuju u toku sadržaja, a ne nužno redoslijed čitanja s lijeva na desno. Za većinu dokumenata s latiničnim pismom koje su izradili standardni uredski alati to je u redu. Za skenirane PDF-ove koji su prošli OCR s neobičnim sekvencama glifova ili za tekst s desna na lijevo, redoslijed može biti pogrešan. U tim je slučajevima ReadablePageContent korisniji.
Strukturirana ekstrakcija pomoću ReadablePageContent
Metoda ReadablePageContent ide korak više: vraća zapis TPdfReadableContent čije polje Fragments nosi označene fragmente sadržaja, od kojih svaki ima Kind koji identificira odlomke, naslove, stavke popisa, ćelije tablice i tako dalje. Kada PDF nosi stablo strukture (provjerite Pdf.IsTagged), izvor je rosStructure i redoslijed čitanja je mjerodavan. Za neoznačene datoteke, PDFium se vraća na rosHeuristic, što grupira znakove prema njihovim graničnim okvirima (bounding boxes) u uvjerljive cjeline za čitanje, ali ne može jamčiti točnost.
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;
Ako je Content.Source = rosHeuristic, a vaš izlaz izgleda zbrkano, tekstualni sloj dokumenta vjerojatno nije napisan imajući na umu redoslijed čitanja. U tom je slučaju jedini pouzdani popravak ponovni izvoz iz izvorne aplikacije s pravilnim označavanjem ili pokretanje koraka naknadne obrade koji sortira ishodišta znakova po osi Y, a zatim X.
Što vam daju CharacterOrigin i CharacterRectangle
Oba svojstva vraćaju poziciju znaka u prostoru stranice (točke, ishodište u donjem lijevom kutu, os Y raste prema gore). CharacterOrigin[i] je sidrišna točka osnovne linije glifa; CharacterRectangle[i] je puni granični okvir. To su gradivni blokovi za sve što nadilazi običan tekst: prepoznavanje granica stupaca, grupiranje znakova u retke usporedbom Y koordinata unutar tolerancije ili izgradnja karte pogodaka za označavanje teksta u pregledniku. Ako trebate saznati koji se znak nalazi ispod klika mišem, CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) dohvata tu pretragu izravno bez potrebe da prolazite kroz pravokutnike.
Postavljanje DLL-a na mjesto
PDFium VCL delegira cjelokupnu analizu PDF-a nativnom DLL-u, bilo pdfium32.dll ili pdfium64.dll, ovisno o vašoj ciljnoj platformi. Komponenta dolazi s CopyDlls.bat skriptom koja kopira ispravnu datoteku u sistemski direktorij sustava Windows. Pokretanje te skripte jednom kao administrator na razvojnom računalu je dovoljno; za isporuku kopirate DLL uz izvršnu datoteku aplikacije. Verzije s omogućenim V8 mehanizmom (pdfium32v8.dll, pdfium64v8.dll) znatno su veće i potrebne su samo ako vaši PDF-ovi sadrže JavaScript koji se mora izvršiti. Za čistu ekstrakciju teksta, standardna verzija je ispravan izbor.
Ako DLL nedostaje u vrijeme izvođenja, Active := True tiho će zakazati baš kao i kod nedostajuće datoteke jer komponenta interno hvata pogrešku učitavanja. Prije isporuke uvijek testirajte na čistom računalu.
Korištenje FontSize[] uz Character[] za analizu izgleda
Nakon običnog teksta, API na razini znakova izlaže FontSize[i], koji vraća veličinu svake iscrtane točke glifa. U kombinaciji s CharacterOrigin[i] i CharacterRectangle[i], to vam omogućuje da razlikujete glavni tekst od naslova bez oslanjanja na stablo strukture. Blok znakova u kojem veličina fonta skače iznad praga gotovo je sigurno naslov u neoznačenom dokumentu. Isti se postupak primjenjuje na otkrivanje opisa (mali tekst ispod graničnog okvira slike) ili fusnota (mali tekst blizu dna stranice). Ništa od ovoga ne zahtijeva renderiranje; sva tri svojstva čitaju izravno iz tekstualnog sloja koji PDFium gradi tijekom rada svojstva Active := True.
Jedna nijansa: FontSize[i] odražava veličinu nakon što se primijeni CTM stranice (current transformation matrix), pa će dokument u kojem je autor skalirao cijelu stranicu prijaviti proporcionalno prilagođene veličine. Ako uspoređujete veličine na stranicama s različitim dimenzijama, normalizirajte vrijednosti prema visini MediaBox-a svake stranice prije donošenja odluka o pragu.
Zapisivanje izlaza u datoteku
Delphijev TStringList čisto obrađuje UTF-8 izlaz od verzije XE. Postavite WriteBOM := False ako trebate datoteku bez BOM oznake (mnogi drugi alati to očekuju):
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 vrlo velike dokumente gdje je memorija problem, pišite izravno u TStreamWriter s TEncoding.UTF8 unutar petlje stranice umjesto da najprije sve skupljate u popis.
API-ji Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent i CharacterIndexAtPos prikazani ovdje dio su komponente PDFium VCL Component za Delphi i C++Builder.