HotPDF Delphi Component obnavlja razmake između reči i prelome redova u THotPDF.ExtractLoadedPageText-u iz geometrije glifova, a ne iz znakova razmaka. Razmak se ubacuje kad razmak iza sopstvene širine glifa pređe 0,15 visine teksta, a nov red počinje samo kad se koordinatni početak teksta pomeri preko smera pisanja za više od polovine visine teksta. Od v2.768.3 tekst stranice uključuje i tekst iscrtan kroz Form XObjecte i ostavlja glifove van vidljive crop oblasti. Ostatak ovog teksta objašnjava zašto svako pravilo izgleda onako kako izgleda, jer je svako od njih zamenilo jednostavnije pravilo koje je na pravim dokumentima davalo uverljiv ali pogrešan izlaz
Simptome poznaje svako ko je hranio PDF tekst u indeks pretrage. Naslovna stranica se izvuče kao PDFReferenceManualNovember4,1998, poreski obrazac se raspadne na 156 redova, dijagonalni vodeni žig stiže znak po red, a odsečen probni otisak počinje slug linijom štampača koju nijedan pregledač nikada ne pokazuje. Ni jedan od tih fajlova nije pokvaren. Svaki koristi sasvim legalan način postavljanja teksta koji naivan izvlakač pročita pogrešno
Zašto izvučeni PDF tekst gubi razmake između reči?
Izvučeni tekst gubi razmake jer PDF nikada nije dužan da ih sadrži. Proizvođač može da razdvoji reči ispisivanjem znaka razmaka, ali može isto tako da pomeri pero brojem unutar TJ niza (ISO 32000-1 §9.4.3) ili svežim Td-om (§9.4.2), i TeX izlaz, mnogi Distiller fajlovi i većina poravnatih rasporeda radi baš to. Pre v2.766.76, HPDFAssemblePageText gledao je samo vertikalno kretanje, pa se prelom reči učinjen pozicioniranjem jednostavno izgubio. Sastavljač sada meri, duž smera pisanja prethodnog glifa, rastojanje od kraja sopstvene širine tog glifa do koordinatnog početka tekućeg glifa, i ubacuje jedan razmak kad rastojanje pređe 0,15 visine kutije tekućeg glifa, merene od uzlazne do izlazne linije u user space-u. Razmak se ne dodaje kad je ijedna strana već prazna, ni između dva CJK znaka, jer poravnavanje razvlači ideograme a to razvlačenje ne znači granicu reči. Zapisi glifova izlažu istu geometriju, pa možete ponoviti odluku kad vas neki fajl zbuni
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 uzlazne do izlazne linije, u user space-u
Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
// horizontalni tekst: razmak od kraja sopstvene š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 meriti od sopstvene širine glifa umesto od pozicije pera?
HotPDF meri razmake reči od GlyphEndX / GlyphEndY-a jer pozicija pera posle glifa već sadrži razmake koji nisu prazan prostor. ISO 32000-1 §9.4.4 definiše horizontalno pomeranje kao širinu glifa puta veličina fonta, plus međuznakovni razmak Tc, plus razmak reči Tw, sve skalirano sa Tz. BaselineEndX / BaselineEndY drže celo to pomeranje, dok GlyphEndX / GlyphEndY drže samo napredovanje fonta i Tz. Razlika je bitna za proizvođače koji sabijaju međuslovje negativnim Tc-om pa potom vraćaju prostor TJ korekcijom posle svakog glifa: mereno sa pozicije pera, vraćanje izgleda kao prazan prostor, i kineski izraz “95后” izvučen je kao “9 5 后”. Prag je vezan za visinu kutije glifa a ne za Tf veličinu iz sličnog razloga. Word izvozi često upisuju 1 Tf i nose pravu veličinu u skaliranom Tm-u, pa Tfs kaže 1 dok je tekst visok 10 poena, i pravilo vezano za Tfs tretiralo bi dva zapisa iste stranice različito
Pravilo ima poštene ivice. Naslov postavljen vrlo raščenim međuslovjem, gde samo Tc otvori više od 0,15 visine teksta između slova, izvlači se sa razmakom između svakog slova, što je ono kako stranica izgleda ali verovatno ne ono što ste želeli da indeksirate. Delovi iscrtani van reda na jednoj osnovnoj liniji daju negativan razmak i spajaju se bez razmaka. Ni jedan slučaj nije čest u tekstu, i na test korpusu je promena digla poklapanja reči sa referentnim izvlakačem na 28 stranica a nijedno nije smanjila
Kada HotPDF počinje novi red u izvučenom tekstu?
Od v2.766.79, nov red počinje kad pomeranje od koordinatnog početka prethodnog glifa do tekućeg, projicirano na normalu prethodnog smera pisanja, pređe polovinu veće visine kutije dva glifa. Ranije pravilo je poredilo sirovo Y pomeranje sa polovinom Tfs, što je padalo u oba smera. Sa 1 Tf i skaliranim Tm-om prag se smanjio na pola jedinice, pa je eksponent podignut text rise-om od 0,4 ili obično treperenje osnovne linije lomilo red. Pravilo je takođe potpuno ignorišilo X, pa je tekst pod rotiranim Tm-om stepao niz stranicu sa svakim glifom i izlazio jedan glif po redu. Projekcija na normalu smera čini da rotirani nizovi rade kao horizontalni, a uzimanje veće od dve visine drži veliku uzorčnu reč i njen mali potpis u jednom redu kad dele osnovnu liniju. Na pomenutom poreskom obrascu broj redova pao je sa 156 na 97. Vertikalni tekst u writing modu 1 (§9.7.4.3) ide posebnim putem: ti glifovi se grupišu u kolone, čitaju s desna na levo i odozgo nadole, sa prelomom reda pri svakoj promeni kolone
Koji tekst ExtractLoadedPageText uključuje, a koji ostavlja?
ExtractLoadedPageText vraća tekst koji pregledač pokazuje. Od v2.766.80 radi samo sa vidljivim glifovima, izbacujući svaki glif čiji centar kutije ispadne van GetLoadedPageVisibleBox-a, što je CropBox odsečen na MediaBox (§14.11.2). Time nestaju slug linije i druge štamparske oznake postavljene kao tekst van oblasti odsecanja. ExtractLoadedPageGlyphs namerno i dalje vraća svaki glif content streama stranice, pa taj materijal možete naći kad vam zatreba. Filter je test kutije, ne test vidljivosti: tekst skriven clipping putanjom, ispisan belo ili pokriven 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, slug linija uključena
if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
Writeln(Length(Glyphs), ' glyphs in the page content stream');
// samo ono što stranica pokazuje, uz ubačen tekst Form XObjecta
if Pdf.ExtractLoadedPageText(0, PageText) then
Writeln(PageText);
finally
Pdf.Free;
end;
end;
Tekst ispisan kroz Form XObjecte deo je teksta stranice od v2.768.3. Zaglavlja, žigovi i vodeni žigovi vrlo često žive u formama, i neki standardni dokumenti su izgubili 30 do 35 posto znakova pre ove promene. THotPDF.InterpretContentWithForms beleži svaki Do zajedno sa CTM-om na snazi, interpretira formu na njenom /Matrix-u puta taj CTM (§8.10.1), i ubacuje glifove forme na poziciji Do-a, rekurzivno ulazeći u ugnežđene forme. Forma bez sopstvenog /Resources-a pozajmljuje one streama koji je ispisuje, kako §7.8.3 i dopušta. Formski glifovi nose TokenIndex = -1, i ExtractLoadedPageGlyphs i dalje vraća samo glifove page streama, jer pretraga, zamena i redakcija upisuju izmene nazad kroz TokenIndex i uređivali bi pogrešne bajtove da se formski glif ušunjao. Dve pojednostavljene vredi znati: formski tekst se ne odseca na /BBox forme, a rekurzija staje na 12 nivoa umesto detekcijom ciklusa, pa neispravna forma koja iscrtava samu sebe ponavlja svoj tekst dok ne dostigne taj plafon
Zašto se tekst posle Q operatora dekodovao kao smeće?
Tekst posle Q-a mogao je da se dekoduje pogrešno pre v2.766.73 jer je izvlakač na q-u čuvao samo CTM. Parametri tekst stanja, dakle font, veličina, Tc, Tw, Tz, TL, režim iscrtavanja i podizanje, pripadaju grafičkom stanju (§9.3.1), pa Q mora da ih povrati zajedno sa svime ostalim na steku (§8.4.2). Jedan industrijski izveštaj izabrao je dvobajtni Identity-H font unutar q … Q-a pa zatim pokazao jednobajtni WinAnsi tekst bez sopstvenog Tf-a. Izvlakač je zadržao unutrašnji font, pročitao vodeće tačke i reč “Adobe” u sadržaju kao dvobajtne kodove, i izbacio 15% znakova stranice. Interpretatorov q/Q stek sada drži celo tekst stanje. Pravila izvlačenja opisana ovde važe za svaku stranicu, pa ceo dokument može u fajl jednim pozivom
var
Output: TFileStream;
Pages: Integer;
begin
Output := TFileStream.Create('report.txt', fmCreate);
try
// prazan opseg = 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 API za tekst da koristite?
ExtractLoadedPageText ostaje u redosledu content streama, što je prava podrazumevana vrednost za pretragu i indeksiranje; dekoding lanac ispod njega pokriven je u tekstu o izvlačenju teksta iz učitanih PDF-ova uz HotPDF. Za tagovane dokumente gde je redosled autorstva bitan, izvlačenje teksta po redosledu strukture šeta strukturno stablo umesto da pogađa iz geometrije, a za podatke zaključane u tabelama, tipizirano izvlačenje tabela preko preloma stranica vraća ćelije umesto redova. Potpuna API referenca i probno preuzimanje nalaze se na stranici proizvoda HotPDF Delphi PDF Component