HotPDF Delphi Component skladá medzery medzi slovami a konce riadkov v THotPDF.ExtractLoadedPageText z glyfovej geometrie, nie zo znakov medzery. Medzera sa vloží, keď medzera za vlastnou šírkou glyfu presiahne 0,15 výšky textu, a nový riadok sa začne len vtedy, keď sa pôvod textu posunie naprieč smerom písania o viac než polovicu výšky textu. Od v2.768.3 text strany zahrňuje aj text namaľovaný cez Form XObjects a vynechá glyfy mimo viditeľnej crop oblasti. Zvyšok článku vysvetľuje, prečo každé pravidlo vyzerá tak, ako vyzerá, lebo každé z nich nahradilo jednoduchšie pravidlo, ktoré na reálnych dokumentoch vyrábalo vierohodný, ale zlý výstup
Príznaky pozná každý, kto kedy pustil PDF text do vyhľadávacieho indexu. Obálka sa vyextrahuje ako PDFReferenceManualNovember4,1998, daňový formulár sa rozpadne na 156 riadkov, diagonálny vodoznak dorazí znak na riadok a orezaná tlačová predloha vedie riadkom pre tlačiareň, ktorý žiadny prehliadač nikdy neukáže. Žiadny z týchto súborov nie je pokazený. Každý používa úplne legálny spôsob umiestňovania textu, ktorý naivný extraktor prečíta zle
Prečo extrahovaný PDF text príde o medzery medzi slovami?
Extrahovaný text príde o medzery medzi slovami, lebo PDF ich nikdy obsahovať nemusí. Producent môže slová oddeliť zobrazením znaku medzery, ale rovnako dobre môže pohnúť perom číslom vnútri poľa TJ (ISO 32000-1 §9.4.3) alebo čerstvým Td (§9.4.2) a výstup TeXu, mnohé Distiller súbory aj väčšina zarovnaných rozložení robia presne to. Pred v2.766.76 sa HPDFAssemblePageText pozeral len na vertikálny pohyb, takže zlom slova urobený pozicovaním jednoducho zmizol. Assembler teraz meria v smere písania predchádzajúceho glyfu vzdialenosť od konca jeho vlastnej šírky po pôvod aktuálneho glyfu a vloží jednu medzeru, keď vzdialenosť presiahne 0,15 výšky boxu aktuálneho glyfu, meranej od ascent po descent v user space. Medzera sa nepridá, keď je niektorá strana už prázdna, a žiadna medzi dvoma CJK znakmi, lebo zarovnávanie rozťahuje ideografy bez toho, aby ten rozťah znamenal hranicu slova. Glyfové záznamy vystavujú tú istú geometriu, takže rozhodnutie dokážete reprodukovať, keď vás konkrétny súbor zarazí
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
// výška glyfového boxu od ascent po descent, v user space
Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
// horizontálny text: medzera od konca vlastnej šírky predchádzajúceho glyfu
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;
Prečo merať od vlastnej šírky glyfu namiesto pozície pera?
HotPDF meria medzery medzi slovami od GlyphEndX / GlyphEndY, lebo pozícia pera po glyfe už obsahuje rozostupy, ktoré nie sú medzerou. ISO 32000-1 §9.4.4 definuje horizontálne posunutie ako šírku glyfu krát veľkosť fontu, plus character spacing Tc, plus word spacing Tw, všetko škálované Tz. BaselineEndX / BaselineEndY držia to celé posunutie, zatiaľ čo GlyphEndX / GlyphEndY držia len font advance a Tz. Rozdiel je podstatný pre producentov, ktorí stiahnu tracking záporným Tc a potom priestor vrátia úpravou TJ po každom glyfe: merané z pozície pera vyzerá to vrátenie ako medzera a čínsky termín “95后” sa vyextrahoval ako “9 5 后”. Prah je viazaný na výšku glyfového boxu namiesto veľkosti Tf z podobného dôvodu. Word exporty často zapisujú 1 Tf a skutočnú veľkosť nesú v škálovanom Tm, takže Tfs hovorí 1, kým text je vysoký 10 bodov, a pravidlo zavesené na Tfs by zaobchádzalo s oboma hláskovaniami tej istej strany odlišne
Pravidlo má poctivé okraje. Nadpis zložený s veľmi voľným trackingom, kde Tc samo otvorí medzi písmenami viac než 0,15 výšky textu, sa vyextrahuje s medzerou medzi každým písmenom, čo je to, ako strana vyzerá, ale pravdepodobne nie to, čo ste chceli indexovať. Časti nakreslené neusporiadane na jednom baseline vytvoria zápornú medzeru a spoja sa bez medzery. Ani jeden prípad nie je v tele textu bežný a na testovacom korpuse zmena zvýšila zhody slov oproti referenčnému extraktoru na 28 stranách, ani jednu neznížila
Kedy HotPDF začína v extrahovanom texte nový riadok?
Od v2.766.79 sa nový riadok začína, keď posun od pôvodu predchádzajúceho glyfu k aktuálnemu, premietnutý na normálu predchádzajúceho smeru písania, presiahne polovicu väčšej výšky boxu z oboch glyfov. Skoršie pravidlo porovnávalo surový Y posun s polovicou Tfs, čo zlyhalo v dvoch smeroch. S 1 Tf a škálovaným Tm sa prah scvrkol na polovicu jednotky, takže horný index zdvihnutý text rise 0,4 alebo obyčajný baseline jitter zlámal riadok. Pravidlo tiež úplne ignorovalo X, takže text pod otočeným Tm schádzal po strane s každým glyfom a vyšiel glyf na riadok. Premietnutie na normálu smeru spraví z otočených behov správanie ako z horizontálnych a väčšia z oboch výšok udrží veľké vzorové slovo aj jeho malý popis na jednom riadku, keď zdieľajú baseline. Na spomínanom daňovom formulári klesol počet riadkov zo 156 na 97. Vertikálny text v writing mode 1 (§9.7.4.3) ide po samostatnej ceste: tie glyfy sa zoskupujú do stĺpcov, čítajú sprava doľava a zhora nadol, so zlomom riadka pri každej zmene stĺpca
Ktorý text ExtractLoadedPageText zahrnie a ktorý vynechá?
ExtractLoadedPageText vráti text, ktorý ukazuje prehliadač. Od v2.766.80 pracuje len z viditeľných glyfov a zahodí každý glyf, ktorého stred boxu padá mimo GetLoadedPageVisibleBox, čo je CropBox orezaný na MediaBox (§14.11.2). Tým zmiznú riadky pre tlačiareň a ďalšie tlačové značky uložené ako text mimo orezovej oblasti. ExtractLoadedPageGlyphs zámierne naďalej vracia každý glyf content streamu strany, takže ten materiál nájdete aj vtedy, keď ho potrebujete. Filter je testom boxu, nie testom viditeľnosti: text skrytý za clipping path, namaľovaný bielou alebo prekrytý obrázkom sa vyextrahuje tiež
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]));
// každý glyf content streamu strany, riadok pre tlačiareň vrátane
if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
Writeln(Length(Glyphs), ' glyphs in the page content stream');
// len to, čo strana ukazuje, s vpleteným textom Form XObjects
if Pdf.ExtractLoadedPageText(0, PageText) then
Writeln(PageText);
finally
Pdf.Free;
end;
end;
Text namaľovaný cez Form XObjects je súčasťou textu strany od v2.768.3. Hlavičky, pečiatky a vodoznaky bývajú veľmi často vo formách a niektoré dokumenty noriem stratili pred zmenou 30 až 35 percent svojich znakov. THotPDF.InterpretContentWithForms si zaznamená každý Do spolu s platným CTM, interpretuje formu pri jej /Matrix krát to CTM (§8.10.1) a vpletie glyfy formy na pozíciu Do a rekurzívne zostupí do vnorených foriem. Forma bez vlastných /Resources si požičia tie streamu, ktorý ju maľuje, ako dovoľuje §7.8.3. Glyfy foriem nesú TokenIndex = -1 a ExtractLoadedPageGlyphs stále vracia len glyfy page streamu, lebo hľadanie, nahradzovanie a redakcia zapisujú zmeny späť cez TokenIndex a upravili by zlé bajty, keby sa medzi ne vpletol glyf formy. Dve zjednodušenia stoja za poznanie: text formy sa neorezáva na jej /BBox a rekurzia sa zastaví na 12 úrovniach namiesto detekcie cyklov, takže deformovaná forma, ktorá maľuje seba, opakuje svoj text, kým dospeje k tomu stropu
Prečo sa text po operátore Q dekódoval ako smetie?
Text po Q mohol pred v2.766.73 dekódovať zle, lebo si extraktor pri q uložil len CTM. Parametre textového stavu, teda font, veľkosť, Tc, Tw, Tz, TL, renderovací režim aj rise, patria do grafického stavu (§9.3.1), takže Q ich musí obnoviť spolu so všetkým ostatným na zásobníku (§8.4.2). Jeden priemyselný report vybral dvojbajtový font Identity-H vnútri q … Q a potom ukazoval jednobajtový text WinAnsi bez vlastného Tf. Extraktor si nechal vnútorný font, prečítal bodky riadiace obsah a slovo “Adobe” v obsahu ako dvojbajtové kódy a prišiel o 15 % znakov strany. Zásobník q/Q v interpretri teraz drží celý textový stav. Pravidlá extrakcie popísané tu platia pre každú stranu, takže celý dokument môže ísť do súboru na jedno volanie
var
Output: TFileStream;
Pages: Integer;
begin
Output := TFileStream.Create('report.txt', fmCreate);
try
// prázdny rozsah = všetky strany; form feed medzi stranami; UTF-8 BOM
Pages := Pdf.ExtractLoadedPagesTextToStream(Output, '', #12, True);
Writeln(Pages, ' pages extracted');
finally
Output.Free;
end;
end;
Ktoré textové API HotPDF máte použiť?
ExtractLoadedPageText ostáva v poradí content streamu, čo je správna predvoľba pre hľadanie a indexovanie; dekódovací reťaz pod ňou popisuje článok o extrakcii textu z načítaných PDF s HotPDF. Pre tagované dokumenty, kde záleží na poradí autorovania, prejde extrakcia textu v poradí štruktúry štruktúrovým stromom namiesto hádania z geometrie a pre dáta zamknuté v tabuľkách vráti typovaná extrakcia tabuliek cez zlomy strán bunky namiesto riadkov. Úplná API referencia aj skúšobná verzia sú na produktovej stránke HotPDF Delphi PDF Component