Vytáhnout z existujícího PDF text, obrázky a písma zní jako vyřešený problém, dokud tím neproženete skutečný korpus. Namiřte vyhledávací indexer na čtyřicet tisíc zákaznických souborů a rozbité případy se roztřídí do několika rozpoznatelných hromádek. Slova se slévají dohromady, protože nikdo extraktoru neřekl, jak široká mezera se počítá jako mezislovní. Jiné stránky se vrátí jako blábol, protože subsetované písmo nenese žádnou mapu ze svých kódů glyfů na skutečné znaky. A „logo firmy“ se ukáže být devíti samostatnými obrazovými objekty naskládanými za měkkou maskou. Nic z toho není chybou knihovny. Je to rozdíl mezi zavoláním extrakční funkce a pochopením toho, co ta funkce z bajtů na disku dokáže a nedokáže obnovit
losLab PDF Library, edice pro Pascal, dává kódu v Delphi a C++Builderu víc než jednu cestu, jak každý z těch tří proudů číst, a jednotlivé úrovně se liší v tom, co zaručují. Fígl spočívá v přiřazení úrovně k úloze: vyhledávací index, kontrolor redakcí a preflight pro PDF/A chtějí z téže stránky každý něco jiného a sáhnutí po nesprávném volání buď promrhá úsilí, nebo vyrobí výstup, jemuž nemůžete věřit
Úrovně extrakce textu a co která slibuje
GetPageText bere hodnotu voleb od 0 do 8 a to číslo volí engine, ne formát. Hodnoty 0 až 2 spouštějí odlehčený průchod, jenž se hodí na rychlý náhled. Hodnoty 3 až 8 vedou přes engine respektující rozvržení, který rekonstruuje řádky a mezery z toho, kde glyfy na stránce skutečně sedí. Uvnitř tohoto rozsahu záleží na odchylkách: 4 a 6 rozdělují výstup na slova, 5 a 6 vydávají šířky jednotlivých glyfů a 7 vrací prostý text, u něhož jsou metadata o písmu, barvě a blocích záměrně zahozena. Volba 7 je ta, kterou krmit vyhledávací index, protože index chce slova a nic jiného
Žádné nastavení voleb nezachrání dokument, který tu informaci nikdy neobsahoval. PDF mapuje kódy znaků na tvary glyfů a jedinou věcí, jež tyto kódy mapuje zpět na čitelný text, je CMap ToUnicode daného písma (ISO 32000-1 §9.10). Když subsetované písmo dorazí bez ní, uvízne každý extraktor. Tato knihovna, kopírování a vkládání v prohlížeči, konkurenční sada nástrojů: všechny se zredukují na hádání podle názvů glyfů, nebo nevrátí nic. Praktickou odpovědí je detekce, ne hrdinství. Ohodnoťte takovou stránku jako málo spolehlivou a pošlete ji na OCR, protože tiše zaindexovat nesmysl je horší než přiznat, že to nedokážete přečíst
Pro případy, na které ploché volby nestačí, tedy vlastní tokenizaci, forenzní rozbor obsahového streamu nebo textový trychtýř postavený podle vlastních pravidel, je dekodér dostupný o vrstvu níž. TPDFExtractor se konstruuje nad slovníkem zdrojů stránky a její kolekcí písem. Jeho metoda ExtractTextW prohání surové textové operace obsahového streamu zpět toutéž písmovou mašinerií, aby obnovila Unicode, a jeho událost OnFindObject vám podá každý objekt, jak proudí kolem. Většina kódu se do takové hloubky nikdy dostat nemusí. Aplikace, které ano, jsou rády, že je ta vrstva veřejná, a ne pohřbená
Umístěné bloky: jednotka pro nálezy hledání a kontrolu redakcí
Prostý text vám řekne, co stránka říká. Dříve či později produkt potřebuje vědět také kde to říká, aby mohl zvýraznit nález hledání, obtáhnout kandidáta na redakci rámečkem nebo ukotvit anotaci na správné místo. ExtractPageTextBlocks vrací handle seznamu textových úseků a každý úsek nese svůj text, svůj ohraničující obdélník a název i velikost písma, jímž byl vysazen:
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;
Jeden detail v této oblasti podráží nohy integracím častěji než cokoli jiného. SetTextExtractionArea, SetTextExtractionWordGap a SetTextExtractionOptions jsou stav na úrovni dokumentu, který přetrvává, ne argumenty předávané u jednotlivých volání. Nastavte omezení oblasti kvůli jedné funkci, řekněme kvůli čtení jen hlavičkového pásu pro klasifikaci dokumentu, a tiše to zkrátí každou další extrakci na témž handlu, včetně úrovní GetPageText respektujících rozvržení, po nichž sáhnete později. Buď extrakční stav mezi logickými úlohami resetujte, nebo dejte každé úloze vlastní handle dokumentu
Prahová hodnota mezery mezi slovy je pákou na tu první hromádku selhání, tedy na slévající se slova. SetTextExtractionWordGap říká enginu rozvržení, kolik vodorovného prostoru, měřeno vůči vlastnímu rozestupu glyfů na stránce, odděluje jedno slovo od druhého. Hustá tabulka chce menší mezeru než volně vysázená marketingová stránka, takže práh vyladěný podle třídy dokumentu poráží jedinou globální konstantu. Přetrvává na dokumentu jako zbytek extrakčního stavu, takže počítejte s tím, že jej budete nastavovat záměrně, ne jednou a navždy
Obrázky: původní streamy, ne snímky obrazovky
Špatný způsob, jak z PDF dostat obrázky, je vykreslit stránku a oříznout ji. Tím se pixely převzorkují, zapeče se do nich případné otočení a zahodí se to, čím byl originál. GetPageImageList místo toho vyjmenuje skutečné obrazové zdroje, na které stránka odkazuje, a každá položka vrací své vlastnosti i svá původní, nedotčená 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;
Než u položky cokoli předpokládáte, zkontrolujte GetImageListItemFormatDesc, protože to, na co stránka odkazuje, málokdy bývá jeden úhledný obrázek na každý viditelný obrázek. Měkká maska se objeví jako samostatná položka. Tentýž XObject se často opakuje napříč mnoha stránkami, takže před archivací exportu „všech obrázků“ deduplikujte podle otisku obsahu, jinak zapíšete totéž logo stokrát. JPEG v CMYK potřebuje dále v řetězci správu barev, jinak se v prohlížečích, jež berou kanály doslova, vykreslí invertovaně. Když chcete inventuru celého dokumentu místo jedné stránky po druhé, prohledá FindImages spolu se SetFindImagesMode celý soubor jedním průchodem
Jednu hranici stojí za to nadnést zainteresovaným stranám dřív, než kdokoli sepíše akceptační kritéria: extrakce obrázků vrací pouze rastrové zdroje. Logo nebo graf nakreslený vektorovými cestami není obrázkem ve smyslu zdroje a v žádném seznamu obrázků se nikdy neobjeví, ať už na obrazovce čte jako obrázek sebevíc. Když je požadavkem opravdu dodat ten graf jako soubor, poctivým přístupem je vykreslit oblast stránky do bitmapy, což je jiná operace s jinou věrností. Oba druhy výstupu nepatří do téže exportní složky bez nálepky, která říká, co je co
Písma: plocha pro audit, ne exportní funkce
API pro písma odpovídá na otázky o písmech. Nepodá vám samotné soubory písem a tento rozdíl utváří všechno, co nad ním postavíte. Poté, co FindFonts dokument prohledá, prochází výčet písma podle ID a volání vlastností hlásí údaje o tom písmu, jež je zrovna vybrané:
var
I: Integer;
begin
Pdf.FindFonts;
for I := 1 to Pdf.FontCount do // indexy písem začínají na 1, ne na 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;
Hlídejte meze smyčky. Indexy písem běží od 1 do FontCount, zatímco indexy textových bloků a seznamu obrázků z pár odstavců výše počítají od nuly. Přeneste jednu konvenci do druhé a dostanete chybu o jedničku, jež buď přeskočí první písmo, nebo přeteče přes konec, a projde to i letmým testováním, protože většina dokumentů má několik písem a to nesprávné pořád vypadá věrohodně. Buďte si také jasní v rozsahu. Toto API nemá export písem na úrovni bajtů. Žádné volání nevrátí vložený program písma jako soubor TTF nebo OTF a výčet spolu s inspekcí metadat je celý zamýšlený model. Ten model přesto pokrývá to, co produkční práce od písem skutečně chce: detekci subsetů podle vzoru názvu, audity vložení před archivní konverzí (nevložené písmo je tvrdou překážkou pro PDF/A, jak rozvádí Preflight PDF/A a PDF/UA v Delphi) a diagnostiku kódování pro chvíle, kdy spolehlivost extrakce klesá. Pro umístění téhle hranice existuje i licenční důvod. Program subsetovaného písma je licencovaný materiál a při chybějící většině glyfů je jako instalovatelné písmo stejně k ničemu. Brát jej jako auditní metadata místo jako extrahovatelné aktivum je pozice, kterou lze obhájit
To poslední volání se vyplácí při třídění. Spusťte GetFontEncoding na každé písmo, přečtěte je vedle příznaku subsetu a dokážete předpovědět kvalitu extrakce dřív, než vytáhnete jediný znak. Stránka, jejíž písma jsou všechna subsetovaná s nestandardními kódováními, je kandidátem na OCR už z pouhé prohlídky, díky čemuž ji dávková pipeline nasměruje správně, aniž by na ni napřed promrhala neúspěšný extrakční průchod
Extrakce ve velkém bez načítání dokumentů
V dávkové pipeline je načtení celého dokumentu jen kvůli přečtení jedné stránky promrhané I/O a napříč korpusem se to rychle sečte. Varianty s jediným voláním, ExtractFilePageText a ExtractFilePageTextBlocks, berou přímo název souboru, heslo a číslo stránky a plné načtení přeskočí. Pro soubory v řádu gigabajtů existuje ještě nižší převod. Cesta přímého přístupu otevírá soubor proudovým čtením xref, takže DAOpenFileReadOnly následované DAExtractPageText sáhne jen na objekty, které ta jedna stránka skutečně potřebuje. Přichází s posunem konvence, jejž si stojí za to zapamatovat: funkce DA adresují stránky přes PageRef, tedy handle objektové reference, který získáte z DAFindPage, nikdy podle surového čísla stránky. Předejte číslo tam, kam patří handle, a volání zapracuje na nesprávném objektu, aniž by ohlásilo chybu, což je ten nejhorší druh omylu k ladění. Zbytek nástrojů přímého přístupu rozkládá článek slučování, dělení a přímý přístup u velkých PDF
Existuje-li jediný návyk, jenž odděluje extrakční kód přeživší skutečný korpus od kódu, který kulhá, je to brát stránku jako nedůvěryhodný vstup, a ne jako čistý zdroj dat. Text, který se rozchází s tím, co prohlížeč vykresluje, je téměř vždy problém kódování, ligatura sesypaná do jednoho glyfu nebo subsetované písmo bez položek ToUnicode, a řešením je změřit spolehlivost a špatné stránky odklonit na OCR, ne se prát s bajty. API pro písma nikdy nevyprodukuje TTF ani OTF, a to záměrně, takže postupy kolem písem stavte na auditních otázkách. A přetrvávající extrakční stav, ze všeho nejvíc obdélník oblasti, je nastavení, které vlastníte po celou dobu života handlu dokumentu, ne parametr, na nějž po jednom volání zapomenete. Zvládněte tyhle tři reflexy a zbytek API se bude chovat slušně
Evaluační sestavení, ukázkové projekty a kompletní referenční příručka extrakčního API jsou na produktové stránce losLab PDF Library for Delphi