Funkcija čitanja naglas ima jedan vidljiv zadatak pored glasa: kako se svaka reč izgovori, ona mora da osvetli tu reč na stranici i drži je u vidu. Da biste to uradili, potreban vam je okvir koji ograničava (bounding box) svaku reč, indeksiran za isti tok karaktera koji govorni motor čita. Dobijte okvire, ali promašite indeksiranje i isticanje će kasniti reč ili dve iza zvuka; dobijte indeksiranje, ali pogrešno rukujte stanjem stranice i isticanje će sleteti na potpuno pogrešnu stranicu. Kriptografski težak deo ovoga, sam sintesajzer, jeste deo koji retko puca. SAPI prijavljuje granice reči do karaktera. Ono što puca je tanak sloj mapiranja između pomeraja karaktera u baferu govora i pravougaonika na iscrtanoj stranici.
PDFium komponenta isporučuje to mapiranje za Delphi, C++Builder i Lazarus, sa okvirima reči dostupnim od verzije v1.53 i kursorom za praćenje od verzije v1.56. Površina je namerno uska: poziv koji vraća okvire reči za stranicu, traker koji pretvara pomeraj karaktera u iscrtano isticanje, i nekoliko svojstava za boju i automatsko skrolovanje. Koliko god bila uska, redosled kojim pozivate stvari određuje da li funkcija radi, a većina neuspeha u nastavku dolazi od pozivanja ispravnih funkcija u pogrešnom redosledu.
Karakteri nisu reči, a TTS motori govore u karakterima
Govorni motor konzumira ravan string i prijavljuje napredak kao pozicije karaktera unutar tog stringa. PDF stranica ima glifove postavljene u prostoru stranice, gde je „reč“ heuristički skup nizova glifova. Ova dva koordinatna sistema ne dele ništa osim ako tekst koji predajete sintesajzeru nije bajt-za-bajt tekst iz kojeg su izračunati okviri reči. To je pravilo broj jedan i ono je neoprostivo. Normalizujte beline, uklonite meke crtice ili na drugi način „očistite“ izvučeni tekst pre nego što ga izgovorite, i svaki pomeraj nizvodno biće tiho pogrešan. Govorite tačno ono što ste izvukli ili vodite eksplicitnu tabelu za redefinisanje pomeraja. Ne postoji treća opcija koja preživljava stvarne dokumente.
Tabela redefinisanja nije hipotetički granični slučaj. Onog trenutka kada vaš korisnički interfejs umetne najavu izgovorene stranice („stranica pet“) ili proširi skraćenicu za sintesajzer, izgovoreni string se razilazi od izvučenog. Zabeležite poziciju i dužinu svakog umetanja, a zatim oduzmite akumulirano prilagođavanje pre svakog poziva za praćenje. To je oko dvadeset linija knjigovodstva, i to je razlika između isticanja koje preživljava sledeći zahtev za funkciju i onog koje puca prvi put kada neko zatraži izgovorene naslove.
Šta vam daje okvir reči
Svaki zapis TPdfWordBox nosi tekst reči, njen StartIndex i broj karaktera Count unutar teksta stranice, Rect u prostoru stranice i 1-bazni broj Page. Polje StartIndex je most između dva koordinatna sistema: to je isti pomeraj koji će SAPI vratiti dok čita. PageWordBoxes vraća ceo niz za aktivnu stranicu:
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;
Komentar o redosledu je load-bearing. PageWordBoxes čitača tokenizuje sloj teksta stranice koju prikaz trenutno prikazuje, pa prvo navigirajte prikazom, a potom izvucite; iscrtavanje nije potrebno, samo otvoren dokument. (Komponenta dokumenta, TPdf, izlaže sopstvene PageWordBoxes povezane sa Pdf.PageNumber za upotrebu bez prozora. Ova dva broja stranica su nezavisna, što je sopstvena zamka.) Prazan rezultat na stranici koja očigledno nosi sadržaj znači skeniranje samo sa slikom. Usmerite ga na OCR, ili barem najavite („stranica 4 ne sadrži čitljiv tekst“), umesto da pustite da glas utihne bez ikakvog objašnjenja.
Povezivanje SAPI granica reči sa trakerom
TrackReadingWordAt na čitaču je šarka cele funkcije. Dajte mu broj stranice i indeks karaktera; on pronalazi okvir reči koji sadrži taj karakter, crta kursor za čitanje na njemu i vraća indeks reči, ili -1 kada indeks padne između reči. SAPI-jevo obaveštenje o granici reči daje tačnu poziciju karaktera koju želi:
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;
Dva defanzivna detalja ovde nalaze svoje mesto. Prvo, TrackReadingWordAt drži sopstveni keš okvira reči za praćenu stranicu, koji se automatski obnavlja kada se stranica promeni, tako da cena po granici ostaje ravna bez obzira na to koliko brzo granice stižu. Drugo, on ne proverava granice previše velikodušno. Indeks na ili preko broja karaktera stranice vraća -1 umesto da se priklješti za poslednju reč. Tretirajte -1 kao „zadrži prethodno isticanje“, nikada kao grešku, jer interpunkcijski nizovi i razmaci između reči legitimno proizvode granice koje ne pripadaju nijednoj reči. Beleženje svakog -1 će vas zatrpati. Umesto toga ih brojte po stranici i pažljivo pogledajte svaku stranicu gde odnos skoči, jer to obično znači neslaganje u normalizaciji teksta sa pravila broj jedan.
Sam kursor: boja, praćenje i čišćenje
SetReadingWord crta isticanje direktno kada sami držite okvir reči, ReadingWordColor ga stilizuje, a ReadingWordFollow := True skroluje prikaz taman toliko da izgovorena reč ostane vidljiva. To poslednje svojstvo nalazi svoje mesto. Ručno napravljeno skrolovanje „centriraj trenutnu reč“ čini da stranica trza pri svakom prelasku u novi red, i čitaoci osetljivi na pokret će isključiti celu funkciju za minut. Isticanje se iscrtava samo na stranici koja je trenutno prikazana u aktivnom TPdfView, tako da višestruko čitanje stranica mora da pomera PageNumber u koraku sa govorom, a zatim da ponovo pokrene pripremni korak za novu stranicu pre nego što stigne njen prvi događaj granice. Preskočite to i prvih nekoliko isticanja na svakoj stranici ukazuju na zastarele koordinate.
procedure TReaderForm.StopReading;
begin
FVoice.Stop; // halt SAPI playback first
PdfView.ClearReadingWord; // then remove the highlight; a stale cursor reads as a bug
end;
Simetrija pri gašenju je ono što održava isticanje ispravnim. Svaka putanja pauze, zaustavljanja i okretanja stranice mora da se završi sa ClearReadingWord. Ostavite to po strani i ćilibar pravougaonik će stajati na zaustavljenoj stranici izgledajući tačno kao kvar, što je vrsta stvari koju će svaki tester prijaviti iako zapravo ništa nije pokvareno.
Brzina govora opterećuje ovaj cevovod više nego veličina dokumenta. Pri 300 reči u minuti, događaji granica stižu svakih 200 ms, a pri najbržim SAPI stopama dolaze brže nego što oko može prijatno da prati. Ispravan odgovor je spajanje, a ne ređanje u red čekanja. Ako nova granica stigne dok je ažuriranje isticanja još uvek na čekanju, odbacite zastarelo i iscrtajte najnovije. Kursor koji poseti svaku reč redom ali kasni pola sekunde deluje pokvareno; onaj koji povremeno preskoči reč dok ostaje u sinhronizaciji sa glasom ne deluje tako.
Granični slučajevi koji razlikuju demo verzije od proizvoda
Nekoliko kategorija dokumenata razotkriva šavove. Kombinovani karakteri su najsuptilniji: Unicode sekvence kao što su osnovno slovo plus kombinovani dijakritik mogu zauzeti više indeksa karaktera nego što vizuelna reč sugeriše, tako da bilo koja aritmetika pomeraja koja pretpostavlja jedan indeks po glifu polako odlazi u stranu. To je najjači argument za to da se dopusti da TrackReadingWordAt poseduje mapiranje umesto računanja brojeva reči ručno. Crtica na kraju reda je običnija, ali češća: reč prelomljena preko prelaza u novi red postaje dva okvira, a ako je izgovorite kao jedan token, događaj granice za njenu drugu polovinu se razrešava na prvi okvir. To je obično u redu, ali to je odluka, pa je donesite namerno umesto da je otkrijete. Označavanje (tagging) menja sam redosled čitanja. Kada dokument nosi ispravne oznake strukture (područje ISO 14289, PDF/UA), redosled reči prati logičku strukturu; bez njih se vraća na heuristiku rasporeda, i neoznačena stranica sa dve kolone može se čitati pravo preko obe kolone. Rotirane stranice su poslednja česta zamka: Rect svake reči i dalje je ispravno ograničava u prostoru stranice, ali politika praćenja prikaza podešena za horizontalni tok skroluje neprijatno kada tekst ide vertikalno, pa zadržite bar jedan rotirani dokument u regresionom skupu. Za rukovanje redosledom čitanja, rečenice preko ReadingUnits i širi asistivni stek, pogledajte izgradnju pristupačnog PDF čitača u Delphi-ju.
Jedno ograničenje platforme oblikuje implementaciju. SAPI je samo za Windows. API za okvire reči i praćenje je bajt-za-bajt identičan pod Lazarus-om i FPC-om, ali Linux i macOS build-ovi trebaju drugačiji sintesajzer povezan iza istih događaja granica; to podešavanje je pokriveno u pokretanju čitača pod Lazarus-om i FPC-om. Trošak isticanja takođe utiče na vaš keš stranica kada brzine govora porastu, a aritmetika budžeta u keširanju iscrtavanja i performansama zumiranja se prenosi i ovde bez promena.
Kada je isticanje jedne reči pogrešna granularnost
Karaoke na nivou reči nisu uvek ono što čitalac želi. Pri visokim brzinama govora, treperenje kursora reč po reč postaje sopstveni vizuelni šum, i neki slušaoci lakše prate rečenicu nego pulsiranje pojedinačnih reči. Za taj slučaj komponenta izlaže grublju jedinicu. ReadingUnits vraća jedinice na nivou rečenice i bloka, svaka sa svojim pravougaonicima isticanja, i njih iscrtavate pomoću SetReadingHighlight umesto SetReadingWord. Povezivanje je istog oblika: pomeraj granice i dalje odredjuje koja jedinica svetli, ali jedinica koju ističete obuhvata frazu ili red umesto jednog tokena. Sporiji čitaoci i brza reprodukcija imaju tendenciju da to preferiraju, i ništa vas ne sprečava da ponudite oba režima iza podešavanja.
Donje granice verzija vredi utvrditi pre nego što počnete da gradite na ovome: okviri reči zahtevaju PDFium Component v1.53 ili noviji, a kursor za praćenje zahteva v1.56. Kompletan API za čitanje, jedinice na nivou rečenice i funkcionalna demo verzija čitanja naglas nalaze se na stranici proizvoda za PDFium Component.