PDF teksto išgavimas atrodo paprastas, kol nesusiduriate su dokumentu, kuriame teksto sluoksnio nėra, jis sugadintas arba padalintas į dešimtis mažų simbolių sekų be jokios prasmingos tvarkos. PDFium komponentas suteikia du įėjimo taškus: Character[] masyvą tiesioginei, indeksais pagrįstai prieigai prie kiekvieno puslapio glifo ir ReadablePageContent struktūruotam vaizdui, kuris atkuria pastraipas ir antraštes iš PDF žymų medžio arba euristinės analizės. Nė vienas iš jų ne visada yra teisingas pasirinkimas, todėl svarbu suprasti, ką kiekvienas atskleidžia
Dokumento atidarymas ir tylaus gedimo spąstai
TPdf atidaro failą nustatydamas FileName ir perjungdamas Active := True. Kritinė detalė: Active := True niekada nesukelia išimties. Jei failo nėra, jis apsaugotas slaptažodžiu arba sugadintas, PDFium klaidą sugauna viduje, o Active tiesiog lieka False. Tai reiškia, kad kiekvienas išgavimo ciklas turi nuo to apsisaugoti:
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;
Slaptažodžiu apsaugotiems failams reikia nustatyti Pdf.Password := '...' prieš Active := True. Antros galimybės nėra: kai Active nepavyksta, turite uždaryti ir vėl atidaryti naudodami teisingą slaptažodį
Puslapis po puslapio išgavimas naudojant Character[]
Žemiausio lygio metodas pereina kiekvieną simbolį kiekviename puslapyje. Nustatykite Pdf.PageNumber, kad įkeltumėte to puslapio teksto sluoksnį, tada iteruokite CharacterCount įrašus naudodami Character[] savybę. Verta patikrinti dvi kiekvieno įrašo žymas: CharacterGenerated[i] žymi sintetinius glifus, kuriuos įterpė atvaizduotojas (pavyzdžiui, minkštieji brūkšneliai eilučių lūžiuose), neturinčius tikros Unicode reikšmės, o CharacterMapError[i] signalizuoja, kad PDFium negalėjo susieti glifo su kodo tašku, kas nutinka su šriftų koduotėmis, neturinčiomis ToUnicode lentelės
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;
Rezultatas yra plokščia Unicode kodo taškų eilutė ta tvarka, kuria PDFium juos išvardija, t. y. ta tvarka, kuria jie atsiranda turinio sraute, o ne būtinai skaitymo iš kairės į dešinę tvarka. Daugumai lotyniškais rašmenimis parašytų dokumentų, sukurtų standartiniais biuro įrankiais, tai tinka. Nuskenuotiems PDF failams, kuriems buvo pritaikytas OCR su neįprastomis glifų sekomis, arba tekstui iš dešinės į kairę, tvarka gali būti neteisinga. Būtent tada ReadablePageContent tampa naudingesnis
Struktūruotas išgavimas naudojant ReadablePageContent
ReadablePageContent pakyla vienu lygiu aukščiau: jis grąžina TPdfReadableContent įrašą, kurio Fragments masyvas neša pažymėtus turinio fragmentus, kiekvienas su Kind, identifikuojančiu pastraipas, antraštes, sąrašo elementus, lentelės langelius ir pan. Kai PDF turi struktūros medį (patikrinkite Pdf.IsTagged), šaltinis yra rosStructure, o skaitymo tvarka yra autoritetinga. Nepažymėtiems failams PDFium naudoja rosHeuristic, kuris sugrupuoja simbolius pagal jų ribojamąsias dėžutes į tikėtinus skaitymo vienetus, bet negali garantuoti tikslumo
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;
Jei Content.Source = rosHeuristic and jūsų išvestis atrodo iškraipyta, dokumento teksto sluoksnis greičiausiai nebuvo parašytas atsižvelgiant į skaitymo tvarką. Tokiu atveju vienintelis patikimas sprendimas yra eksportuoti jį iš naujo iš šaltinio programos su tinkamu žymėjimu arba paleisti po apdorojimo veiksmą, kuris surūšiuoja simbolių pradžios taškus pagal Y, tada pagal X
Ką jums suteikia CharacterOrigin ir CharacterRectangle
Abi savybės grąžina simbolio padėtį puslapio erdvėje (taškais, pradžia apatiniame kairiajame kampe, Y didėja į viršų). CharacterOrigin[i] yra glifo bazinės linijos inkaro taškas; CharacterRectangle[i] yra visa ribojamoji dėžutė. Tai yra pagrindiniai elementai bet kam, kas peržengia paprasto teksto ribas: aptikti stulpelių ribas, grupuoti simbolius į eilutes lyginant Y koordinates tolerancijos ribose arba sukurti pataikymo testo žemėlapį teksto pasirinkimui peržiūros programoje. Jei reikia sužinoti, kuris simbolis yra po pelės paspaudimu, CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) atlieka šią paiešką tiesiogiai, jums nereikia iteruoti per stačiakampius
DLL įdiegimas
PDFium komponentas deleguoja visą PDF analizę vietiniam DLL, arba pdfium32.dll, arba pdfium64.dll, priklausomai nuo jūsų tikslinės platformos. Komponentas pateikiamas su CopyDlls.bat skriptu, kuris nukopijuoja reikiamą failą į Windows sistemos katalogą. Vieno paleidimo administratoriaus teisėmis kūrimo mašinoje pakanka; diegimui vietoje to nukopijuojate DLL šalia programos vykdomojo failo. Variantai su V8 (pdfium32v8.dll, pdfium64v8.dll) yra gerokai didesni ir reikalingi tik tuo atveju, jei jūsų PDF failuose yra JavaScript, kurį reikia vykdyti. Grynajam teksto išgavimui standartinė versija yra tinkamas pasirinkimas
Jei DLL nėra vykdymo metu, Active := True tyliai nepavyks, kaip ir trūkstamo failo atveju, nes komponentas klaidą sugauna viduje. Visada išbandykite švarioje mašinoje prieš išleidžiant
FontSize[] naudojimas kartu su Character[] maketo analizei
Be paprasto teksto, simbolio lygio API atskleidžia FontSize[i], kuris grąžina kiekvieno glifo atvaizduojamą taško dydį. Kartu su CharacterOrigin[i] ir CharacterRectangle[i], tai leidžia atskirti pagrindinį tekstą nuo antraščių nepasikliaujant struktūros medžiu. Simbolių seka, kurioje šrifto dydis šokteli virš ribos, beveik neabejotinai yra antraštė nepažymėtame dokumente. Ta pati technika taikoma aptinkant antraštes po paveikslėliu (mažas tekstas po paveikslėlio ribojamąja dėžute) arba išnašas (mažas tekstas puslapio apačioje). Nė vienas iš šių dalykų nereikalauja atvaizdavimo; visos trys savybės skaitomos tiesiai iš teksto sluoksnio, kurį PDFium sukuria per Active := True
Vienas niuansas: FontSize[i] atspindi dydį pritaikius puslapio CTM (dabartinę transformacijos matricą), todėl dokumente, kurio autorius mastelino visą puslapį, bus pranešti proporcingai pakoreguoti dydžiai. Jei lyginate dydžius skirtinguose puslapiuose su skirtingais puslapio matmenimis, prieš priimdami sprendimus dėl slenksčių, normalizuokite pagal kiekvieno puslapio MediaBox aukštį
Išvesties įrašymas į failą
Delphi TStringList švariai tvarko UTF-8 išvestį nuo XE versijos. Nustatykite WriteBOM := False, jei jums reikia failo be BOM (daugelis tolesnių vartotojų užspringsta dėl pradinio BOM):
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;
Labai dideliems dokumentams, kur atmintis yra problema, rašykite tiesiai į TStreamWriter su TEncoding.UTF8 puslapio ciklo viduje, užuot viską kaupę sąraše
Čia parodyti Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent ir CharacterIndexAtPos API yra PDFium komponento, skirto Delphi ir C++Builder, dalis