PDFium Component tilføjer et søgbart tekstlag til scannede PDF-sider fra Delphi via ApplyOcrSearchLayer. Den renderer hver valgt side, overdrager pixlerne til en OCR-udbyder, du selv leverer, og skriver de genkendte ord tilbage som usynlige tekstobjekter placeret oven på ordene i scanningen. Det oprindelige sidebillede afkodes, genkodes eller erstattes aldrig, så det visuelle resultat er byte-for-byte den side, du startede med
Genkendelsesmotoren er bevidst ikke en del af biblioteket. PDFium eksponerer siderendering, koordinatafbildning, skrifttypeindlæsning, oprettelse af tekstobjekter og usynlige rendertilstande, men den indeholder ingen OCR-motor, og at lade som andet ville betyde at bundte en andens genkendelsesprodukt ind i en PDF-komponent. I stedet bor genkendelse bag grænsefladen IPdfOcrProvider: biblioteket sender fastlayoutede, top-oprindelige BGRA-pixels, og udbyderen returnerer Unicode-tekst, konfidensværdier og ordfirkanter
Hvad er et søgbart tekstlag egentlig?
En scannet PDF er et billede af et dokument. Sideindholdet er ét stort billede, og der er intet at markere, søge i, kopiere eller indeksere. Et søgbart tekstlag tilføjer rigtige tekstobjekter oven på det billede med rendertilstanden sat til usynlig, så viewere ikke tegner noget, men markering, søgning og udtræk finder ordene præcis, hvor de optræder
Positionering er hele spillet. Hvis den usynlige tekst sidder nogle punkter forskudt, lander markeringsfremhævelser ved siden af ordene frem for på dem, og at kopiere et afsnit producerer tekst i den forkerte rækkefølge. Det er derfor, geometrien skal komme fra de samme transformationer, PDFium bruger til at rendere siden, frem for fra et proportionalt gæt
Implementering af udbyderen
Udbyderkontrakten er én metode. Den modtager en sidebillede-post, der bærer dimensioner, stride, DPI, pixelformat og selve pixel-bytene, plus en annulleringstoken, og returnerer ord eller en fejlmeddelelse:
uses
PDFium;
type
TMyOcrProvider = class(TInterfacedObject, IPdfOcrProvider)
public
function RecognizePage(const Image: TPdfOcrImage;
const CancellationToken: IPdfCancellationToken;
out Words: TPdfOcrWords; out ErrorMessage: string): Boolean;
end;
function TMyOcrProvider.RecognizePage(const Image: TPdfOcrImage;
const CancellationToken: IPdfCancellationToken;
out Words: TPdfOcrWords; out ErrorMessage: string): Boolean;
var
I: Integer;
begin
// Image.Pixels indeholder top-oprindelige BGRA-rækker af Image.Stride bytes.
// Send dem til din motor, og udfyld derefter én post pr. genkendt ord
SetLength(Words, RecognisedCount);
for I := 0 to RecognisedCount - 1 do
begin
Words[I].Text := EngineWordText(I);
Words[I].Confidence := EngineWordConfidence(I); // 0..1
Words[I].Quad := TPdfOcrQuad.FromRectangle(
EngineLeft(I), EngineTop(I), EngineRight(I), EngineBottom(I));
end;
ErrorMessage := '';
Result := True;
end;
Quads frem for rektangler, fordi en scanning sjældent er kvadratisk med siden. Et ord på en let roteret side optager et parallelogram, og TPdfOcrQuad bærer fire hjørnepunkter, så skæve og roterede ord bevarer en nøjagtig markeringsregion. Motorer, der kun rapporterer akse-justerede bokse, kan bruge FromRectangle, som bygger den degenererede quad
Hvorfor kan ordpositioner ikke skaleres proportionalt?
Det er fristende at konvertere en pixelkoordinat til en sidekoordinat ved at dividere med renderbredden og gange med sidebredden. Det virker kun for sider uden rotation, en CropBox identisk med MediaBox, og en oprindelse ved nul, og mange scannede dokumenter fejler mindst én af disse betingelser
PDFium Component afbilder hvert af de fire quad-hjørner individuelt via FPDF_DeviceToPage, den samme afbildning, rendereren brugte til at producere pixlerne, så /Rotate-poster og forskudte crop-bokse håndteres ved konstruktion. Den affine matrix for tekstobjektet bygges derefter ud fra tre af de afbildede punkter, nederste venstre, nederste højre og øverste venstre hjørne, hvilket er præcis nok til at udtrykke position, skalering, rotation og skævhed
Selve tekstobjektet oprettes med enheds-skriftstørrelse, så dets reelle skrifttypegrænser kan måles, og de målte objektgrænser afbildes derefter på mål-quadden. At dimensionere ud fra en gættet punktstørrelse og håbe, den matcher det scannede ord, ville drive med hver skrifttypesubstitution; at måle først gør tilpasningen uafhængig af, hvilken skrifttype laget bruger
At køre det over et dokument
Indstillingsposten styrer opløsning, filtrering og hvert budget. Konfidensfiltrering betyder mere, end det ser ud til: skrammelord ved lav konfidens forurener søgeresultater permanent, og i modsætning til en forkert rendering bemærker ingen det, før en søgning returnerer noget meningsløst:
var
Pdf: TPdf;
Options: TPdfOcrOptions;
Report: TPdfOcrReport;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'scanned-contract.pdf';
Pdf.LoadDocument;
Options := TPdfOcrOptions.Default;
Options.Dpi := 300; // genkendelsesopløsning
Options.MinConfidence := 0.60; // drop usikre ord
Options.SkipPagesWithText := True; // lad født-digitale sider være
Options.ContinueOnError := True; // én dårlig side må ikke stoppe jobbet
Options.MaxPixelsPerPage := 40 * 1000 * 1000;
if Pdf.ApplyOcrSearchLayer(TMyOcrProvider.Create, Options, Report) then
Pdf.SaveAs('scanned-contract-searchable.pdf');
for I := 0 to High(Report.Pages) do
if Report.Pages[I].Status = popsFailed then
Writeln(Format('page %d failed: %s',
[Report.Pages[I].PageNumber, Report.Pages[I].ErrorMessage]));
Writeln(Format('%d word(s) inserted, %d rejected, %d page(s) skipped',
[Report.InsertedWordCount, Report.RejectedWordCount,
Report.SkippedPageCount]));
finally
Pdf.Free;
end;
end;
SkipPagesWithText fortjener vægt i blandede arkiver. En PDF, der allerede bærer rigtig tekst, hvad enten den er født digital eller tidligere behandlet, får et andet tekstlag, hvis man kører OCR over den blindt, og duplikatet gør, at udtræk returnerer hvert ord to gange. Statussen pr. side popsSkippedExistingText fortæller præcis, hvilke sider der blev ladt urørt
Budgetter, annullering og fejlbegrænsning
Enhver mængde, som et fjendtligt eller blot enormt dokument kan oppuste, har et loft: pixels pr. side og i alt, ord pr. side og i alt, og tegn pr. ord. Alle tjekkes, før siden skrives, ikke bagefter, og pixelestimatet beregnes ud fra sidedimensioner og DPI, før noget bitmap allokeres. At hæve DPI fra 150 til 300 firdobler hukommelsen pr. side, så loftet pr. side er den parameter, man skal justere først, når et batch-job begynder at fejle på store formater
Annulleringstokenet tråder gennem hele stien: progressiv rendering, udbyderkaldet og indsættelsesløkken pr. ord. Det betyder, at en bruger, der annullerer under genkendelse af en fil på 400 sider, stopper inden for én side frem for ved slutningen af dokumentet, og det samme token-mønster, brugt andre steder i komponenten, beskrevet i annullérbar progressiv rendering, gælder her uændret
Fejlbegrænsning er pr. side. Biblioteket indsamler de objekthåndtag, det indsatte på en side, og kalder FPDFPage_GenerateContent én gang, efter alle ord er placeret. Hvis noget fejler undervejs, uanset om det er en udbyderfejl eller et skrifttypeproblem, fjernes de objekter, der er indsat på den side, i omvendt rækkefølge, og sideindholdet regenereres, så en mislykket side vender tilbage til sin oprindelige tilstand i stedet for at beholde et halvt tekstlag. Dokumentløkken fortsætter eller stopper derefter i henhold til ContinueOnError, og den aktive side gendannes altid
Verificering af, at billedet reelt var urørt
Det stærkeste tjek til rådighed er også det simpleste: rendér siden før og efter påføring af laget ved samme størrelse, og sammenlign bitmapene. De bør være identiske byte for byte, fordi usynlig tekst ikke tegner noget, og billedstrømmen aldrig blev afkodet. Enhver forskel betyder, at noget andet end tekstlaget ændrede siden
Verificér derefter tekstsiden ved at udtrække fra den behandlede fil og bekræfte, at ordpositioner lander på scanningen. Udtræksstien er den samme, der er beskrevet i udtræk af tekst fra PDF-dokumenter, og til et hurtigt visuelt tjek af justering lader rendering af sider til billeder som i konvertering af PDF-sider til JPEG dig lægge ordbokse oven på scanningen
OCR-lagdeling, rendering, udtræk og redigering kører alle mod det samme dokumentobjekt i Delphi, C++Builder og Lazarus; hele API-fladen er beskrevet på siden PDFium Component til Delphi