Tekninen artikkeli

Sanakohtainen (Word-by-Word) TTS-korostus (Highlighting) Delphi PDFium -katseluohjelmissa

Ääneenlukuominaisuudella (read-aloud feature) on (has one) äänen ohella (beyond the voice) yksi visuaalinen (visible) tehtävä (job): sitä (as) mukaa kun (as each) kukin (each) sana (word is) puhutaan (spoken), sen on sytytettävä (light up) tuo sana sivulla (on the page) ja pidettävä (keep) se näkyvissä (in view). Tehdäksesi tämän (To do that) tarvitset jokaisen sanan (every word) rajoituslaatikon (bounding box), joka (indexed to the) on indeksoitu samaan (same) merkkivirtaan (character stream), josta (the) puhemoottori (speech engine is) lukee (reading from). Hanki (Get the) laatikot (boxes) mutta (but) epäonnistu (miss the) indeksoinnissa (indexing), ja (and the) korostus (highlight) ajelehtii (drifts a) sanan tai (or) pari audion jäljessä (behind the audio); hoida indeksointi (get the indexing) mutta käsittele sivun tilaa väärin (mishandle the page state), ja (and the) korostus laskeutuu (lands on the) kokonaan (entirely) väärälle sivulle (wrong page). Tämän puhepuoli (speech part of this), itse (itself) syntetisaattori (synthesizer), on (is the) se (part) osa, joka (that) menee harvoin (rarely) rikki (breaks). SAPI raportoi sanarajat (word boundaries) merkin (to the character) tarkkuudella. Se mikä (What) menee rikki (breaks), on (is the) ohut (thin) kartoituskerros (mapping layer) puhepuskurissa (speech buffer) olevan (between a) merkkisiirtymän (character offset) ja (and a) renderöidyllä sivulla (rendered page) olevan suorakulmion (rectangle) välillä

PDFium Component toimittaa (ships) tuon (that) kartoituksen (mapping for) Delphille, C++Builderille ja Lazarukselle, sanalaatikoiden (word boxes) ollessa saatavilla versiosta v1.53 alkaen (since) ja seurantakohdistimen (tracking cursor) versiosta v1.56 alkaen. Pinta (surface) on tarkoituksella (deliberately) kapea: kutsu, joka palauttaa sivun (page) sanalaatikot, seurantakomponentti (tracker), joka (that) muuttaa (turns a) merkkisiirtymän (character offset into a) maalatuksi (painted) korostukseksi (highlight), ja (and a) pari ominaisuutta (properties for) värille ja automaattiselle (auto-scroll) vieritykselle. Niin (As) kapea (narrow) kuin (as) se onkin (it is), järjestys (order), jossa (you) kutsut (call) asioita (things in), ratkaisee (decides whether the), toimiiko (works) ominaisuus (feature), ja suurin (most of the) osa (failures) alla (below) olevista epäonnistumisista johtuu oikeiden (right) funktioiden (functions) kutsumisesta (calling the) väärässä (wrong) järjestyksessä (sequence)

Merkit (Characters) eivät (are not) ole sanoja (words), ja (and) TTS-moottorit (engines) puhuvat (speak in) merkeissä (characters)

Puhemoottori (speech engine) kuluttaa (consumes a) tasaisen (flat) merkkijonon (string) ja raportoi edistymisen (progress) merkkisijainteina (character positions within) tuon (that) merkkijonon sisällä. PDF-sivulla on glyyfejä (glyphs) sijoitettuna (placed in) sivuavaruuteen (page space), jossa (where a) "sana" ("word") on (is a) heuristinen (heuristic) klusteri (cluster of) glyyfiajoja (glyph runs). Nämä (The) kaksi koordinaatistoa (coordinate systems) eivät jaa (share) mitään (nothing) ellei (unless the) teksti (text), jonka ojennat (you hand the) syntetisaattorille (synthesizer), ole tavu tavulta (byte-for-byte) se (the) teksti (text the), josta (from) sanalaatikot laskettiin (word boxes were computed). Tuo (That) on (is) sääntö yksi (rule one), ja (and it is) se on (is) armoton (unforgiving). Normalisoi välilyönnit (Normalize whitespace), poista pehmeät (strip soft) tavuviivat (hyphens) tai muutoin "siisti" ("clean up" the) poimittua (extracted) tekstiä (text) ennen (before) sen (it) puhumista (speaking), ja jokainen (every) sitä seuraava siirtymä (downstream offset is) on (is silently) hiljaa (silently) väärin. Puhu (Speak) täsmälleen (exactly) se mitä (what you) poimit (extracted), tai (or) ylläpidä (keep an) eksplisiittistä (explicit) siirtymien uudelleenkartoitustaulukkoa (offset remapping table). Ei (There is no) ole kolmatta (third) vaihtoehtoa (option that), joka selviytyisi (survives) todellisista asiakirjoista (real documents)

Uudelleenkartoitustaulukko (remapping table) ei (is not a) ole hypoteettinen (hypothetical) rajatapaus (edge case). Heti kun (The moment your) käyttöliittymäsi (UI) lisää (inserts a) puhutun sivukuulutuksen (spoken page announcement) ("sivu (page) viisi (five)") tai laajentaa lyhenteen (abbreviation for the) syntetisaattoria varten, puhuttu merkkijono (spoken string) eroaa (diverges from the) poimitusta (extracted one). Tallenna (Record) jokaisen (each) lisäyksen sijainti (insertion's position) ja (and) pituus (length), ja (then) vähennä (subtract the) sitten (then) kertynyt säätö (accumulated adjustment before) ennen (before) jokaista (every) seurantakutsua (tracking call). Se on ehkä parikymmentä (twenty) riviä (lines of) kirjanpitoa (bookkeeping), ja (and it is the) se on ero (difference between a) korostuksen, joka selviää seuraavasta ominaisuuspyynnöstä (feature request), ja sellaisen (and one that) välillä (between a), joka (that) menee rikki ensimmäisen (first) kerran (time someone), kun (someone) joku pyytää puhuttuja (spoken) otsikoita (headings)

Mitä (What a) sanalaatikko (word box) antaa (gives you) sinulle

Jokainen TPdfWordBox-tietue (record) kantaa (carries the) sanan tekstiä, sen (its) kenttää StartIndex ja merkkien lukumäärää (character Count) sivun tekstin sisällä, sivuavaruuden (page-space) kenttää Rect ja 1-pohjaista (1-based) sivunumeroa Page. Kenttä StartIndex on (is the) silta (bridge between the) kahden koordinaatiston (coordinate systems) välillä: se on (it is the) sama (same) siirtymä (offset), jonka SAPI antaa (hand) takaisin lukiessaan (as it reads). PageWordBoxes palauttaa aktiivisen sivun täyden taulukon (full array):

procedure TReaderForm.PreparePage(PageNo: Integer);
begin
  PdfView.PageNumber := PageNo;   // the view's word boxes track its displayed page

  FWords := PdfView.PageWordBoxes;
  FPageText := BuildSpeechText(FWords);   // concatenate Word.Text in order

  if Length(FWords) = 0 then
    HandleImageOnlyPage(PageNo);          // a scan with no text layer
end;

Järjestystä koskeva kommentti on (is) kantava (load-bearing). Katseluohjelman (viewer's) PageWordBoxes tokenisoi sen (the) sivun tekstikerroksen (text layer of the page), jonka näkymä (view) kulloinkin näyttää (currently displays), joten (so) navigoi (navigate the) näkymä (view) ensin ja poimi (extract) toiseksi; renderöintiä ei tarvita (no rendering is required), vain (only an) avoin asiakirja. (Asiakirjakomponentti (document component), TPdf, paljastaa (exposes its own) oman (own) PageWordBoxes-ominaisuutensa (PageWordBoxes), joka on sidottu arvoon Pdf.PageNumber päätöntä (headless) käyttöä varten. Nämä kaksi sivunumeroa (page numbers) ovat (are) toisistaan riippumattomia (independent), mikä (which is its) on oma ansa (own trap).) Tyhjä (empty) tulos sivulla, joka kantaa (carries) näkyvästi (visibly) sisältöä, tarkoittaa pelkkää kuvaskannausta (image-only scan). Reititä (Route it to) se (it to) OCR:ään, tai (or) ainakin kuuluta se (announce it) ("sivu 4 ei sisällä luettavaa tekstiä" ("page 4 contains no readable text")), sen sijaan (rather than) että (letting the) antaisit (letting the) äänen (voice) vaieta (fall silent with no) ilman selitystä (explanation)

SAPI-sanarajojen (word boundaries) johdottaminen (Wiring) seurantaohjelmaan (tracker)

TrackReadingWordAt, katseluohjelmassa, on (is the) koko (whole) ominaisuuden (feature) sarana (hinge of the). Anna (Give it a) sille sivunumero ja merkin indeksi (character index); se (it) löytää (finds the) sanalaatikon, joka sisältää tuon merkin, maalaa (paints the) lukukohdistimen (reading cursor) sen päälle ja palauttaa (returns the) sanan indeksin (word index), tai -1, kun (when the) indeksi osuu (falls between) sanojen väliin. SAPI:n (SAPI's) ilmoitus sanarajasta (word-boundary notification) tarjoaa täsmälleen (exactly the) sen merkin (character) sijainnin, jonka se (it) haluaa (wants):

procedure TReaderForm.OnSpeechWordBoundary(StreamPos: Integer);
var
  WordIdx: Integer;
begin
  // Maps the offset to a word box and moves the highlight in one call
  WordIdx := PdfView.TrackReadingWordAt(FPageNo, StreamPos);
  if WordIdx < 0 then
    Exit;                     // boundary fell outside any word: keep last highlight
end;

Kaksi (Two) puolustavaa yksityiskohtaa (defensive details) ansaitsee (earn) paikkansa (their keep) tässä. Ensinnäkin (First), TrackReadingWordAt pitää yllä (keeps its own) omaa (own) sanalaatikoiden (word-box) välimuistiaan (cache) seurattavalle (for the tracked) sivulle (page), joka rakennetaan (rebuilt) automaattisesti (automatically) sivun muuttuessa, joten rajoja (per-boundary) koskeva (cost) hinta pysyy (stays) tasaisena riippumatta (no matter) siitä, kuinka nopeasti rajat (boundaries) saapuvat. Toiseksi, se ei (it does not) tarkista (bounds-check) rajoja (bounds) avokätisesti (generously). Indeksi (index), joka on sivun merkkimäärän (character count) kohdalla tai sen yli (at or beyond the), palauttaa arvon (returns) -1 sen sijaan, että se rajaisi sen (clamping to the) viimeiseen (final) sanaan. Käsittele (Treat) -1 arvona "pidä edellinen korostus" ("hold the previous highlight"), älä koskaan (never as an) virheenä (error), koska välimerkkiajot (punctuation runs) ja sanojen välinen (inter-word) tyhjä tila (whitespace) tuottavat (legitimately produce) legitiimisti (legitimately) rajoja, jotka (boundaries that) eivät kuulu millekään sanalle (belong to no word). Jokaisen arvon (every) -1 loggaaminen (Logging) hautaa sinut (will bury you). Laske ne (Count them) sen (per page) sijaan (instead) sivua kohden (per page), ja (and) katso tarkasti (look hard at) mitä tahansa sivua, jolla suhde (ratio spikes) piikkaa (spikes), koska (since that) se tarkoittaa (usually means a) yleensä (usually) tekstin normalisoinnin epäsuhtaa (text-normalization mismatch back at) takaisin (back at) säännössä (rule) yksi (one)

Itse kohdistin (cursor itself): väri (color), seuranta (follow) ja (and) siivous (cleanup)

SetReadingWord maalaa korostuksen suoraan silloin (when), kun (you) pidät sanalaatikkoa (word box) itse (yourself) hallussasi, ReadingWordColor tyylittelee (styles) sen, ja ReadingWordFollow := True vierittää (scrolls the) näkymää (view) juuri (just) sen verran (enough to), että (keep the) puhuttu (spoken) sana (word) pysyy näkyvissä (visible). Tuo (That) viimeinen ominaisuus ansaitsee paikkansa. Käsin koodattu (hand-rolled) "keskitä nykyinen sana" ("center the current word") -vieritys (scroll) saa (makes the) sivun huojumaan (lurch on) jokaisella rivinvaihdolla (line break), ja liikkeelle herkät lukijat (motion-sensitive readers) kytkevät (will turn the) koko ominaisuuden pois päältä (off inside a) minuutissa (minute). Korostus (highlight) renderöityy vain (only on the) sillä (page) sivulla (currently), joka näytetään parhaillaan (shown in the) aktiivisessa (active) TPdfView-näkymässä, joten (so) monisivuisen (multi-page) lukemisen on (reading has to) edistettävä (advance) arvoa PageNumber tahdissa (in step with) puheen (speech) kanssa, ja suoritettava (then re-run the) sitten uudelleen (re-run the) uuden (new) sivun (page) valmisteluvaihe (prepare step) ennen kuin (before) sen (its) ensimmäinen (first) rajatapahtuma (boundary event) laskeutuu (lands). Ohita (Skip) se (that) ja (and the) jokaisen (every) sivun (page) ensimmäiset (first few) korostukset (highlights) osoittavat (point at) vanhentuneisiin (stale) koordinaatteihin

procedure TReaderForm.StopReading;
begin
  FVoice.Stop;                // halt SAPI playback first
  PdfView.ClearReadingWord;   // then remove the highlight; a stale cursor reads as a bug
end;

Symmetria sammutettaessa (shutdown) pitää korostuksen rehellisenä (honest). Jokaisen taukopolun (pause), pysäytyspolun (stop) ja sivunkääntöpolun (page-turn path) on (has to) päätyttävä (end in) komentoon ClearReadingWord. Jätä se (Leave it) pois (out), ja pysäytetyllä (stopped) sivulla istuva keltainen (amber) suorakulmio (rectangle) näyttää (looking) täsmälleen (exactly like a) vialta (defect), mikä on juuri sellaista, mistä (which is the kind of thing) jokainen testaaja (every tester) tekee ilmoituksen (will file), vaikka mikään ei oikeasti olekaan rikki (actually broken)

Puhenopeus (Speech rate) rasittaa (stresses this) tätä putkea (pipeline) kovemmin (harder than) kuin (than) asiakirjan (document) koko. 300 sanan (words per) minuuttinopeudella (minute) rajatapahtumat (boundary events) saapuvat 200 ms:n välein (every), ja (and at the) nopeimmilla (fastest) SAPI-nopeuksilla ne tulevat nopeammin (faster than) kuin silmä (eye) pystyy mukavasti seuraamaan (comfortably tracks). Oikea vastaus on sulauttaa (coalesce), ei (not) jonottaa (queue). Jos (If a) uusi raja (new boundary) saapuu, kun korostuksen päivitys on vielä vireillä (pending), pudota (drop the) vanhentunut (stale one) ja (and) maalaa uusin. Kohdistin (cursor), joka vierailee jokaisessa sanassa järjestyksessä (in order) mutta jää jälkeen (lags) puoli sekuntia (half a second), tuntuu (feels) rikkinäiseltä (broken); sellainen, joka hyppää silloin tällöin sanan yli, mutta pysyy samassa tahdissa äänen kanssa, ei tunnu

Rajatapaukset (Edge cases), jotka erottavat (separate) demot (demos from) tuotteista (products)

Muutama asiakirjakategoria (categories of document) paljastaa (expose the) saumat (seams). Yhdistävät merkit (Combining characters) ovat hienovaraisimpia (subtlest): Unicode-sekvenssit, kuten (such as a) peruskirjain (base letter) plus (plus a) yhdistävä (combining) tarke (diacritic), voivat viedä useampia merkki-indeksejä (character indices) kuin visuaalinen (visual) sana (word) antaa (suggests) ymmärtää, joten (so any) mikä tahansa siirtymäaritmetiikka (offset arithmetic), joka olettaa yhden (one) indeksin (index per) glyyfiä (glyph) kohden, ajelehtii (drifts) hitaasti (slowly). Tuo (That) on vahvin argumentti (strongest argument for) sille, että annetaan (letting) TrackReadingWordAt-komennon (TrackReadingWordAt own the) omistaa (own the) kartoitus (mapping) sen sijaan, että lasketaan sananumerot käsin (by hand). Tavuviivoitus (Hyphenation) on arkipäiväisempää (more mundane) mutta yleisempää (more common): rivinvaihdon (line break) yli (across a) katkennut sana (word broken) muuttuu kahdeksi laatikoksi, ja jos puhut sen yhtenä (single) tokenina (token), sen toisen (second) puolikkaan rajatapahtuma (boundary event) ratkeaa (resolves to the) ensimmäiseen laatikkoon. Tuo (That) on yleensä (usually) hienosti (fine), mutta se on päätös (decision), joten tee (so make) se tarkoituksella (on purpose) sen (instead of) löytämisen (discovering it) sijaan. Tägitus (Tagging changes the) muuttaa (changes the) itse lukujärjestystä (reading order itself). Kun asiakirja kantaa oikeita (proper) rakennetägejä (structure tags) (ISO 14289, PDF/UA -reviiriä (territory of)), sanojen (word) järjestys (sequencing) noudattaa (follows the) loogista rakennetta (logical structure); ilman niitä (without them it) se palautuu asetteluheuristiikkaan (layout heuristics), ja kaksisarakkeinen (two-column) tägittämätön (untagged) sivu voi lukea suoraan molempien (both) sarakkeiden (columns) yli (across). Kierretyt (Rotated) sivut (pages) ovat (are the) viimeinen yleinen tapaus: jokaisen sanan Rect rajoittaa sen (it) silti oikein sivuavaruudessa (page space), mutta vaakasuoraan (horizontal) virtaukseen (flow) viritetty (tuned for) näkymäalueen (viewport-follow) seurantakäytäntö (policy) vierittää ravisuttavasti (jarringly), kun teksti (text) kulkee pystysuunnassa (vertically), joten pidä (so keep) ainakin yksi (at least one) kierrettu (rotated) asiakirja regressiojoukossa (regression set). Lukujärjestyksen käsittelystä, lausetason (sentence-level) yksiköistä ominaisuuden ReadingUnits kautta ja laajemmasta (wider) apuvälinepinosta (assistive stack) katso saavutettavan PDF-lukijan rakentaminen Delphissä (building an accessible PDF reader in Delphi)

Yksi (One) alustarajoite (platform constraint) muokkaa käyttöönottoa (deployment). SAPI on (is) vain Windowsille (Windows-only). Sanalaatikko- (word-box) ja (and) seuranta-API (tracking API) on tavu tavulta identtinen Lazaruksen ja FPC:n alla, mutta Linux- ja macOS-koontiversiot (builds) tarvitsevat (need a) erilaisen (different) syntetisaattorin (synthesizer) kytkettynä (wired behind the) samojen rajatapahtumien taakse; tuo (that) asetelma on (setup is) katettu artikkelissa katseluohjelman (viewer) ajaminen (running the) Lazaruksen (Lazarus) ja FPC:n alla. Korostuksen hinta (Highlight cost) vuorovaikuttaa (interacts) myös (also) sivun välimuistisi (page cache) kanssa heti kun (once) puhenopeudet (speech rates) nousevat (climb), ja (and the) artikkeli pdfium-component-render-cache-zoom-performance.html (renderöinnin välimuistitila ja zoomaussuorituskyky, render caching and zoom performance) budjettiaritmetiikka (budget arithmetic) siirtyy (carries over) tänne ilman muutoksia (change)

Kun yhden (single-word) sanan korostus (highlighting) on väärä tarkkuus (granularity)

Sanatason karaoke (Word-level karaoke) ei ole aina (is not always) sitä mitä (what a) lukija (reader) haluaa (wants). Suurilla (high) puhenopeuksilla (speech rates) sanalta sanalle (word by word) välkkyvästä (flickering) kohdistimesta (cursor) tulee (becomes its) omaa visuaalista (visual) meluaan (noise), ja jotkut (some) kuuntelijat (listeners) seuraavat (follow a) lausetta (sentence) mukavammin kuin yksittäisten (single) sanojen strobovaloa (strobe of). Tuota tapausta varten komponentti (component) paljastaa (exposes a) karkeamman (coarser) yksikön (unit). ReadingUnits palauttaa (returns) lause- (sentence-) ja (and) lohkotason (block-level) yksiköitä, joilla (each) on (with its) omat korostussuorakulmionsa (highlight rectangles), ja sinä maalaat (paint them) ne komennolla SetReadingHighlight komennon SetReadingWord sijaan. Johdotus (wiring) on (is the) samanmuotoinen (same shape): rajasiirtymä (boundary offset) ohjaa yhä (still drives) sitä (which), mikä yksikkö (unit) syttyy (lights up), mutta yksikkö, jonka (you) korostat (highlight), ulottuu (spans a) lausekkeen (clause or a) tai rivin (line) yli ennemmin (rather than a) kuin yksittäisen (single) tokenin yli. Hitaammat (Slower) lukijat ja nopean (high-rate) toiston (playback) käyttäjät (both tend) suosivat kumpikin (tend to prefer) sitä yleensä, eikä mikään (and nothing) estä (stops you) sinua (from) tarjoamasta molempia tiloja (modes) asetuksen (setting) takana (behind a)

Versiolattiat (version floors) on (are) arvokasta (worth) kiinnittää alas (pinning down) ennen kuin rakennat (build against) tätä vastaan (this): sanalaatikot tarvitsevat PDFium Component v1.53:n tai (or) uudemman (later), ja (and the) seurantakohdistin (tracking cursor) tarvitsee v1.56:n. Koko (full) lukemisen (reading) API, lausetason (sentence-level) yksiköt (units) ja (and a) toimiva (working) ääneenluku-demo (read-aloud demo) ovat (are on the) tuotesivulla (product page for) PDFium Component