Szöveg, képek és betűkészletek kihúzása egy meglévő PDF-ből megoldott feladatnak hangzik, amíg valódi korpuszt nem futtat át rajta. Irányítson egy keresési indexelőt negyvenezer ügyfélfájlra, és a törések néhány felismerhető kupacba rendeződnek. A szavak összefolynak, mert senki sem mondta meg a kinyerőnek, mekkora rés számít szóköznek. Más oldalak halandzsaként jönnek vissza, mert egy részhalmazolt betűkészlet nem hordoz leképezést a jelalakkódjairól valódi karakterekre. A „cégembléma” pedig kilenc külön képobjektumnak bizonyul, egymásra rakva egy lágy maszk mögött. Mindez nem a könyvtár hibája. Ez a különbség aközött, hogy meghív egy kinyerőfüggvényt, és hogy érti, mit tud és mit nem tud a függvény visszanyerni a lemezen lévő bájtokból
A losLab PDF Library Pascal kiadása egynél több módot ad a Delphi és C++Builder kódnak arra, hogy elolvassa ezt a három folyamot, és a szintek abban különböznek, mit garantálnak. A trükk az, hogy a szintet a feladathoz illeszti: egy keresési index, egy kitakarási felülvizsgáló és egy PDF/A előellenőrzés más-más dolgot akar ugyanarról az oldalról, és a rossz híváshoz nyúlva vagy munkát pazarol, vagy olyan kimenetet állít elő, amelyben nem bízhat
Szövegkinyerési szintek és amit mindegyik ígér
A GetPageText 0-tól 8-ig terjedő beállításértéket vár, és ez a szám nem formátumot, hanem motort választ. A 0-tól 2-ig terjedő értékek könnyűsúlyú menetet futtatnak, amely gyors előnézethez megfelelő. A 3-tól 8-ig terjedő értékek az elrendezéstudatos motoron mennek át, amely abból építi újra a sorokat és a szóközöket, ahol a jelalakok ténylegesen ülnek az oldalon. Ezen a tartományon belül számítanak a változatok: a 4 és a 6 szavakra bontja a kimenetet, az 5 és a 6 jelalakonkénti szélességet is kibocsát, a 7 pedig egyszerű szöveget ad vissza, szándékosan elhagyva a betűkészlet-, szín- és blokkadatokat. A 7-es beállítás az, amelyet keresési indexnek érdemes adni, mert az index szavakat akar, mást semmit
Egyetlen beállítás sem menthet meg olyan dokumentumot, amely eleve nem hordozta az információt. A PDF a karakterkódokat jelalakokra képezi le, és az egyetlen dolog, amely ezeket a kódokat visszavezeti olvasható szöveggé, a betűkészlet ToUnicode CMap táblája (ISO 32000-1 §9.10). Ha egy részhalmazolt betűkészlet ilyen nélkül érkezik, minden kinyerő megreked. Ez a könyvtár, a megjelenítőbeli másolás-beillesztés, egy versenytárs eszközkészlet: mindegyik jelalaknevekből való találgatásra vagy üres eredményre szorul. A gyakorlati válasz az észlelés, nem a hősködés. Pontozza az oldalt alacsony megbízhatóságúra, és küldje OCR-be, mert a halandzsa néma indexelése rosszabb, mint beismerni, hogy nem tudja elolvasni
Azokra az esetekre, amelyeket a lapos beállítások nem fednek le — egyedi tokenizálás, tartalomfolyam-nyomozás, saját szabályokra épített szövegtölcsér —, a dekódoló egy réteggel lejjebb áll rendelkezésre. A TPDFExtractor egy oldal erőforrásszótára és betűkészlet-gyűjteménye fölé épül. Az ExtractTextW metódusa a nyers tartalomfolyam szövegműveleteit futtatja vissza ugyanazon a betűkészlet-gépezeten, hogy visszanyerje az Unicode-ot, az OnFindObject eseménye pedig átadja minden objektumot, ahogy az elhalad. A legtöbb kódnak soha nem kell ilyen mélyre nyúlnia. Azok az alkalmazások, amelyeknek mégis, örülnek, hogy a réteg nyilvános, nem pedig eltemetett
Pozicionált blokkok: a találatok és a kitakarási felülvizsgálat egysége
Az egyszerű szöveg elárulja, mit mond az oldal. Előbb-utóbb a terméknek azt is tudnia kell, hol mondja, hogy kiemelhessen egy találatot, keretet rajzolhasson egy kitakarásra jelölt szakasz köré, vagy a megfelelő ponthoz horgonyozhasson egy jegyzetet. Az ExtractPageTextBlocks szövegszakaszok listájára ad leírót, és minden szakasz hordozza a szövegét, a befoglaló keretét, valamint annak a betűkészletnek a nevét és méretét, amellyel szedték:
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;
Ezen a területen egy részlet gáncsolja el az integrációkat minden másnál gyakrabban. A SetTextExtractionArea, a SetTextExtractionWordGap és a SetTextExtractionOptions megmaradó dokumentumszintű állapot, nem hívásonként átadott argumentum. Állítson be területi korlátozást az egyik szolgáltatáshoz — mondjuk csak a fejlécsáv olvasásához egy dokumentum besorolásakor —, és az némán megcsonkít minden későbbi kinyerést ugyanazon a leírón, beleértve az elrendezéstudatos GetPageText szinteket is, amelyekhez később nyúl. Vagy állítsa vissza a kinyerési állapotot a logikai feladatok között, vagy adjon minden feladatnak saját dokumentumleírót
A szóközküszöb az a kar, amely az első hibakupacot, az összefolyó szavakat kezeli. A SetTextExtractionWordGap megmondja az elrendezésmotornak, mekkora vízszintes távolság — az oldal saját jelalak-térközéhez mérve — választja el az egyik szót a következőtől. Egy sűrű táblázat kisebb rést kíván, mint egy lazán szedett marketingoldal, tehát a dokumentumosztályonként hangolt küszöb jobb egyetlen globális állandónál. Ez is megmarad a dokumentumon, mint a kinyerési állapot többi része, tehát tervezzen tudatos beállítással, ne az „egyszer beállítom és elfelejtem” szemlélettel
Képek: eredeti folyamok, nem képernyőképek
A rossz mód a képek kinyerésére az, hogy kirajzolja az oldalt és levágja belőle. Ez újramintavételezi a képpontokat, belesüti a forgatást, és eldobja azt, ami az eredeti volt. A GetPageImageList ehelyett azokat a tényleges képerőforrásokat sorolja fel, amelyekre az oldal hivatkozik, és minden elem visszaadja a tulajdonságait és az eredeti, érintetlen adatait:
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;
Nézze meg a GetImageListItemFormatDesc értékét, mielőtt bármit feltételezne egy elemről, mert az, amire egy oldal hivatkozik, ritkán egyetlen rendes kép látható képenként. Egy lágy maszk saját külön bejegyzésként jelenik meg. Ugyanaz az XObject gyakran ismétlődik sok oldalon át, tehát tartalomhasítás alapján szűrje ki az ismétlődéseket, mielőtt „minden kép” exportot archiválna, különben százszor írja ki ugyanazt az emblémát. A CMYK JPEG-eknek később színkezelésre van szükségük, különben fordítva jelennek meg azokban a megjelenítőkben, amelyek névértéken veszik a csatornákat. Ha oldalankénti helyett dokumentumszintű leltárt szeretne, a FindImages a SetFindImagesMode beállítással egyetlen menetben pásztázza végig a teljes fájlt
Van egy határvonal, amelyet érdemes felvetni az érdekelteknek, mielőtt bárki átvételi feltételeket írna: a képkinyerés csak raszteres erőforrásokat ad vissza. Egy vektoros útvonalakkal megrajzolt embléma vagy diagram erőforrás értelemben nem kép, és soha nem fog felbukkanni egyetlen képlistában sem, bármilyen egyértelműen olvasódik is képként a képernyőn. Ha a követelmény valóban az, hogy ezt a diagramot fájlként kell átadni, a becsületes megközelítés az oldalrégió bitképbe rajzolása, ami más művelet, más hűséggel. A kétféle kimenet nem tartozik ugyanabba az exportmappába anélkül, hogy címke jelezné, melyik melyik
Betűkészletek: ellenőrzési felület, nem exportszolgáltatás
A betűkészlet-API a betűkészletekről szóló kérdésekre válaszol. Magukat a betűkészletfájlokat nem adja át, és ez a megkülönböztetés alakít mindent, amit rá lehet építeni. Miután a FindFonts végigpásztázta a dokumentumot, a felsorolás azonosító szerint járja végig a betűkészleteket, a tulajdonsághívások pedig arról a betűkészletről számolnak be, amelyik éppen ki van választva:
var
I: Integer;
begin
Pdf.FindFonts;
for I := 1 to Pdf.FontCount do // a betűkészlet-indexek 1-től indulnak, nem 0-tól
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;
Figyelje a ciklushatárokat. A betűkészlet-indexek 1-től a FontCount értékéig futnak, míg a néhány bekezdéssel feljebb tárgyalt szövegblokk- és képlistaindexek nullaalapúak. Vigye át az egyik szokást a másikba, és egyeltéréses hibát kap, amely vagy kihagyja az első betűkészletet, vagy túlfut a végén, ráadásul átmegy a felületes teszteken, mert a legtöbb dokumentumban több betűkészlet van, és a rossz is hihetőnek látszik. A hatókörrel is legyen tisztában. Ennek az API-nak nincs bájtszintű betűkészlet-exportja. Egyetlen hívás sem adja vissza a beágyazott betűkészletprogramot TTF vagy OTF fájlként, és a felsorolás plusz a jellemzők vizsgálata a teljes szándékolt modell. Ez a modell mégis lefedi azt, amit az éles munka valóban kér a betűkészletektől: a részhalmazolás észlelését névminta alapján, a beágyazottság ellenőrzését egy archiválási átalakítás előtt (a be nem ágyazott betűkészlet kemény PDF/A akadály, ahogy azt a PDF/A és PDF/UA előellenőrzés Delphiben cikk kifejti), valamint a kódolási diagnosztikát arra az esetre, amikor a kinyerés megbízhatósága leesik. A határvonal helyének licencelési oka is van. A részhalmazolt betűkészletprogram licencelt anyag, és mivel a jelalakjai nagy része hiányzik, telepíthető betűkészletként úgyis használhatatlan. Ha ellenőrzési adatként kezeli, nem kinyerhető eszközként, olyan álláspontot foglal el, amely megvédhető
Az utolsó hívás kiveszi a részét a válogatásból. Futtassa a GetFontEncoding hívást minden betűkészletre, olvassa össze a részhalmazolási jelzővel, és megjósolhatja a kinyerés minőségét, mielőtt egyetlen karaktert is kihúzna. Az az oldal, amelynek minden betűkészlete részhalmazolt és nem szabványos kódolású, már ránézésre OCR-jelölt, ami lehetővé teszi, hogy a kötegelt munkafolyamat helyesen irányítsa, anélkül hogy előbb elpazarolna rá egy sikertelen kinyerési menetet
Kinyerés nagy tételben, dokumentumbetöltés nélkül
Kötegelt munkafolyamatban egy teljes dokumentum betöltése csak azért, hogy egyetlen oldalt elolvasson, elpazarolt ki- és bevitel, és egy korpuszon át gyorsan összeadódik. Az egyhívásos változatok, az ExtractFilePageText és az ExtractFilePageTextBlocks, közvetlenül fájlnevet, jelszót és oldalszámot fogadnak, és kihagyják a teljes betöltést. Gigabájtos méretű fájlokhoz van még egy alacsonyabb fokozat. A közvetlen eléréses út folyamszerű xref-olvasásokkal nyit meg egy fájlt, tehát a DAOpenFileReadOnly, majd a DAExtractPageText csak azokat az objektumokat érinti, amelyekre az adott oldalnak valóban szüksége van. Egy megjegyzendő szokásváltással jár: a DA függvények PageRef értékkel címzik az oldalakat, ami a DAFindPage hívásából kapott objektumhivatkozási leíró, soha nem nyers oldalszámmal. Ha oda adja a számot, ahová a leíró való, a hívás hibajelzés nélkül a rossz objektumon dolgozik, és ez a hibafajta a legrosszabb kinyomozni. A közvetlen eléréses eszközkészlet többi részét a nagy PDF-ek egyesítéséről, darabolásáról és közvetlen eléréséről szóló cikk teríti ki
Ha van egyetlen szokás, amely elválasztja a valódi korpuszt túlélő kinyerőkódot a bicegőtől, akkor az, hogy az oldalt nem megbízható bemenetnek tekinti, nem tiszta adatforrásnak. Az a szöveg, amely ellentmond a megjelenítő által kirajzoltnak, szinte mindig kódolási gond — egyetlen jelalakká olvadó ligatúra vagy a ToUnicode bejegyzéseit nélkülöző részhalmazolt betűkészlet —, és a megoldás a megbízhatóság mérése és a rossz oldalak OCR-be terelése, nem a bájtokkal való birkózás. A betűkészlet-API tervezésénél fogva soha nem állít elő TTF vagy OTF fájlt, tehát a betűkészletes munkafolyamatokat ellenőrzési kérdések köré építse. A megmaradó kinyerési állapot pedig, mindenekelőtt a területi téglalap, olyan beállítás, amelyet a dokumentumleíró teljes élettartamára birtokol, nem paraméter, amelyet egy hívás után elfelejt. Fejlessze ki ezt a három reflexet, és az API többi része rendesen viselkedik
A kiértékelési változatok, a bemutatóprojektek és a teljes kinyerési API-hivatkozás a losLab PDF Library for Delphi termékoldalán találhatók