HotPDF Delphi Component znova zgradi presledke besed in prelome vrstic v THotPDF.ExtractLoadedPageText iz geometrije glifov, ne iz znakov presledka. Presledek pride noter, ko razmik po lastni širini glife preseže 0.15 višine teksta, nova vrstica pa se začne šele, ko se izhodišče teksta premakne čez smer pisanja za več kot polovico višine teksta. Od v2.768.3 tekst strani vključuje tudi tekst, naslikan skozi Form XObjects, in pušča ven glife izven vidnega obreznega območja. Preostanek tega prispevka razloži, zakaj vsako pravilo izgleda tako, kot izgleda, ker je vsako zamenjalo preprostejše pravilo, ki je na resničnih dokumentih dajalo verjeten, a napačen izhod
Simptome pozna vsak, kdor je že dal tekst PDF v iskalni indeks. Naslovnica se izvleče kot PDFReferenceManualNovember4,1998, davčni obrazec se razpade na 156 vrstic, diagonalni vodni žig pride en znak na vrstico, obrezani pogravt pa se začne s tiskarsko kontrolno vrstico, ki je noben pregledovalnik ne pokaže. Nobena od teh datotek ni pokvarjena. Vsaka uporablja povsem zakonit način postavljanja teksta, ki ga naiven izvlečevalnik prebere narobe
Zakaj izvlečeni tekst PDF izgubi svoje presledke?
Izvlečeni tekst izgubi presledke, ker jih PDF ni nikoli dolžan vsebovati. Proizvajalec lahko loči besede s prikazom znaka presledka, lahko pa enakovredno premakne pero s številom znotraj polja TJ (ISO 32000-1 §9.4.3) ali s svežim Td (§9.4.2), izpis TeX, marsikatera datoteka Distiller in večina obojestransko poravnanih postavitev pa točno to počnejo. Pred v2.766.76 je HPDFAssemblePageText gledal samo navpični premik, zato je prelom besede, narejen s pozicioniranjem, preprosto izginil. Sestavljalnik zdaj meri, v smeri pisanja prejšnje glife, razdaljo od konca lastne širine tiste glife do izhodišča trenutne glife, in vstavi en presledek, ko razdalja preseže 0.15 višine okvirja trenutne glife, merjeno od vzpona do spusta v uporabniškem prostoru. Presledek se ne doda, kadar je katera koli stran že prazna, in ne med dvema znakoma CJK, ker poravnava raztegne ideografe narazen, ne da bi ta raztegnitev pomenila mejo besede. Zapisi glifov izpostavijo isto geometrijo, zato lahko odločitev ponovite, ko vas določena datoteka zmede
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
// višina okvirja glife od vzpona do spusta, v uporabniškem prostoru
Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
// vodoravni tekst: razmik od konca lastne širine prejšnje glife
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;
Zakaj meriti od lastne širine glife namesto od položaja peresa?
HotPDF meri razmike besed od GlyphEndX / GlyphEndY, ker položaj peresa za glifo že vsebuje razmaknjenost, ki ni razmik. ISO 32000-1 §9.4.4 definira vodoravni premik kot širino glife krat velikost pisave, plus razmik znakov Tc, plus razmik besed Tw, vse skalirano s Tz. BaselineEndX / BaselineEndY hranita ta polni premik, GlyphEndX / GlyphEndY pa samo pisavni zasuk in Tz. Razlika je pomembna za proizvajalce, ki stisnejo sledenje z negativnim Tc in nato vrnejo prostor skozi prilagoditev TJ za vsako glifo: merjeno od položaja peresa, vračilo izgleda kot razmik, in kitajski izraz “95后” se je izvlekel kot “9 5 后”. Prag je vezan na višino okvirja glife namesto na velikost Tf iz podobnega razloga. Izvozi Word pogosto zapišejo 1 Tf in nosijo pravo velikost v skaliranem Tm, zato Tfs pravi 1, medtem ko je tekst visok 10 točk, in pravilo, ključano na Tfs, bi obe črkovanji iste strani obravnavalo različno
Pravilo ima iskrene robove. Naslov, postavljen z zelo ohlapnim sledenjem, kjer sam Tc odpre med črkami več kot 0.15 višine teksta, se izvleče s presledkom med vsako črko, kar je tisto, kar stran pokaže, a verjetno ne tisto, kar ste želeli indeksirati. Deli, narisani izven vrstnega reda na eni osnovni črti, dajo negativen razmik in se združijo brez presledka. Noben primer ni pogost v glavnem tekstu, sprememba pa je na testnem korpusu dvignila ujemanja besed glede na referenčni izvlečevalnik na 28 straneh, brez da bi katero znižala
Kdaj HotPDF začne novo vrstico v izvlečenem tekstu?
Od v2.766.79 se nova vrstica začne, ko premik od izhodišča prejšnje glife do trenutne, projiciran na normalo prejšnje smeri pisanja, preseže polovico večje višine okvirja obeh glif. Zgodnejše pravilo je primerjalo surov premik Y s polovico Tfs, kar je odpovedalo v dveh smereh. Z 1 Tf in skaliranim Tm se je prag skrčil na pol enote, zato je nadpis, dvignjen z dvigom teksta 0.4, ali navadno tresenje osnovne črte prelomilo vrstico. Pravilo je tudi v celoti spregledalo X, zato je tekst pod zasukanim Tm stopal po strani navzdol z vsako glifo in izšel ena glifa na vrstico. Projekcija na normalo smeri naredi zasukane teke podobne vodoravnim, vzeti večjo od obeh višin pa obdrži veliko vzorčno besedo in njeno majhno pripombo v eni vrstici, ko si delita osnovno črto. Na zgoraj omenjenem davčnem obrazcu se je število vrstic znižalo s 156 na 97. Navpični tekst v načinu pisanja 1 (§9.7.4.3) sledi ločeni poti: ti glifi se grupirajo v stolpce, berejo se od desne levo in od zgoraj navzdol, s prelomom vrstice ob vsaki spremembi stolpca
Kateri tekst vključi ExtractLoadedPageText in katerega pusti ven?
ExtractLoadedPageText vrne tekst, ki ga pokaže pregledovalnik. Od v2.766.80 dela iz vidnih glifov samo, odvrže vsako glifo, katere središče okvirja pade izven GetLoadedPageVisibleBox, kar je CropBox obrezan na MediaBox (§14.11.2). To odstrani kontrolne vrstice in druge tiskarske oznake, postavljene kot tekst izven območja reza. ExtractLoadedPageGlyphs namenoma še naprej vrača vsako glifo toka vsebine strani, zato tistega gradiva še vedno najdete, ko ga potrebujete. Filter je preizkus okvirja, ne preizkus vidnosti: tekst, skrit s potjo izreza, naslikan v beli ali pokrit s sliko, se še vedno izvleče
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]));
// vsaka glifa toka vsebine strani, vključno s kontrolno vrstico
if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
Writeln(Length(Glyphs), ' glyphs in the page content stream');
// samo to, kar stran pokaže, s spricanim tekstom Form XObject
if Pdf.ExtractLoadedPageText(0, PageText) then
Writeln(PageText);
finally
Pdf.Free;
end;
end;
Tekst, naslikan skozi Form XObjects, je del teksta strani od v2.768.3. Glave, žigi in vodni žigi zelo pogosto živijo v obrazcih, nekatere dokumenti standardov pa so pred spremembo izgubili 30 do 35 odstotkov svojih znakov. THotPDF.InterpretContentWithForms zabeleži vsak Do skupaj z veljavnim CTM, interpretira obrazec pri njegovi /Matrix krat ta CTM (§8.10.1) in sprica glife obrazca noter na položaju Do, z rekurzijo v gnezdene obrazce. Obrazec brez lastnih /Resources si izposodi tiste toka, ki ga riše, kot to dovoli §7.8.3. Glife obrazcev nosijo TokenIndex = -1, ExtractLoadedPageGlyphs pa še vedno vrača samo glife toka strani, ker iskanje, zamenjava in izrez zapišejo spremembe nazaj skozi TokenIndex in bi uredili napačne bajte, če bi zdrsnila notri glifa obrazca. Dve poenostavitvi sta vredni vedenja: tekst obrazca ni obrezan na obrazčev /BBox, rekurzija pa se ustavi pri 12 ravneh, namesto prek zaznavanja ciklov, zato deformirani obrazec, ki riše sebe, ponavlja svoj tekst, dokler ne doseže te meje
Zakaj se je tekst za operatorjem Q dekodiral kot smeti?
Tekst za Q se je pred v2.766.73 lahko dekodiral narobe, ker je izvlečevalnik na q shranil samo CTM. Parametri tekstovnega stanja, in sicer pisava, velikost, Tc, Tw, Tz, TL, način izrisovanja in dvig, pripadajo grafičnemu stanju (§9.3.1), zato jih mora Q povrniti skupaj z vsem ostalim na skladu (§8.4.2). Eno industrijsko poročilo je izbralo dvo-bajtno pisavo Identity-H znotraj q … Q in nato prikazalo eno-bajtni tekst WinAnsi brez lastnega Tf. Izvlečevalnik je obdržal notranjo pisavo, prebral vodila in besedo “Adobe” v kazalu vsebine kot dvo-bajtne kode in odvrgel 15 % znakov strani. Sklad q/Q interpreterja zdaj drži polno tekstovno stanje. Pravila izvlečenja, opisana tukaj, veljajo za vsako stran, zato lahko cel dokument gre v datoteko z enim klicem
var
Output: TFileStream;
Pages: Integer;
begin
Output := TFileStream.Create('report.txt', fmCreate);
try
// prazen obseg = vse strani; form feed med stranmi; UTF-8 BOM
Pages := Pdf.ExtractLoadedPagesTextToStream(Output, '', #12, True);
Writeln(Pages, ' pages extracted');
finally
Output.Free;
end;
end;
Kateri API za tekst HotPDF naj uporabite?
ExtractLoadedPageText ostane v vrstnem redu toka vsebine, kar je prava privzeta izbira za iskanje in indeksiranje; verigo dekodiranja pod njim pokriva izvlečenje teksta iz naloženih PDF s HotPDF. Za označene dokumente, kjer je vrstni red avtorstva pomemben, izvlečenje teksta v vrstnem redu strukture prehodi drevo strukture, namesto da bi ugibal iz geometrije, za podatke, ujete v tabelah, pa tipizirano izvlečenje tabel čez prelome strani vrača celice namesto vrstic. Popolna referenca API in preizkusni prenos so na strani izdelka HotPDF Delphi PDF Component