A HotPDF Delphi Component a THotPDF.ExtractLoadedPageText-ben glyph geometriából építi újra a szóközöket és a sortöréseket, nem szóköz karakterekből. Szóköz akkor kerül be, amikor egy glyph saját szélessége utáni rés meghaladja a szövegmagasság 0,15-ödét, és új sor csak akkor kezdődik, amikor a szövegorigó az írásirány mentén a szövegmagasság felénél messzebb mozdul. A v2.768.3 óta az oldalszöveg a Form XObjecteken át rajzolt szöveget is tartalmazza, és kihagyja a látható crop területen kívül eső glyphokat. Ez a cikk a továbbiakban azt magyarázza el, miért néz ki minden szabály úgy, ahogy, mert mindegyik egy egyszerűbb szabályt váltott fel, ami valódi dokumentumokon hihető, de rossz kimenetet gyártott
A tünetek ismerősek bárkinek, aki valaha adott PDF szöveget keresési indexbe. Egy borító PDFReferenceManualNovember4,1998 alakban jön ki, egy adórlap 156 sorra esik szét, egy átlós vízjel soronként egy karakterrel érkezik, és egy levágott próbanyomat a nyomtató slug sorával vezet, amit egyetlen megjelenítő sem mutat. Egyik fájl sem hibás. Mindegyik tökéletesen legális módon helyez el szöveget, amit naiv kinyerő félreolvas
Miért veszítik el a kinyert PDF szövegek a szóközeiket?
A kinyert szöveg azért veszíti el a szóközeit, mert egy PDF-ből sosem kötelező azokat tartalmaznia. Egy producer elválthatja a szavakat szóköz karakter megjelenítésével, de ugyanúgy elmozdíthatja a tollat is egy számmal egy TJ tömbben (ISO 32000-1 §9.4.3) vagy egy friss Td-vel (§9.4.2), és a TeX kimenet, sok Distiller fájl és a legtöbb sorkizárt elrendezés pontosan ezt teszi. A v2.766.76 előtt az HPDFAssemblePageText csak a függőleges mozgást nézte, így a pozicionálással jelzett szóhatár egyszerűen eltűnt. Az összeállító mostantól az előző glyph írásirányának mentén méri a távolságot az adott glyph saját szélességének végétől az aktuális glyph origójáig, és egy szóközt szúr be, ha a távolság meghaladja az aktuális glyph dobozmagasságának 0,15-ödét, ascenttől descentig mérve user space-ben. Szóköz nem kerül be, ha bármelyik oldal már üres, és egyik sem kerül két CJK karakter közé, mert a sorkizárás széthúzza az ideogramokat anélkül, hogy az a szóhatárt jelentene. A glyph rekordok ugyanezt a geometriát kiadják, így bármikor újraszámolhatod a döntést, ha egy konkrét fájl rejtvény
uses
SysUtils, HPDFDoc, HPDFContentStream;
procedure DumpWordGaps(Pdf: THotPDF; PageIndex: Integer);
var
Glyphs: THPDFGlyphArray;
I: Integer;
Height, Gap: Double;
begin
if not Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
Exit;
for I := 1 to High(Glyphs) do
begin
// a glyph doboz ascenttől descentig terjedő magassága, user space-ben
Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
// vízszintes szöveg: rés az előző glyph saját szélességének végétől
Gap := Glyphs[I].BaselineStartX - Glyphs[I - 1].GlyphEndX;
if (Height > 0) and (Gap > 0.15 * Height) then
Writeln(Format('U+%.4x gap %.2f height %.2f: space',
[Glyphs[I].Unicode, Gap, Height]));
end;
end;
Miért a glyph saját szélességétől mér, és nem a tollpozíciótól?
A HotPDF a szórés távolságokat a GlyphEndX / GlyphEndY-tól méri, mert a tollpozíció egy glyph után már olyan távolságot is tartalmaz, ami nem rés. Az ISO 32000-1 §9.4.4-e a vízszintes elmozdulást a glyph szélességének és a fontméretnek a szorzataként definiálja, plusz a karaktertávolság Tc, plusz a szóköz távolság Tw, mind a Tz-vel skálázva. A BaselineEndX / BaselineEndY a teljes elmozdulást hordozza, a GlyphEndX / GlyphEndY viszont csak a font advance-et és a Tz-t. A különbség azoknál a producereknél számít, amik negatív Tc-vel szorítják a betűközt, majd minden glyph után egy TJ korrekcióval visszaadják a teret: tollpozíciótól mérve a visszaadás résnek látszik, és a kínai „95后" kifejezés „9 5 后" alakban jött ki. A küszöb hasonló okból a glyph dobozmagasságához kötődik, nem a Tf méretéhez. A Word exportok gyakran 1 Tf-t írnak, és a valódi méretet skálázott Tm-ben hordozzák, így a Tfs 1-et mond, miközben a szöveg 10 pont magas, és egy Tfs-hez kötött szabály ugyanazon oldal két helyesírását eltérően kezelné
A szabálynak őszinte határai vannak. Egy nagyon laza betűközzel szedett cím, ahol a Tc önmagában több mint a szövegmagasság 0,15-ödét nyit a betűk közt, minden betű közé szóközzel jön ki, ami az, amit az oldal mutat, de valószínűleg nem az, amit indexelni akartál. Az egy bazisvonalra rendezetlen sorrendben kirajzolt darabok negatív rést produkálnak, és szóköz nélkül fűződnek össze. Egyik eset sem gyakori a szövegtörzsben, és a tesztkorpuszon a változás 28 oldalon emelte a szóegyezéseket egy referencia kinyerőhöz képest, anélkül, hogy bármelyiken csökkentette volna
Mikor kezd új sort a HotPDF a kinyert szövegben?
A v2.766.79 óta új sor akkor kezdődik, amikor az előző glyph origójából az aktuálisba menő mozdulás, az előző írásirány normálára vetítve, meghaladja a két glyph közül a nagyobb dobozmagasság felét. A korábbi szabály a nyers Y mozdulást hasonlította a Tfs feléhez, ami két irányban is elbukott. 1 Tf és skálázott Tm mellett a küszöb fél egységre zsugorodott, így egy 0,4-es text rise-zal emelt fölérendelt index vagy egy hétköznapi bazisvonal-zaj sortört. A szabály az X-et teljesen figyelmen kívül hagyta, így egy elforgatott Tm alatti szöveg minden glyphmal lépkedett lefelé az oldalon, és soronként egy glyphként jött ki. Az iránynormálra vetítés azt éri el, hogy az elforgatott futamok úgy viselkedjenek, mint a vízszintesek, a két magasság közül a nagyobbik választása pedig azt, hogy egy nagy méretű mintaszó és a kis felirata egy sorban maradjon, ha osztoznak a bazisvonalon. A fent említett adólapon a sorszám 156-ról 97-re esett. A writing mode 1-es (§9.7.4.3) függőleges szöveg külön útvonalon jár: azok a glyphok oszlopokba csoportosulnak, jobbról balra és felülről lefelé olvasódnak, oszlopváltásonként sortöréssel
Melyik szöveget veszi fel az ExtractLoadedPageText, és melyiket hagyja el?
Az ExtractLoadedPageText azt a szöveget adja vissza, amit a megjelenítő mutat. A v2.766.80 óta csak a látható glyphokból dolgozik, és elhagy minden glyphot, aminek a dobozközepe a GetLoadedPageVisibleBox-on kívül esik, ami a MediaBoxra levágott CropBox (§14.11.2). Ez tünteti el a slug sorokat és a többi, a vágási területen kívül szedett nyomdai jelet. Az ExtractLoadedPageGlyphs szándékosan továbbra is az oldal content streamjének minden glyphját visszaadja, így arra az anyagra akkor is rátalálsz, ha kell. A szűrő dobozteszt, nem láthatóságteszt: a clipping path mögé rejtett, fehérrel festett vagy képpel takart szöveg is kijön
var
Pdf: THotPDF;
Glyphs: THPDFGlyphArray;
PageText: UnicodeString;
L, B, R, T: Single;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('trimmed-proof.pdf');
if Pdf.GetLoadedPageVisibleBox(0, L, B, R, T) then
Writeln(Format('Visible box: %.1f %.1f %.1f %.1f', [L, B, R, T]));
// az oldal content streamjének minden glyphja, slug sorral együtt
if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
Writeln(Length(Glyphs), ' glyphs in the page content stream');
// csak az, amit az oldal mutat, Form XObject szöveggel beillesztve
if Pdf.ExtractLoadedPageText(0, PageText) then
Writeln(PageText);
finally
Pdf.Free;
end;
end;
A Form XObjecteken át festett szöveg a v2.768.3 óta része az oldalszövegnek. A fejlécek, a pecsétek és a vízjelek nagyon gyakran formákban laknak, és egyes szabványdokumentumok a változás előtt karaktereik 30-35 százalékát veszítették el. A THotPDF.InterpretContentWithForms minden Do-t feljegyez az éppen hatályos CTM-mel együtt, a formát a /Matrix-szának és annak a CTM-nek a szorzatánál értelmezi (§8.10.1), a form glyphjait pedig a Do pozíciójába illeszti be, egymásba ágyazott formákba rekurzívan lépve. A saját /Resources nélküli form a festő stream forrásaival dolgozik, ahogy a §7.8.3 megengedi. A form glyphok TokenIndex = -1 értéket hordoznak, és az ExtractLoadedPageGlyphs továbbra is csak oldalstream glyphokat ad vissza, mert a keresés, csere és redakció a TokenIndex-en át írja vissza a változásokat, és rossz bájtokat szerkesztene, ha form glyph becsúszna. Két egyszerűsítés érdemes ismerni: a form szöveget nem vágja le a form /BBox-a, és a rekurzió 12 szintnél áll meg ciklusérzékelés helyett, így egy önmagát festő, rosszul formált form a szövegét addig ismétli, amíg el nem éri azt a capet
Miért olvasódott gibberish-ként a Q operátor utáni szöveg?
A Q utáni szöveg a v2.766.73 előtt rosszul dekódolódhatott, mert a kinyerő csak a CTM-et mentette q-nál. A szövegállapot paraméterei, nevezetesen a font, a méret, a Tc, a Tw, a Tz, a TL, a renderelési mód és a rise, a grafikai állapothoz tartoznak (§9.3.1), így a Q-nak azokat is helyre kell állítania a verem minden más elemével együtt (§8.4.2). Egy ipari riport egy kétbájtos Identity-H fontot választott egy q … Q blokkon belül, majd saját Tf nélküli egybájtos WinAnsi szöveget mutatott. A kinyerő megtartotta a belső fontot, a tartalomjegyzék vezetőpontjait és az „Adobe" szót kétbájtos kódokként olvasta, és az oldal karaktereinek 15%-át elvesztette. Az interpreter q/Q verme mostantól a teljes szövegállapotot tartja. Az itt leírt kinyerési szabályok minden oldalra vonatkoznak, így egy teljes dokumentum egy hívással fájlba mehet
var
Output: TFileStream;
Pages: Integer;
begin
Output := TFileStream.Create('report.txt', fmCreate);
try
// üres tartomány = minden oldal; lapdobás az oldalak közt; UTF-8 BOM
Pages := Pdf.ExtractLoadedPagesTextToStream(Output, '', #12, True);
Writeln(Pages, ' pages extracted');
finally
Output.Free;
end;
end;
Melyik HotPDF szöveg API-t használd?
Az ExtractLoadedPageText content-stream sorrendben marad, ami keresésre és indexelésre a helyes alapértelmezés; a mögötte álló dekódolási láncot a szöveg kinyerése betöltött PDF-ekből HotPDF-fel tárgyalja. Taggelt dokumentumoknál, ahol a szerkesztési sorrend számít, a struktúrasorrendű szövegkinyerés a struktúrafát járja geometriai találgatás helyett, táblákba zárt adatoknál pedig a típusos táblakinyerés oldaltöréseken át cellákat ad vissza, nem sorokat. Teljes API referencia és trial letöltés a HotPDF Delphi PDF Component termékoldalon