Udtrækning af PDF-tekst virker simpelt, indtil du støder på et dokument, hvor tekstlaget mangler, er beskadiget eller er opdelt i snesevis af bittesmå tegnforløb uden nogen meningsfuld rækkefølge. PDFium Component giver dig to indgangspunkter: Character[]-arrayet for rå, indeksbaseret adgang til hver enkelt glyph på en side, og ReadablePageContent for en struktureret visning, der genskaber afsnit og overskrifter ud fra PDF'ens tag-træ eller heuristiske analyse. Ingen af dem er altid det rigtige valg, så det betyder noget at forstå, hvad hver af dem eksponerer
Åbning af dokumentet og fælden med lydløse fejl
TPdf åbner en fil ved at indstille FileName og slå Active := True til. Den afgørende detalje: Active := True udløser aldrig en undtagelse. Hvis filen mangler, er adgangskodebeskyttet eller beskadiget, fanger PDFium fejlen internt, og Active forbliver blot False. Det betyder, at enhver udtrækningsløkke skal beskytte sig mod dette:
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;
Adgangskodebeskyttede filer kræver, at Pdf.Password := '...' er sat, før Active := True. Der er ingen anden chance: Når Active fejler, lukker og genåbner du med den korrekte adgangskode
Side-for-side udtrækning med Character[]
Tilgangen på laveste niveau gennemgår hvert enkelt tegn på hver side. Indstil Pdf.PageNumber for at indlæse tekstlaget for den side, og gentag derefter over CharacterCount-poster ved hjælp af Character[]-egenskaben. To flag på hver post er værd at kontrollere: CharacterGenerated[i] markerer syntetiske glypher indsat af rendereren (f.eks. bløde bindestreger ved linjeskift), der ikke har nogen reel Unicode-værdi, og CharacterMapError[i] signalerer, at PDFium ikke kunne kortlægge glyphen til et kodepunkt, hvilket sker med skrifttypekodninger, der mangler en ToUnicode-tabel
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;
Resultatet is a flat string of Unicode code points in the order PDFium enumerates them, which is the order they appear in the content stream, not necessarily left-to-right reading order. For most Latin-script documents produced by standard office tools this is fine. For scanned PDFs that were OCR’d with unusual glyph sequences, or for right-to-left text, the ordering can be wrong. That is when ReadablePageContent becomes more useful
Struktureret udtrækning med ReadablePageContent
ReadablePageContent går et niveau op: Det returnerer en TPdfReadableContent-post, hvis Fragments-array bærer taggede indholdsfragmenter, hver med en Kind, der identificerer afsnit, overskrifter, listepunkter, tabelceller og så videre. Når PDF'en bærer et strukturelt træ (kontroller Pdf.IsTagged), er kilden rosStructure, og læsererækkefølgen er autoritativ. For utaggede filer falder PDFium tilbage på rosHeuristic, som grupperer tegn efter deres afgrænsningsrammer (bounding boxes) i sandsynlige læseenheder, men kan ikke garantere nøjagtighed
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;
Hvis Content.Source = rosHeuristic, og dit output ser forvansket ud, blev dokumentets tekstlag sandsynligvis ikke skrevet med læserækkefølge for øje. På det tidspunkt er den eneste pålidelige løsning at eksportere igen fra kildeprogrammet med korrekt tag-mærkning eller at køre et efterbehandlingstrin, der sorterer tegnernes oprindelse efter Y og derefter X
Hvad CharacterOrigin og CharacterRectangle giver dig
Begge egenskaber returnerer placeringen af et tegn i siderummet (punkter, oprindelse i nederste venstre hjørne, Y stigende opad). CharacterOrigin[i] er glyph'ens grundlinje-forankringspunkt; CharacterRectangle[i] er den fulde afgrænsningsramme. Disse er byggestenene til alt ud over ren tekst: Registrering af spaltegrænser, gruppering af tegn i linjer ved at sammenligne Y-koordinater inden for en tolerance, eller opbygning af et hit-test-kort til tekstvalg i en fremviser. Hvis du har brug for at finde, hvilket tegn der sidder under et museklik, CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) foretager dette opslag direkte uden, at du skal løbe rektangler igennem
Sådan får du lagt DLL'en på plads
PDFium Component uddelegerer al PDF-fortolkning til en indfødt DLL, enten pdfium32.dll eller pdfium64.dll afhængigt af din målplatform. Komponenten leveres med et CopyDlls.bat-script, der kopierer den rigtige fil til Windows-systemmappen. Det er nok at køre det som administrator én gang på en udviklingsmaskine; til distribution kopierer du i stedet DLL'en ved siden af programmets eksekverbare fil. De V8-aktiverede varianter (pdfium32v8.dll, pdfium64v8.dll) er væsentligt større og kun nødvendige, hvis dine PDF-filer indeholder JavaScript, der skal køres. Til ren tekstudtrækning er standardversionen det rigtige valg
Hvis DLL'en mangler under kørslen, vil Active := True fejle lydløst, ligesom det gør for en manglende fil, fordi komponenten fanger indlæsningsfejlen internt. Test altid på en ren maskine, før du distribuerer
Brug FontSize[] sammen med Character[] til layoutanalyse
Ud over ren tekst eksponerer API'en på tegnniveau FontSize[i], som returnerer den rendered punktstørrelse for hver glyph. Kombineret med CharacterOrigin[i] og CharacterRectangle[i] lader dette dig skelne brødtekst fra overskrifter uden at forlade dig på strukturens tag-træ. Et tegnforløb, hvor skriftstørrelsen springer op over en tærskel, er næsten helt sikkert en overskrift i et utagget dokument. Den samme teknik gælder for registrering af billedtekster (lille tekst under et billedes afgrænsningsramme) eller fodnoter (lille tekst nær bunden af siden). Intet af dette kræver rendering; alle tre egenskaber læser direkte fra det tekstlag, som PDFium bygger under Active := True
En enkelt nuance: FontSize[i] afspejler størrelsen, efter at sidens CTM (current transformation matrix) er anvendt, so a document where the author scaled the entire page will report sizes proportionally adjusted. Hvis du sammenligner størrelser på tværs af sider med forskellige sidedimensioner, skal du normalisere i forhold til hver sides MediaBox-højde før du træffer beslutninger om tærskelværdier
Skrivning af outputtet til en fil
Delphis TStringList håndterer UTF-8-output fejlfrit siden XE. Indstil WriteBOM := False, hvis du har brug for en fil uden BOM (mange efterfølgende systemer fejler på en foranstillet 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;
For meget store dokumenter, hvor hukommelsen er en bekymring, skal du skrive direkte til en TStreamWriter med TEncoding.UTF8 inde i sideløkken i stedet for at samle alt i en liste først
API'erne Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent og CharacterIndexAtPos, der er vist her, er en del af PDFium Component til Delphi og C++Builder