Neregys naudotojas atidaro ketvirčio ataskaitą jūsų naujoje „Delphi“ peržiūros programoje, įjungia NVDA ekrano skaitytuvą ir išgirsta puslapio poraštę, tada skaičių stulpelį ir tik pačioje pabaigoje – pavadinimą, kurį bet kuris regintis skaitytojas būtų perskaitęs pirmiausia. Arba apskritai nieko negirdi. Puslapis ekrane atrodo nepriekaištingai, ir tai yra spąstai: atvaizdavimas ir skaitymas yra dvi skirtingos problemos, sprendžiamos skirtingu kodu. Tvarka, kuria PDF dokumentas nubrėžia simbolius, neprivalo sutapti su tvarka, kuria žmogus turėtų juos girdėti, todėl tik atvaizdavimo funkcijomis paremta skaityklė sukuria puikų vaizdą, bet visiškai netinkamą įgarsinimą. Dėl šios priežasties „PDFium Component“ biblioteka (skirta Delphi, C++Builder ir Lazarus) turi atskirą skaitymo API rinkinį. Piešimo funkcijos negali atkurti skaitymo tvarkos, kurios dokumente niekada nebuvo
Prieinama skaityklė priklauso nuo trijų dalykų. Ji turi išgauti tvarką, kurią ekrano skaitytuvas galėtų įgarsinti, išlaikyti matomą žymeklį ties skaitomu žodžiu ir atpažinti, kai dokumentas neturi struktūros žymių, užuot bandžius spėlioti. Kiekvienam iš šių uždavinių spręsti yra skirtas aiškus API metodas, kurį praleidus nukentės galutinė kokybė
Skaitymo tvarka saugoma struktūros medyje, o ne piešimo eilėje
ISO 32000-1 §14.8 standartas apibrėžia loginę struktūrą kaip elementų medį, esantį virš puslapio turinio. PDF/UA (ISO 14289-1) žengia dar toliau ir padaro šį medį privalomą: kiekviena tikro turinio dalis turi būti pasiekiama skaitymo tvarka, o puslapio apipavidalinimo elementai (artefaktai) turi būti praleidžiami. Teisingai pažymėta ataskaita žino, kad „Ketvirčio rezultatai“ yra antrojo lygio antraštė, o bendra lentelė yra lentelės objektas su antraštės langeliais. Nepažymėta ataskaita tėra išdėstytų simbolių rinkinys, kuris vizualiai tik primena dokumentą
Metodas ReadablePageContent nuskaito šią struktūrą (kai ji yra) ir grąžina fragmentus su semantiniais tipais Kind (pavyzdžiui, cfHeading ir cfParagraph), todėl vartotojo sąsaja gali pranešti „antraštė“ prieš perskaitant žodžius, užuot skaičiusi paryškintą eilutę kaip paprastą tekstą. Nesant struktūros medžio, ta pati funkcija naudoja heuristinę maketo analizę: nustato stulpelius, sugrupuoja eilutes, rūšiuoja iš kairės į dešinę ir iš viršaus į apačią. Šis atsarginis būdas tinka vieno stulpelio dokumentams, tačiau yra nepatikimas laikraščiams, kelių stulpelių formoms ar puslapiams su šoninėmis išnašomis. Svarbiausia žinoti, kokį rezultatą gavote. Įrašas TPdfReadableContent turi savybę Source, kurios reikšmė lygi rosStructure, kai tvarka gauta iš pažymėto medžio, arba rosHeuristic, kai ji buvo nustatyta pagal geometriją. Pateikdami atspėtą tvarką kaip patvirtintą, nukentės prieinamumo kokybė
Geriausia dokumento atidarymo metu nuskaityti IsTagged ir vieną kartą iškviesti ValidatePdfUa, išsaugant atsakymą. Neteisingas PDF/UA patikrinimas nėra priežastis atmesti failą – tai priežastis būsenos juostoje parodyti pranešimą „numatoma skaitymo tvarka“. Taip nutikus naudotojo skundui dėl sumaišyto teksto įgarsinimo, iškart žinosite, ar problema susijusi su failo žymėjimu, ar su programos kodu
Nuo puslapio iki kalbos eilės su ReadingUnits
Teksto įgarsinimo (text-to-speech) funkcijoms patogiausia naudoti ReadingUnits. Ji grąžina aktyvaus puslapio TPdfReadingUnit įrašų masyvą, kur kiekvienas įrašas turi skaitomą tekstą, jo semantinį vaidmenį ir koordinates puslapyje. Taip pat yra dokumento lygio funkcija DocumentReadingUnits, skirta nepertraukiamam skaitymui per kelis puslapius. Vienas elementas įdedamas tiesiai į kalbos eilę:
procedure TReaderForm.QueuePageSpeech(PageNumber: Integer);
var
Units: TPdfReadingUnits;
i: Integer;
begin
Pdf.PageNumber := PageNumber; // ReadingUnits works on the active page
Units := Pdf.ReadingUnits;
FSpeechQueue.Clear;
for i := Low(Units) to High(Units) do
FSpeechQueue.Add(Units[i]); // text + semantics + highlight rects
FCurrentPage := PageNumber;
SpeakNextUnit;
end;
Šiame cikle lengva padaryti dvi klaidas. Kalbos eilę formuokite atskirai kiekvienam puslapiui ir atnaujinkite ją naudotojui keičiant puslapius, nes skaitymo elementai naudoja puslapio koordinates – palikus trečiojo puslapio eilę, žymėjimai bus atvaizduojami ketvirtajame puslapyje. Be to, tuščias Units masyvas puslapyje, kuriame akivaizdžiai yra turinio, rodo, kad tai yra tik paveikslėlis. Nuskenuotas puslapis susideda tik iš pikselių ir neturi tekstinio sluoksnio, todėl teisinga būtų įgarsinti įspėjimą („šiame puslapyje nėra išgaunamo teksto“), užuot tiesiog nutylėjus taip, kad klausytojas negalėtų to atskirti nuo programos užstrigimo
Žodžio žymeklis, sekantis balsą
Visos pastraipos paryškinimas vienu metu yra nepatogus silpnaregiams naudotojams, kurie akimis seka garsiai skaitomus žodžius. Žodžių lygio paryškinimui (karaoke efektui) reikia dviejų dalių: kiekvieno žodžio geometrijos ir būdo, kaip susieti TTS (balsų) variklio pranešimus su ta geometrija. Metodas PageWordBoxes grąžina geometriją kaip TPdfWordBox įrašus (kiekvienas turi žodžio tekstą, simbolių postūmį, simbolių skaičių ir stačiakampį). Metodas TrackReadingWordAt atlieka susiejimą. Perduokite jam simbolio poziciją, kurią praneša SAPI (arba NVDA) sistemos įvykis, ir jis vienu iškvietimu suras reikiamą žodį bei nupieš ties juo žymeklį
procedure TReaderForm.PrepareKaraoke(PageNumber: Integer);
begin
// The view's word boxes come from the page the view displays.
// Setting Pdf.PageNumber alone would not move the view
PdfView.PageNumber := PageNumber;
FWordBoxes := PdfView.PageWordBoxes;
end;
procedure TReaderForm.OnTtsWordBoundary(Sender: TObject; CharIndex: Integer);
var
WordIdx: Integer;
begin
// TrackReadingWordAt maps the offset AND paints the word cursor
WordIdx := PdfView.TrackReadingWordAt(FCurrentPage, CharIndex);
if WordIdx < 0 then
PdfView.ClearReadingWord; // boundary ran past the page text
end;
Ši sistema turi vieną privalumą ir vieną griežtą reikalavimą. Privalumas: TrackReadingWordAt pati saugo sekamo puslapio žodžių talpyklą (cache), todėl nieko nereikia įkelti iš anksto, o atvaizdavimas nevyksta, nes žodžiai imami iš tekstinio sluoksnio. Net ir nematoma kalbos paslauga gali sekti pozicijas. Griežtas reikalavimas: simbolių indeksas turi rodyti į komponento išgautą tekstą, o ne į jūsų pačių suformatuotą eilutę. Kai CharIndex viršija puslapio teksto pabaigą, funkcija grąžina -1 (tai dažnai nutinka TTS varikliui pranešus apie paskutinius skyrybos ženklus). Reikšmę -1 supraskite kaip „pašalinti žymeklį“, o ne kaip klaidą
Ekrane žymeklio spalvą nustato savybė ReadingWordColor. Numatytoji gintaro (amber) spalva tinka daugeliui puslapio fonų, tačiau patikrinkite ją su visais spalvų filtrais. Gintarinis žymeklis gali visiškai išnykti apvertus spalvas (inversion) (būtent taip kartu su įgarsinimu dažnai dirba silpnaregiai naudotojai). Nustatykite ReadingWordFollow := True ir peržiūros langas automatiškai slinks paskui skaitomą žodį, kas yra būtina priartintame puslapyje. Atminkite taisyklę: SetReadingWord piešia tik aktyviame TPdfView puslapyje. Iš anksto nuspręskite, ar rankinis slinkimas sustabdo įgarsinimą, ar sekimo režimas jį perrašo, kitaip balsas skaitys toliau, o žymeklis atsidurs už ekrano ribų
Dokumentai, kurie gali sutrikdyti skaityklės darbą
Kai kurios dokumentų formos gali lengvai sutrikdyti standartinį kodą, todėl jas verta įtraukti į nuolatinių testų sąrašą:
- Nepažymėti dokumentai su daug teksto. Heuristinė tvarka tinka tiesiškoms ataskaitoms, tačiau klysta puslapiuose su šoninėmis lentelėmis ar išnašomis. Pažymėkite tokią tvarką kaip „numatomą“ vartotojo sąsajoje ir žurnale
- Tik vaizdo skenuoti dokumentai. Tekstinio sluoksnio nėra. Aptikite juos per tuščius skaitymo elementus ir pasiūlykite naudotojui atlikti OCR atpažinimą, užuot skaitę tuščią puslapį
- Kombinuoti simboliai ir mišrūs raštai. „Unicode“ kombinuojami ženklai ne visada sutampa su vizualiais žodžiais santykiu 1:1, todėl žodžių stačiakampių skaičius gali skirtis nuo jūsų pačių kodo skaičiavimų. Nenaudokite savo apskaičiuotų postūmių kreipimuisi į masyvą – pasitikėkite tik tais indeksais, kuriuos grąžina
TrackReadingWordAt
Testuokite kaip auditorius, o ne demonstraciniam pristatymui
Vien tai, kad programa perskaitė pavyzdinį tekstą garsiai, dar nieko neįrodo. Patikimam testavimui paleiskite tris failus su aktyviu NVDA ekrano skaitytuvu: vieną žinomai pažymėtą failą (kur antraštės pranešamos kaip antraštės, o lentelės skaitomos eilės tvarka), vieną žinomai nepažymėtą failą (kur matomas numatomos tvarkos indikatorius) ir nuskenuotą failą (kur perskaitomas įspėjimas apie teksto trūkumą). Kiekvienas iš šių atvejų patikrina kodą, kurį paprasti testai praleidžia
Taip pat patikrinkite, ar žodžio žymeklis išlieka tikslus esant dvigubam ir perpus mažesniam kalbėjimo greičiui, ir ar ReadingWordFollow automatinis slinkimas nekonfliktuoja su rankiniu naudotojo slinkimu. Tada paleiskite skaitymą balsu ir keiskite spalvų filtrus, stebėdami, ar žymeklis neišnyksta. Silpnaregiams skirtų spalvų filtrų straipsnis išsamiai aprašo šį kelią, o skaitymo žymeklio gilioji analizė nagrinėja TTS sinchronizavimą
Šiame straipsnyje naudoti skaitymo elementų ir žodžių stačiakampių API yra platinami kartu su PDFium Component, skirta Delphi ir C++Builder (VCL) bei Lazarus/FPC (LCL). Produkto puslapyje rasite pilną API nuorodą, įskaitant čia naudotų įrašų struktūras