Textextrahering från PDF ser enkelt ut tills du stöter på ett dokument där textlagret saknas, är korrupt eller är uppdelat över dussintals små teckenkörningar utan någon meningsfull ordning. PDFium Component ger dig två ingångar: vektorn Character[] för rå, indexbaserad åtkomst till varje glyf på en sida, och ReadablePageContent för en strukturerad vy som återskapar stycken och rubriker från PDF:ens taggträd eller heuristisk analys. Ingen av dem är alltid rätt val, så det är viktigt att förstå vad var och en av dem exponerar
Öppna dokumentet och det tysta fel-skyddets fälla
TPdf öppnar en fil genom att sätta FileName och växla Active := True. Den kritiska detaljen: Active := True kastar aldrig ett undantag. Om filen saknas, är lösenordsskyddad eller korrupt, fångar PDFium felet internt och Active förblir helt enkelt False. Det betyder att varje extraheringsslinga måste skydda mot detta:
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;
Lösenordsskyddade filer behöver Pdf.Password := '...' sättas innan Active := True. Det finns ingen andra chans: när Active misslyckas, stänger du och öppnar igen med rätt lösenord
Sida-för-sida extrahering med Character[]
Den lägsta nivån går igenom varje tecken på varje sida. Sätt Pdf.PageNumber för att ladda textlagret för den sidan, iterera sedan CharacterCount poster med hjälp av egenskapen Character[]. Det finns två flaggor på varje post som är värda att kontrollera: CharacterGenerated[i] markerar syntetiska glyfer insatta av renderaren (mjuka bindestreck vid radbrytningar, till exempel) som inte har något verkligt Unicode-värde, och CharacterMapError[i] signalerar att PDFium inte kunde mappa glyfen till en kodpunkt, vilket händer med teckensnittskodningar som saknar en ToUnicode-tabell
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 är en platt sträng av Unicode-kodpunkter i den ordning PDFium räknar upp dem, vilket är den ordning de visas i innehållsströmmen, inte nödvändigtvis i läsordning från vänster till höger. För de flesta dokument med latinsk skrift producerade av standardkontorsverktyg är detta okej. För skannade PDF-filer som har OCR-behandlats med ovanliga glyf-sekvenser, eller för höger-till-vänster-text, kan ordningen vara fel. Det är då ReadablePageContent blir mer användbar
Strukturerad extrahering med ReadablePageContent
ReadablePageContent går en nivå upp: den returnerar en TPdfReadableContent-post vars Fragments-vektor innehåller taggade innehållsfragment, var och en med en Kind som identifierar stycken, rubriker, listobjekt, tabellceller och så vidare. När PDF:en har ett strukturträd (kontrollera Pdf.IsTagged), är källan rosStructure och läsordningen är auktoritativ. För otaggade filer faller PDFium tillbaka till rosHeuristic, vilket grupperar tecken efter deras avgränsningsrutor till rimliga läsenheter men kan inte garantera korrekthet
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;
Om Content.Source = rosHeuristic och din utdata ser förvanskad ut, skrevs dokumentets textlager förebyggande inte med läsordning i åtanke. Vid den punkten är den enda pålitliga lösningen att exportera om från källapplikationen med korrekt taggning, eller köra ett efterbearbetningssteg som sorterar tecknens ursprung efter Y och sedan X
Vad CharacterOrigin och CharacterRectangle ger dig
Båda egenskaperna returnerar positionen för ett tecken i sidrymden (punkter, med ursprung i det nedre vänstra hörnet, Y ökar uppåt). CharacterOrigin[i] är glyfens baslinje-ankarpunkt; CharacterRectangle[i] är hela avgränsningsrutan. Dessa är byggstenarna för allt utöver vanlig text: upptäcka kolumngränser, gruppera tecken i rader genom att jämföra Y-koordinater inom en tolerans, eller bygga en träfftestkarta för textval i en visare. Om du behöver hitta vilket tecken som sitter under ett musklick, gör CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) den sökningen direkt utan att du behöver iterera rektanglar
Få DLL:en på plats
PDFium Component delegerar all PDF-tolkning till en inbyggd DLL, antingen pdfium32.dll eller pdfium64.dll beroende på din målplattform. Komponenten levereras med ett CopyDlls.bat-skript som kopierar rätt fil till Windows systemkatalog. Att köra det som administratör en gång på en utvecklingsmaskin räcker; för distribution kopierar du DLL:en vid sidan av applikationens körbara fil istället. De V8-aktiverade varianterna (pdfium32v8.dll, pdfium64v8.dll) är betydligt större och är endast nödvändiga om dina PDF-filer innehåller JavaScript som måste köras. För ren textextrahering är standardbygget det rätta valet
Om DLL:en saknas vid körning kommer Active := True att misslyckas tyst precis som det gör för en saknad fil, eftersom komponenten fångar laddningsfelet internt. Testa alltid på en ren maaskin innan leverans
Använda FontSize[] tillsammans med Character[] för layoutanalys
Utöver vanlig text exponerar API:et på teckennivå FontSize[i], vilket returnerar den renderade punktstorleken för varje glyf. I kombination med CharacterOrigin[i] och CharacterRectangle[i] låter detta dig skilja brödtext från rubriker utan att förlita dig på strukturträdet. En teckenkörning där teckenstorleken hoppar över ett tröskelvärde är med största sannolikhet en rubrik i ett otaggat dokument. Samma teknik gäller för att upptäcka bildtexter (liten text under en bilds avgränsningsruta) eller fotnoter (liten text nära sidans botten). Inget av detta kräver rendering; alla tre egenskaperna läser direkt från textlagret som PDFium bygger under Active := True
En nyansering: FontSize[i] återspeglar storleken efter att sidans CTM (aktuell transformationsmatris) har applicerats, så ett dokument där författaren skalade hela sidan kommer att rapportera storlekar som är proportionellt justerade. Om du jämför storlekar över sidor med olika sidodimensioner, normalisera mot varje sidas MediaBox-höjd innan du fattar tröskelbeslut
Skriva utdata till en fil
Delphis TStringList hanterar UTF-8-utdata rent sedan XE. Sätt WriteBOM := False om du behöver en BOM-fri fil (många nedströms konsumenter kvävs på en inledande 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;
För mycket stora dokument där minnet är ett bekymmer, skriv direkt till en TStreamWriter med TEncoding.UTF8 inuti sidloopen istället för att ackumulera allt i en lista först
API:erna Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent och CharacterIndexAtPos som visas här är en del av PDFium Component för Delphi och C++Builder