HotPDF Delphi Component rakentaa sanavälit ja rivinvaihdot metodissa THotPDF.ExtractLoadedPageText glyyfigeometriasta, eivät välilyöntimerkeistä. Väilyönti tulee väliin, kun glyyfin oman leveyden jälkeinen rako ylittää 0,15 tekstikorkeudesta, ja uusi rivi alkaa vasta, kun tekstiorigo siirtyy kirjoitussuunnan yli yli puolet tekstikorkeudesta. Versiosta v2.768.3 alkaen sivuteksti sisältää myös Form XObjectien kautta piirretyn tekstin ja jättää pois näkyvän rajausalueen ulkopuoliset glyyfit. Tämän kirjoituksen loppuosa selittää, miksi jokainen sääntö näyttää siltä miltä näyttää, koska jokainen niistä korvasi yksinkertaisemman säännön, joka tuotti uskottavaa mutta väärää tulosta oikeissa asiakirjoissa
Oireet ovat tuttuja jokaiselle, joka on syöttänyt PDF-tekstiä hakemistoon. Kansisivu poimitaan muodossa PDFReferenceManualNovember4,1998, verolomake hajoaa 156 riviksi, vinossa oleva vesileima saapuu yksi merkki rivillä kohden, ja rajattu vedos alkaa tulostimen slug-rivillä, jota mikään katselin koskaan näytä. Yksikään näistä tiedostoista ei ole rikki. Jokainen käyttää täysin laillista tapaa sijoittaa tekstiä, jonka naiivi poimija lukee väärin
Miksi poimittu PDF-teksti menettää sanavälinsä?
Poimittu teksti menettää sanavälit, koska PDF:n ei koskaan vaadita sisältävän niitä. Tuottaja voi erottaa sanat näyttämällä välilyöntimerkin, mutta se voi yhtä hyvin siirtää kynää TJ-taulukon numerolla (ISO 32000-1 §9.4.3) tai tuoreella Tdllä (§9.4.2), ja TeX-tuloste, monet Distiller-tiedostot ja useimmat tasatut asettelut tekevät juuri niin. Ennen v2.766.76 HPDFAssemblePageText katsoi vain pystysuuntaista liikettä, joten sijoittelulla tehty sanaväli katosi yksinkertaisesti. Kokoonpanija mittaa nyt edellisen glyyfin kirjoitussuuntaa pitkin etäisyyden kyseisen glyyfin oman leveyden päästä nykyisen glyyfin origoon ja lisää yhden välilyönnin, kun etäisyys ylittää 0,15 nykyisen glyyfin laatikon korkeudesta, mitattuna ascentista descentiin käyttäjätilassa. Väilyöntiä ei lisätä, kun kumpikaan puoli on jo tyhjä, eikä kahden CJK-merkin väliin, koska tasa venyttää ideografeja erilleen ilman, että venytys tarkoittaisi sanarajaa. Glyyfitietueet paljastavat saman geometrian, joten voit toistaa päätöksen, kun jokin tiedosto askarruttaa
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
// glyyfilaatikon ascentista descentiin -korkeus, käyttäjätilassa
Height := Sqrt(Sqr(Glyphs[I].QuadX[3] - Glyphs[I].QuadX[0]) +
Sqr(Glyphs[I].QuadY[3] - Glyphs[I].QuadY[0]));
// vaakateksti: rako edellisen glyyfin oman leveyden päästä
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;
Miksi mitata glyyfin omasta leveydestä kynäaseman sijaan?
HotPDF mittaa sanaraot arvoista GlyphEndX / GlyphEndY, koska glyyfin jälkeinen kynäasema sisältää jo väistystä, joka ei ole rako. ISO 32000-1 §9.4.4 määrittelee vaakasiirtymän kaavana glyyfin leveys kertaa fonttikoko, plus merkistäväystä Tc, plus sanaväli Tw, kaikki skaalattuna arvolla Tz. BaselineEndX / BaselineEndY kantavat koko siirtymän, kun taas GlyphEndX / GlyphEndY kantavat vain fontin etenemän ja Tzin. Ero merkitsee tuottajille, jotka kiristävät kirjainsväystä negatiivisella Tcllä ja antavat tilan sitten takaisin TJ-säädön kautta jokaisen glyyfin jälkeen: kynäasemasta mitattuna takaisinanto näyttää raolta, ja kiinalainen termi “95后” poimintaan muodossa “9 5 后”. Kynnys on sidottu glyyfilaatikon korkeuteen Tf-koon sijaan samasta syystä. Word-viennit kirjoittavat usein arvon 1 Tf ja kantavat oikeaa kokoa skaalatussa Tmssä, joten Tfs sanoo 1, vaikka teksti on 10 pistettä korkea, ja Tfs:een sidottu sääntö kohtelisi saman sivun kahta kirjoitusasua eri tavalla
Säännöllä on rehelliset reunat. Otsikko, joka on ladattu hyvin löyhällä kirjainsväyställä, jossa pelkkä Tc avaa yli 0,15 tekstikorkeudesta kirjainten väliin, poimitaan välilyönnillä jokaisen kirjaimen välissä, mikä on se, miltä sivu näyttää, mutta tuskin sitä, mitä halusit indeksoida. Poikkeuksellisessa järjestyksessä samalle peruslinjalle piirretyt palaset tuottavat negatiivisen raon ja liittyvät ilman välilyöntiä. Kumpikaan tapaus ei ole yleinen leipätekstissä, ja testikorpuksella muutos nosti sanatäsmäyksiä referenssipoimijaan verrattuna 28 sivulla laskematta yhtään
Milloin HotPDF aloittaa uuden rivin poimitussa tekstissä?
Versiosta v2.766.79 alkaen uusi rivi alkaa, kun siirtymä edellisen glyyfin origosta nykyiseen, projisoituna edellisen kirjoitussuunnan normaalin päälle, ylittää puolet glyyfien kahdesta suuremmasta laatikkokorkeudesta. Aiempi sääntö vertasi raakaa Y-siirtymää puoleen arvosta Tfs, mikä epäonnistui kahteen suuntaan. Arvolla 1 Tf ja skaalatulla Tmillä kynnys kutistui puoleen yksikköön, joten yläindeksi, jonka tekstin noste on 0,4, tai tavallinen peruslinjan tärinä rikkoi rivin. Sääntö ohitti myös X:n kokonaan, joten kierretyn Tmin alla oleva teksti astui alas sivua jokaisen glyyfin kohdalla ja tuli ulos yhtenä glyyfinä per rivi. Projektointi suunnan normaalin päälle saa kierretyt ajot käyttäytymään kuin vaakasuoria, ja kahdesta korkeudesta suuremman ottaminen pitää ison näyttesanan ja sen pienen kuvatekstin yhdellä rivillä, kun ne jakavat peruslinjan. Yllä mainitulla verolomakkeella rivimäärä putosi arvosta 156 arvoon 97. Pystyteksti kirjoitustilassa 1 (§9.7.4.3) seuraa erillistä polkua: ne glyyfit ryhmitellään sarakkeisiin, luetaan oikealta vasemmalle ja ylhäältä alas, rivinvaihdolla jokaisen sarakevaihdoksen kohdalla
Minkä tekstin ExtractLoadedPageText ottaa mukaan ja minkä jättää pois?
ExtractLoadedPageText palauttaa tekstin, jonka katselin näyttää. Versiosta v2.766.80 alkaen se toimii vain näkyvistä glyyfeistä pudottaen jokaisen glyyfin, jonka laatikon keskipiste putoaa ulos GetLoadedPageVisibleBoxista, joka on CropBox rajattuna MediaBoxiin (§14.11.2). Se poistaa slug-rivit ja muut tulostimen merkinnät, jotka on ladottu tekstinä rajausalueen ulkopuolelle. ExtractLoadedPageGlyphs tahallaan jatkaa jokaisen sivun sisältöstreamin glyyfin palauttamista, joten löydät kyseisen materiaalin yhä, kun tarvitset sitä. Suodatin on laatikkotesti, ei näkyvyystesti: leikkauspolulla piilotettu, valkoisella maalattu tai kuvan peittämä teksti poimitaan yhä
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]));
// jokainen sivun sisältöstreamin glyyfi, slug-rivi mukaan lukien
if Pdf.ExtractLoadedPageGlyphs(0, Glyphs) then
Writeln(Length(Glyphs), ' glyphs in the page content stream');
// vain se, mitä sivu näyttää, Form XObject -teksti sisällytettynä
if Pdf.ExtractLoadedPageText(0, PageText) then
Writeln(PageText);
finally
Pdf.Free;
end;
end;
Form XObjectien kautta piirretty teksti kuuluu sivutekstiin versiosta v2.768.3 alkaen. Otsikot, leimat ja vesileimat asuvat hyvin usein formeissa, ja jotkut standardiasiakirjat menettivät ennen muutosta 30–35 prosenttia merkeistään. THotPDF.InterpretContentWithForms kirjaa jokaisen Don yhdessä voimassa olevan CTM:n kanssa, tulkitsee formin sen /Matrixin kertaa kyseinen CTM (§8.10.1) ja liittää formin glyyfit paikoilleen Don kohdalle, rekursiolla sisäkkäisiin formeihin. Formi ilman omaa /Resourcesia lainaa sen streamin resurssit, joka sen piirtää, niin kuin §7.8.3 sallii. Formiglyyfit kantavat arvoa TokenIndex = -1, ja ExtractLoadedPageGlyphs palauttaa yhä vain sivustreamin glyyfit, koska haku, korvaus ja mustaus kirjoittavat muutokset takaisin TokenIndexin kautta ja muokkaisivat vääriä tavuja, jos formiglyyfi sujahtaisi joukkoon. Kaksi yksinkertaistusta on syytä tuntea: formitekstiä ei rajata formin /BBoxiin, ja rekursio pysähtyy 12 tasoon syklintunnistuksen sijaan, joten väärinmuodostettu formi, joka piirtää itsensä, toistaa tekstinsä kunnes saavuttaa tuon katon
Miksi Q-operaattorin jälkeinen teksti dekoodautui rosaksi?
Teksti Q:n jälkeen saattoi dekoodautua väärin ennen v2.766.73, koska poimija talletti vain CTM:n qssa. Tekstitilan parametrit, nimittäin fontti, koko, Tc, Tw, Tz, TL, renderöintitila ja noste, kuuluvat grafiikkatilaan (§9.3.1), joten Q:n on palautettava ne kaiken muun pinossa olevan mukana (§8.4.2). Eräs toimialaraportti valitsi kaksitavuisen Identity-H-fontin q … Qin sisällä ja näytti sitten yksitavuista WinAnsi-tekstiä ilman omaa Tfä. Poimija piti sisäisen fontin, luki johdantoviivat ja sanan “Adobe” sisällysluettelossa kaksitavuisina koodina ja pudotti 15 % sivun merkeistä. Tulkki q/Q-pinossaan pitää nyt koko tekstitilaa. Tässä kuvatut poimintasäännöt koskevat jokaista sivua, joten koko asiakirja mahtuu tiedostoon yhdellä kutsulla
var
Output: TFileStream;
Pages: Integer;
begin
Output := TFileStream.Create('report.txt', fmCreate);
try
// tyhjä alue = jokainen sivu; sivunvaihtomerkki sivujen väliin; UTF-8 BOM
Pages := Pdf.ExtractLoadedPagesTextToStream(Output, '', #12, True);
Writeln(Pages, ' pages extracted');
finally
Output.Free;
end;
end;
Mitä HotPDF:n teksti-API:a kannattaa käyttää?
ExtractLoadedPageText pysyy sisältöstreamin järjestyksessä, mikä on oikea oletus hakuun ja indeksointiin; sen alla oleva dekoodausketju on käsitelty kirjoituksessa tekstin poiminta ladatuista PDF:istä HotPDF:llä. Tagitetuille asiakirjoille, joiden laatimisjärjestyksellä on väliä, kirjoitus rakennejärjestyksessä tapahtuva tekstin poiminta kävelee rakennepuun geometriasta arvaamisen sijaan, ja taulukoihin lukittuun dataan kirjoitus tyypitetty taulukon poiminta sivunvaihtojen yli palauttaa solut eikä rivejä. Täysi API-referenssi ja kokeiluversio ovat HotPDF Delphi PDF Component -tuotesivulla