Å trekke tekst, bilder og skrifter ut av en eksisterende PDF høres ut som et løst problem helt til du kjører et virkelig korpus gjennom det. Rett en søkeindekserer mot førti tusen kundefiler, og bruddene sorterer seg i noen få gjenkjennelige hauger. Ord løper sammen fordi ingen fortalte uttrekkeren hvor bredt et mellomrom må være for å telle som et ordskille. Andre sider kommer tilbake som volapyk fordi en delmengdeskrift ikke bærer noe kart fra glyffkodene sine til faktiske tegn. Og «firmalogoen» viser seg å være ni separate bildeobjekter stablet bak en myk maske. Ingenting av dette er en feil i biblioteket. Det er forskjellen på å kalle en uttrekksfunksjon og å forstå hva funksjonen kan og ikke kan gjenvinne fra bytene på disk
losLab PDF Library, Pascal-utgaven, gir Delphi- og C++Builder-kode mer enn én måte å lese hver av disse tre strømmene på, og nivåene skiller seg i hva de garanterer. Kunsten er å tilpasse nivået til oppgaven: en søkeindeks, en gjennomgang av sladding og en PDF/A-preflight vil alle ha forskjellige ting ut av den samme siden, og å gripe etter feil kall sløser med innsats eller produserer utdata du ikke kan stole på
Nivåer for tekstuttrekk og hva hvert av dem lover
GetPageText tar en opsjonsverdi fra 0 til 8, og det tallet velger en motor snarere enn et format. Verdiene 0 til 2 kjører en lett runde som holder for en rask forhåndsvisning. Verdiene 3 til 8 går gjennom den oppsettsbevisste motoren, som bygger linjer og avstander opp igjen fra der glyffene faktisk ligger på siden. Innenfor det spennet betyr variasjonene noe: 4 og 6 deler utdataene i ord, 5 og 6 sender ut bredder per glyff, og 7 returnerer ren tekst der skrift, farge og blokkmetadata bevisst er droppet. Opsjon 7 er den du mater en søkeindeks med, siden indeksen vil ha ord og ingenting annet
Ingen opsjonsinnstilling kan redde et dokument som aldri bar informasjonen i utgangspunktet. PDF avbilder tegnkoder på glyffformer, og det eneste som avbilder de kodene tilbake til lesbar tekst, er en skrifts ToUnicode-CMap (ISO 32000-1 §9.10). Når en delmengdeskrift leveres uten en, står enhver uttrekker fast. Dette biblioteket, kopier-og-lim i en leser, en konkurrerende verktøykasse: alle er redusert til å gjette ut fra glyffnavn eller returnere ingenting. Den praktiske responsen er deteksjon, ikke heltedåd. Gi siden lav konfidens og send den til OCR, for å indeksere søppelet stilltiende er verre enn å innrømme at du ikke kan lese det
For de tilfellene de flate opsjonene ikke dekker, egendefinert tokenisering, etterforskning i innholdsstrømmen, en teksttrakt bygd etter dine egne regler, er dekoderen tilgjengelig ett lag ned. TPDFExtractor konstrueres over en sides ressursordbok og skriftsamling. Metoden ExtractTextW kjører rå tekstoperasjoner fra innholdsstrømmen tilbake gjennom det samme skriftmaskineriet for å gjenvinne Unicode, og hendelsen OnFindObject gir deg hvert objekt etter hvert som det strømmer forbi. De fleste koder trenger aldri å nå så dypt. Applikasjonene som gjør det, er de som er glade for at laget er offentlig framfor begravd
Plasserte blokker: enheten for søketreff og gjennomgang av sladding
Ren tekst forteller deg hva siden sier. Før eller siden trenger et produkt også å vite hvor den sier det, slik at det kan framheve et søketreff, tegne en boks rundt en sladdingskandidat eller forankre en annotasjon på rett sted. ExtractPageTextBlocks returnerer et håndtak til en liste over tekstrekker, og hver rekke bærer teksten sin, sin omsluttende ramme og skriftnavnet og størrelsen den ble satt i:
var
Pdf: TPDFlib;
Blocks, I: Integer;
begin
Pdf := TPDFlib.Create;
try
if Pdf.LoadFromFile('contract.pdf', '') <> 1 then
raise Exception.Create('load failed');
Pdf.SelectPage(1);
Blocks := Pdf.ExtractPageTextBlocks(0);
for I := 0 to Pdf.GetTextBlockCount(Blocks) - 1 do
Writeln(Format('%s [%s %.1f pt at %.0f,%.0f]',
[Pdf.GetTextBlockText(Blocks, I),
Pdf.GetTextBlockFontName(Blocks, I),
Pdf.GetTextBlockFontSize(Blocks, I),
Pdf.GetTextBlockBound(Blocks, I, 0),
Pdf.GetTextBlockBound(Blocks, I, 1)]));
Pdf.ReleaseTextBlocks(Blocks);
finally
Pdf.Free;
end;
end;
Én detalj på dette området feller integrasjoner mer enn noen annen. SetTextExtractionArea, SetTextExtractionWordGap og SetTextExtractionOptions er tilstand på dokumentnivå som vedvarer, ikke argumenter du sender per kall. Konfigurer en områdebegrensning for én funksjon, si å lese bare topptekstbåndet for å klassifisere et dokument, og den avkorter stilltiende hvert uttrekk som følger på det samme håndtaket, inkludert de oppsettsbevisste GetPageText-nivåene du griper til senere. Enten nullstill uttrekkstilstanden mellom logiske oppgaver, eller gi hver oppgave sitt eget dokumenthåndtak
Terskelen for ordmellomrom er spaken for den første feilhaugen, ordene som løper sammen. SetTextExtractionWordGap forteller oppsettsmotoren hvor mye vannrett plass, målt mot sidens egen glyffavstand, som skiller ett ord fra det neste. En tett tabell vil ha et mindre mellomrom enn en luftig satt markedsføringsside, så en terskel justert per dokumentklasse slår én global konstant. Den vedvarer på dokumentet som resten av uttrekkstilstanden, så planlegg å sette den bevisst framfor én gang og glemme den
Bilder: originale strømmer, ikke skjermbilder
Den gale måten å hente bilder ut av en PDF på er å rendre siden og beskjære den. Det resampler pikslene, brenner inn enhver rotasjon og kaster bort det originalen var. GetPageImageList teller i stedet opp de faktiske bilderessursene siden refererer til, og hvert element gir tilbake egenskapene sine og sine originale, uberørte data:
var
ImgList, I: Integer;
begin
Pdf.SelectPage(1);
ImgList := Pdf.GetPageImageList(0);
for I := 0 to Pdf.GetImageListCount(ImgList) - 1 do
begin
Writeln(Pdf.GetImageListItemFormatDesc(ImgList, I, 0));
Pdf.SaveImageListItemDataToFile(ImgList, I, 0,
Format('page1-img%.2d.bin', [I]));
end;
Pdf.ReleaseImageList(ImgList);
end;
Sjekk GetImageListItemFormatDesc før du antar noe om et element, for det en side refererer til, er sjelden ett ryddig bilde per synlige illustrasjon. En myk maske dukker opp som sin egen separate oppføring. Den samme XObject-en gjentar seg ofte over mange sider, så fjern duplikater ved innholdshash før du arkiverer en eksport med «alle bilder», ellers skriver du den samme logoen hundre ganger. CMYK-JPEG-er trenger fargestyring anvendt nedstrøms, ellers gjengis de invertert i lesere som tar kanalene for pålydende. Vil du ha en inventarliste for hele dokumentet framfor én side om gangen, skanner FindImages sammen med SetFindImagesMode hele filen i én runde
Det er én grense verdt å ta opp med interessentene før noen skriver aksepteringskriterier: bildeuttrekk returnerer kun rasterressurser. En logo eller et diagram tegnet som vektorbaner er ikke et bilde i ressursforstand og vil aldri dukke opp i noen bildeliste, uansett hvor tydelig det leses som en illustrasjon på skjermen. Når kravet virkelig er å levere det diagrammet som en fil, er den ærlige tilnærmingen å rendre sideområdet til en punktgrafikk, som er en annen operasjon med en annen troverdighet. De to slagene utdata hører ikke hjemme i samme eksportmappe uten en merkelapp som sier hva som er hva
Skrifter: en granskingsflate, ikke en eksportfunksjon
Skrift-API-et besvarer spørsmål om skrifter. Det gir deg ikke selve skriftfilene, og det skillet former alt du kan bygge på det. Etter at FindFonts har skannet dokumentet, går opptellingen gjennom skriftene etter ID, og egenskapskallene melder om den skriften som er valgt akkurat da:
var
I: Integer;
begin
Pdf.FindFonts;
for I := 1 to Pdf.FontCount do // skriftindekser starter på 1, ikke 0
if Pdf.SelectFont(Pdf.GetFontID(I)) = 1 then
Writeln(Format('%s type=%d embedded=%d subset=%d',
[Pdf.FontName, Pdf.FontType,
Pdf.GetFontIsEmbedded, Pdf.GetFontIsSubsetted]));
end;
Følg med på sløyfegrensene. Skriftindekser går fra 1 til FontCount, mens indeksene for tekstblokker og bildelister noen avsnitt lenger opp starter på null. Tar du den ene konvensjonen med inn i den andre, får du en av-med-én-feil som enten hopper over den første skriften eller løper forbi enden, og den vil bestå overfladisk testing fordi de fleste dokumenter har flere skrifter, og feil skrift ser fortsatt plausibel ut. Vær tydelig på omfanget også. Dette API-et har ingen skrifteksport på byte-nivå. Ingen kall returnerer det innebygde skriftprogrammet som en TTF- eller OTF-fil, og opptelling pluss inspeksjon av metadata er hele den tiltenkte modellen. Den modellen dekker likevel det produksjonsarbeid faktisk spør skrifter om: deteksjon av delmengder etter navnemønster, granskinger av innebygging før en arkivkonvertering (en ikke-innebygd skrift er en hard blokkering for PDF/A, som PDF/A- og PDF/UA-preflight i Delphi går inn på), og kodingsdiagnostikk for når uttrekkskonfidensen faller. Det finnes også en lisensgrunn til at grensen ligger her. Et delmengdeskriftprogram er lisensiert materiale og, siden det mangler de fleste glyffene sine, uansett ubrukelig som en installerbar skrift. Å behandle det som granskingsmetadata framfor et uttrekkbart aktivum er den posisjonen du kan forsvare
Det siste kallet gjør nytten sin i triagen. Kjør GetFontEncoding på hver skrift, les den sammen med delmengdeflagget, og du kan forutsi uttrekkskvaliteten før du henter ut ett eneste tegn. En side der alle skriftene er delmengder med ikke-standard koding, er en OCR-kandidat allerede ved inspeksjon, noe som lar en batchpipeline rute den riktig uten først å kaste bort en mislykket uttrekksrunde på den
Uttrekk i stor skala uten å laste dokumenter
I en batchpipeline er det å laste et helt dokument bare for å lese én side bortkastet I/O, og det hoper seg raskt opp over et korpus. Variantene med ett kall, ExtractFilePageText og ExtractFilePageTextBlocks, tar filnavn, passord og sidenummer direkte og hopper over den fulle lastingen. For filer i gigabyteklassen finnes det et enda lavere gir. Veien med direkte tilgang åpner en fil gjennom strømmende xref-lesinger, så DAOpenFileReadOnly etterfulgt av DAExtractPageText berører bare de objektene den ene siden faktisk trenger. Den kommer med et konvensjonsskifte verdt å legge på minnet: DA-funksjonene adresserer sider med PageRef, et objektreferansehåndtak du får fra DAFindPage, aldri med rått sidenummer. Send inn tallet der håndtaket hører hjemme, og kallet opererer på feil objekt uten å utløse en feil, som er den verste sorten tabbe å feilsøke. Resten av verktøykassen for direkte tilgang er lagt ut i sammenslåing, oppdeling og direkte tilgang for store PDF-er
Finnes det én vane som skiller uttrekkskode som overlever et virkelig korpus, fra kode som halter, er det å behandle siden som upålitelig inndata framfor en ren datakilde. Tekst som ikke stemmer med det leseren gjengir, er nesten alltid et kodingsproblem, en ligatur som kollapser til én glyff eller en delmengdeskrift som mangler ToUnicode-oppføringene sine, og løsningen er å måle konfidens og lede de dårlige sidene til OCR, ikke å slåss mot bytene. Skrift-API-et vil aldri produsere en TTF eller OTF, av design, så bygg skriftarbeidsflyter rundt granskingsspørsmål. Og den vedvarende uttrekkstilstanden, områderektangelet ikke minst, er en innstilling du eier gjennom hele levetiden til et dokumenthåndtak, ikke en parameter du glemmer etter ett kall. Får du de tre refleksene riktig, oppfører resten av API-et seg
Evalueringsbygg, demoprosjekter og den fullstendige API-referansen for uttrekk finnes på produktsiden for losLab PDF Library for Delphi