HotPDF Delphi Component obnavlja razmake riječi i prijelome redaka u THotPDF.ExtractLoadedPageText iz geometrije glifova, ne iz znakova razmaka. Razmak ulazi kad procijep iza vlastite širine glifa prijeđe 0.15 visine teksta, a novi redak kreće tek kad se ishodište teksta pomakne preko smjera pisanja za više od pola visine teksta. Od v2.768.3 tekst stranice uključuje i tekst iscrtan kroz Form XObjecte i ostavlja van glifove izvan vidljivog crop područja. Ostatak ovog teksta objašnjava zašto svako pravilo izgleda onako kako izgleda, jer je svako od njih zamijenilo jednostavnije pravilo koje je na stvarnim dokumentima davalo vjerodostojan ali pogrešan izlaz
Simptomi su poznati svakomu tko je PDF tekst ubacio u tražilicu. Naslovnica se izvuče kao PDFReferenceManualNovember4,1998, poreski obrazac se raspadne u 156 redaka, dijagonalni vodeni žig stigne jedan znak po retku, a obrezani probni otisak kreće sa slug linijom tiskara koju nijedan preglednik nikad ne prikaže. Nijedna od tih datoteka nije pokvarena. Svaka koristi savršeno legalan način postavljanja teksta koji naivni extractor pogrešno čita
Zašto izvučeni PDF tekst gubi razmake između riječi?
Izvučeni tekst gubi razmake riječi jer PDF nikad nije dužan sadržavati ih. Proizvođač može razdvojiti riječi prikazujući znak razmaka, ali perom može pomaknuti jednako dobro brojem unutar polja TJ (ISO 32000-1 §9.4.3) ili svježim Tdom (§9.4.2), i to točno čine TeX izlaz, mnoge Distiller datoteke i većina razvučenih postava. Prije v2.766.76 HPDFAssemblePageText gledao je samo okomito kretanje, pa je prijelom riječi učinjen pozicioniranjem jednostavno nestao. Assembler sada mjeri, duž smjera pisanja prethodnog glifa, udaljenost od kraja vlastite širine tog glifa do ishodišta trenutnog glifa, i ubaci jedan razmak kad udaljenost prijeđe 0.15 visine kutije trenutnog glifa, mjereno od ascenta do descenta u user spaceu. Razmak se ne dodaje kad je bila koja strana već prazna, ni između dva CJK znaka, jer razvlačenje razvlazi ideograme bez da to razvlačenje znači granicu riječi. Zapisi glifova izlažu istu geometriju, pa odluku možete reproducirati kad vas neka datoteka muči
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
// visina kutije glifa od ascenta do descenta, u user spaceu
Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
// horizontalni tekst: procijep od kraja vlastite širine prethodnog glifa
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;
Zašto mjeriti od vlastite širine glifa umjesto od pozicije pera?
HotPDF mjeri procijepe riječi od GlyphEndX / GlyphEndY jer pozicija pera iza glifa već sadrži razmake koji nisu procijep. ISO 32000-1 §9.4.4 definira vodoravni pomak kao širinu glifa puta veličinu fonta, plus razmak znakova Tc, plus razmak riječi Tw, sve skalirano s Tz. BaselineEndX / BaselineEndY nose taj puni pomak, dok GlyphEndX / GlyphEndY nose samo fontovski advance i Tz. Razlika je bitna za proizvođače koji stegnu tracking negativnim Tcom, a onda prostor vrate TJ prilagodbom iza svakog glifa: mjereno od pozicije pera, vraćeni prostor izgleda kao procijep, i kineski je izraz “95后” izvučen kao “9 5 后”. Prag je vezan uz visinu kutije glifa, a ne uz veličinu Tfa iz sličnog razloga. Word izvozi često zapišu 1 Tf i pravu veličinu nose u skaliranoj Tm, pa Tfs kaže 1 dok je tekst visok 10 točaka, i pravilo vezano uz Tfs tretiralo bi dva zapisa iste stranice različito
Pravilo ima poštene rubove. Naslov posložen vrlo labavim trackingom, gdje sam Tc otvori više od 0.15 visine teksta između slova, izvlači se s razmakom između svakog slova, što je ono kako stranica izgleda ali vjerojatno nije ono što ste željeli indeksirati. Komadi nacrtani izvan reda na jednoj baselini daju negativan procijep i spoje se bez razmaka. Nijedan slučaj nije čest u tjelesnom tekstu, a na testnom korpusu je izmjena podigla poklapanja riječi prema referentnom extractoru na 28 stranica, bez da ijedno spustila
Kad HotPDF počinje novi redak u izvučenom tekstu?
Od v2.766.79 novi redak kreće kad pomak od ishodišta prethodnog glifa do trenutnog, projiciran na normalu prethodnog smjera pisanja, prijeđe polovinu veće visine kutije dvaju glifova. Ranije je pravilo uspoređivalo sirovi Y pomak s polovicom Tfsa, što je padalo u dva smjera. S 1 Tfom i skaliranom Tm prag se stegnuo na pola jedinice, pa je superskript podignut text riseom od 0.4 ili obično treperenje baseline slomilo redak. Pravilo je X u cijelosti ignoriralo, pa je tekst pod rotiranom Tm silazio stranicom sa svakim glifom i izlazio jedan glif po retku. Projekcija na normalu smjera čini da se rotirani nizovi ponašaju poput vodoravnih, a uzimanje veće od dviju visina drži veliku uzorčanu riječ i njezin mali natpis u jednom retku kad dijele baseline. Na spomenutom poreskom obrascu broj redaka pao je sa 156 na 97. Okomiti tekst u writing mode 1 (§9.7.4.3) ide zasebnim putem: ti se glifovi grupiraju u stupce, čitaju s desna na lijevo i od gore prema dolje, s prijelomom retka pri svakoj promjeni stupca
Koji tekst ExtractLoadedPageText uključuje, a koji ostavlja van?
ExtractLoadedPageText vraća tekst koji preglednik prikaže. Od v2.766.80 radi samo s vidljivim glifovima, bacajući svaki glif čije središte kutije padne izvan GetLoadedPageVisibleBoxa, koji je CropBox izrezan na MediaBox (§14.11.2). To uklanja slug linije i ostale tiskarske oznake posložene kao tekst izvan trim područja. ExtractLoadedPageGlyphs namjerno i dalje vraća svaki glif content streama stranice, pa taj materijal možete naći i kad vam zatreba. Filter je test kutije, ne test vidljivosti: tekst skriven clipping pathom, iscrtan u bijelo ili prekriven slikom i dalje se izvlači
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]));
// svaki glif content streama stranice, uključivo slug liniju
if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
Writeln(Length(Glyphs), ' glyphs in the page content stream');
// samo ono što stranica prikaže, s utkanim tekstom Form XObjecta
if Pdf.ExtractLoadedPageText(0, PageText) then
Writeln(PageText);
finally
Pdf.Free;
end;
end;
Tekst iscrtan kroz Form XObjecte dio je teksta stranice od v2.768.3. Zaglavlja, pečati i vodeni žigovi vrlo često žive u formama, i neki su standardski dokumenti prije izmjene izgubili 30 do 35 posto svojih znakova. THotPDF.InterpretContentWithForms zabilježi svaki Do zajedno s CTM-om na snazi, interpretira formu na njezinoj /Matrix pomnoženoj s tim CTM-om (§8.10.1), i utka glifove forme na poziciji Doa, rekurzivno u ugniježđene forme. Forma bez vlastitih /Resources posuđuje one streama koji je crta, kako dopušta §7.8.3. Form glifovi nose TokenIndex = -1, i ExtractLoadedPageGlyphs i dalje vraća samo glifove page streama, jer search, replace i redakcija upisuju promjene natrag kroz TokenIndex i uređivali bi krive bajtove da se form glif ušunjao. Dvije su pojednostavnjenja vrijedna poznavanja: form tekst ne reže se na formin /BBox, a rekurzija staje na 12 razina umjesto detekcijom ciklusa, pa deformirana forma koja crta samu sebe ponavlja svoj tekst dok ne dosegne taj strop
Zašto se tekst iza operatora Q dekodirao u besmislice?
Tekst iza Q mogao se pogrešno dekodirati prije v2.766.73 jer je extractor na q snimao samo CTM. Parametri tekstovnog stanja, dakle font, veličina, Tc, Tw, Tz, TL, način renderiranja i rise, pripadaju grafičkom stanju (§9.3.1), pa Q mora vratiti njih zajedno sa svime ostalim na stacku (§8.4.2). Jedno je industrijsko izvješće unutar q … Q odabralo dvobajtni Identity-H font, pa zatim prikazalo jednobajtni WinAnsi tekst bez vlastitog Tfa. Extractor je zadržao unutarnji font, pročitao vodilje i riječ “Adobe” u sadržaju kao dvobajtne kodove, i bacio 15% znakova stranice. Stack q/Q interpretera sada drži puno tekstovno stanje. Pravila izvlačenja opisana ovdje vrijede za svaku stranicu, pa cijeli dokument može u datoteku jednim pozivom
var
Output: TFileStream;
Pages: Integer;
begin
Output := TFileStream.Create('report.txt', fmCreate);
try
// prazan raspon = sve stranice; form feed između stranica; UTF-8 BOM
Pages := Pdf.ExtractLoadedPagesTextToStream(Output, '', #12, True);
Writeln(Pages, ' pages extracted');
finally
Output.Free;
end;
end;
Koji HotPDF tekstovni API biste trebali koristiti?
ExtractLoadedPageText ostaje u redoslijedu content streama, što je pravi zadani izbor za search i indeksiranje; dekodirani lanac ispod njega pokriva izvlačenje teksta iz učitanih PDF-ova s HotPDF-om. Za tagirane dokumente kojima je redoslijed autorstva bitan, izvlačenje teksta u redoslijedu strukture hoda strukturnim stablom umjesto da pogađa iz geometrije, a za podatke zarobljene u tablicama tipizirano izvlačenje tablica preko prijeloma stranica vraća ćelije umjesto redaka. Potpuna API referenca i trial preuzimanje su na stranici proizvoda HotPDF Delphi PDF Component