Műszaki cikk

Szöveg, kép és betűtípus kinyerése PDF-ből Delphiben

A PDF-dokumentumokból történő adatkinyerés gyakori igény a vállalati fejlesztésben: számlák feldolgozása, archivált dokumentumok indexelése vagy aláírt szerződések ellenőrzése. Bár a PDF specifikáció leírja, hogyan tárolódnak a szövegek, a képek és a betűtípusok az objektumfában, ezek közvetlen olvasása a fejlesztőktől mély PDF-specifikus ismereteket igényel. Szükség van a karakter-leképezések (ToUnicode), a tömörítési szűrők és a PDF struktúra szintjét is

A PDFlibPas egy natív Object Pascal PDF-könyvtár Delphihez és C++Builderhez. Eredetileg a dokumentumok írására és szerkesztésére optimalizálták, de a v3.75.0 verziótól kezdve egy teljes olvasó-oldali kinyerési API-t is tartalmaz. Ez a cikk arról szól, hogyan nyerhet ki szöveget, beágyazott képeket és betűtípus-információkat a PDFlibPas segítségével, bemutatva a Unicode leképezés fontosságát és az erőforrások felszabadítását. A PDF dokumentumok írása egyszerű, de az olvasási oldalon a bonyolultság ismét megjelenik

A szöveges tartalom kinyerése

A PDF-ben a szöveg nem folytonos karakterláncként tárolódik, hanem elszórt karakter-pozicionálási utasítások (pl. TJ vagy Tj) sorozataként a tartalomfolyamban. Ezen felül a karakterkódok leképezése a tényleges Unicode karakterekre a betűtípus /ToUnicode szótárától függ. Ha ez a szótár hiányzik vagy hibás, a kinyert szöveg olvashatatlan szemétté válik, különösen a nem-standard vagy egyedi betűtípusokkal kódolt szövegeknél

A PDFlibPas a kinyerés során automatikusan feloldja a ToUnicode leképezéseket, és az oldalszöveget Unicode (WideString) formátumban adja vissza a hívónak. A GetPageText a legmagasabb szintű hívás erre a célra:

var
  Pdf: TPDFlib;
  Text: WideString;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.LoadFromFile('document.pdf');
    Pdf.SelectPage(1);
    Text := Pdf.GetPageText;
    // a Text most már tartalmazza az 1. oldal teljes szöveges tartalmát
  finally
    Pdf.Free;
  end;
end;

A GetPageText a fizikai elrendezést követve fűzi össze a szövegelemek bájtolt sorozatát, ami szinte mindig a kívánt kimenet

A beágyazott képek kinyerése

A PDF-ben a képek XObject objektumokként tárolódnak, és általában DCTDecode (JPEG) vagy FlateDecode (PNG-szerű) szűrőkkel vannak tömörítve. A képek kinyerése a PDFlibPas-ban a következőképpen néz ki:

var
  ImageCount, I: Integer;
  ImageID: Integer;
begin
  ImageCount := Pdf.FindImages;
  for I := 1 to ImageCount do
  begin
    ImageID := Pdf.GetImageID(I);
    Pdf.SelectImage(ImageID);
    Pdf.SaveImageToFile(Format('image_%d.png', [I])); // A kép elmentése PNG formátumban
  end;
end;

A FindImages bejárja az aktuális oldal erőforrás szótárát, összegyűjti az összes képet, és visszaadja a képek számát. A GetImageID segítségével lekérhető az egyes képek belső azonosítója, majd a SelectImage után a kép menthető vagy közvetlenül manipulálható. Hasonlóan az annotációkhoz, ez az azonosító a kulcs a kép belső tulajdonságainak lekérdezéséhez

A betűtípusok vizsgálata

A dokumentumok auditálása során gyakori feladat annak ellenőrzése, hogy a használt betűtípusok be vannak-e ágyazva, vagy hogy nem-standard betűtípusokat tartalmaznak-e. A GetFontInfo rekordja ezt a szerkezetet adja vissza:

type
  TPDFlibFontInfo = record
    Name: WideString;
    Embedded: Boolean;        // True ha a betűtípus be van ágyazva a PDF-be
    Subsetted: Boolean;       // True ha a betűtípus részhalmazként van ágyazva
    FontType: WideString;     // pl. TrueType, Type1, Type0
  end;

A rekord mezői segítségével pontosan meghatározható a betűtípus állapota. Ha a Name mező alapján azonosítja a betűtípust, ellenőrizheti, hogy az megfelel-e a vállalati előírásoknak vagy a kisegítő technológiák (accessibility) elvárásainak, mielőtt a szöveget átadná egy harmadik fél által készített OCR motorhoz

A betűtípusok listájának bejárása a következő mintát követi:

var
  FontCount, I: Integer;
  FontInfo: TPDFlibFontInfo;
begin
  FontCount := Pdf.GetFontCount;
  for I := 1 to FontCount do
  begin
    FontInfo := Pdf.GetFontInfo(I);
    Writeln(Format('Font: %s, Embedded: %d, Type: %s',
      [FontInfo.Name, Ord(FontInfo.Embedded), FontInfo.FontType]));
  end;
end;

Különösen a nagy archiválási rendszereknél ez a különbségtétel kulcsfontosságú a minőség szempontjából

A memóriakezelési csapda

Az olvasó-oldali kinyerési hívásoknál a leggyakoribb csapda a memóriakezelés. Sok fejlesztő elfelejti, hogy a ExtractPageTextBlocks egy belső listát hoz létre, amelyet a hívónak manuálisan kell felszabadítania. Ha egyszerűen újra és újra meghívja a metódust a listák felszabadítása nélkül, az a memóriában felhalmozódik, és egy csendes memóriaszivárgást hoz létre

A helyes használati minta a következő:

var
  ListID: Integer;
begin
  ListID := Pdf.ExtractPageTextBlocks(1);
  try
    // ... blokkok feldolgozása a ListID segítségével ...
  finally
    Pdf.FreeTextBlockList(ListID);
    // A TPDFTextBlockList.Free automatikusan felszabadítja az összes blokkot a listában
  end;
end;

A finally blokk biztosítja, hogy a lista akkor is felszabaduljon, ha a feldolgozás során kivétel (exception) lépne fel. Ha a lista azonosítót átadja a FreeTextBlockList metódusnak, a Delphi memóriakezelő azonnal felszabadítja a memóriát

Betűtípus beágyazottságának ellenőrzése

Az archiválási szabványok (pl. PDF/A) megkövetelik, hogy a dokumentumban használt összes betűtípus be legyen ágyazva a fájlba. Ha egy betűtípus nincs beágyazva, a megjelenítő a helyi operációs rendszer betűtípusaival próbálja helyettesíteni azt, ami az elrendezés széteséséhez vezethet. A GetFontInfo rekord segítségével ez az ellenőrzés automatizálható, ez az archiválási és ellenőrzési feladatok alapja

Az itt bemutatott kinyerési és vizsgálati hívások a PDFlibPas Delphi PDF Library termékoldala nyújt teljes képet

A frissített útmutató elkülöníti a szövegkinyerés szintjeit és a pozicionált blokkokat, az eredeti képfolyamokat, a betűtípus-auditot és a nagy dokumentumok feldolgozását