diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 5dc48a8a3..5b43b0fcd 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -24,6 +24,7 @@ import {A11yDocument, HoverRegion, SpeechRegion, LiveRegion} from './Region.js'; +import {STATE} from '../../core/MathItem.js'; import type { ExplorerMathItem } from '../explorer.js'; import {Explorer, AbstractExplorer} from './Explorer.js'; import {ExplorerPool} from './ExplorerPool.js'; @@ -446,6 +447,10 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo * @override */ public Start() { + // In case the speech is not attached yet, we generate it + if (this.item.state() < STATE.ATTACHSPEECH) { + this.item.attachSpeech(this.document); + }; if (!this.attached) return; if (this.node.hasAttribute('tabindex')) { this.node.removeAttribute('tabindex'); @@ -458,7 +463,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo this.current = this.node.querySelector(`[data-semantic-id="${this.restarted}"]`) if (!this.current) { const dummies = Array.from( - this.node.querySelectorAll(`[data-semantic-type="dummy"]`)) + this.node.querySelectorAll('[data-semantic-type="dummy"]')) .map(x => x.getAttribute('data-semantic-id')) let internal = this.generators.element.querySelector( `[data-semantic-id="${this.restarted}"]`); diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index bdf87129b..c37b32fa8 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -31,7 +31,6 @@ import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js'; import {OptionList, expandable} from '../util/Options.js'; import {Sre} from './sre.js'; import { buildSpeech } from './speech/SpeechUtil.js'; - import { GeneratorPool } from './speech/GeneratorPool.js'; /*==========================================================================*/ @@ -46,12 +45,12 @@ export type Constructor = new(...args: any[]) => T; /** * Add STATE value for being enriched (after COMPILED and before TYPESET) */ -newState('ENRICHED', 30); +newState('ENRICHED', STATE.COMPILED + 10); /** - * Add STATE value for adding speech (after TYPESET) + * Add STATE value for adding speech (after INSERTED) */ -newState('ATTACHSPEECH', 155); +newState('ATTACHSPEECH', STATE.INSERTED + 10); /*==========================================================================*/ @@ -243,7 +242,7 @@ export function EnrichedMathItemMixin[]; + + /** + * The identifier from setTimeout for the next speech loop + */ + protected speechTimeout: number = 0; + /** * Enrich the MathItem class used for this MathDocument, and create the * temporary MathItem used for enrchment @@ -421,15 +435,37 @@ export function EnrichedMathDocumentMixin).attachSpeech(this); + if (this.speechTimeout) { + clearTimeout(this.speechTimeout); + this.speechTimeout = 0; } + this.awaitingSpeech = Array.from(this.math); + this.speechTimeout = setTimeout( + () => this.attachSpeechLoop(), + this.options.speechTiming.initial + ); } this.processed.set('attach-speech'); } return this; } + /** + * Loops through math items to attach speech until the timeout threshold is reached. + */ + protected attachSpeechLoop() { + const timing = this.options.speechTiming; + const awaitingSpeech = this.awaitingSpeech; + const timeStart = new Date().getTime(); + const timeEnd = timeStart + timing.threshold; + do { + const math = awaitingSpeech.shift(); + (math as EnrichedMathItem).attachSpeech(this); + } while (awaitingSpeech.length && new Date().getTime() < timeEnd); + this.speechTimeout = awaitingSpeech.length ? + setTimeout(() => this.attachSpeechLoop(), timing.intermediate) : 0; + } + /** * Enrich the MathItems in this MathDocument */ diff --git a/ts/a11y/speech/GeneratorPool.ts b/ts/a11y/speech/GeneratorPool.ts index 74dbedcf9..79732c82f 100644 --- a/ts/a11y/speech/GeneratorPool.ts +++ b/ts/a11y/speech/GeneratorPool.ts @@ -161,9 +161,9 @@ export class GeneratorPool { public computeSpeech(node: N, mml: string): [string, string] { this.element = Sre.parseDOM(mml); const xml = this.prepareXml(node); - const speech = this.speechGenerator.getSpeech(xml, this.element); - const braille = this.brailleGenerator.getSpeech(xml, this.element); - if (this.options.enableSpeech || this.options.enableBraille) { + const speech = this.options.enableSpeech ? this.speechGenerator.getSpeech(xml, this.element) : ''; + const braille = this.options.enableBraille ? this.brailleGenerator.getSpeech(xml, this.element) : ''; + if (speech || braille) { this.setAria(node, xml, this.options.sre.locale); } return [speech, braille]; diff --git a/ts/a11y/speech/SpeechUtil.ts b/ts/a11y/speech/SpeechUtil.ts index 396f7661d..67266ab7c 100644 --- a/ts/a11y/speech/SpeechUtil.ts +++ b/ts/a11y/speech/SpeechUtil.ts @@ -191,7 +191,7 @@ export function buildLabel( * @param {string} speech The speech string. * @param {string=} locale An optional locale. * @param {string=} rate The base speech rate. - * @return {[string, SsmlElement[]]} The speech with the ssml annotation structure + * @return {[string, SsmlElement[]]} The speech with the ssml annotation structure */ export function buildSpeech(speech: string, locale: string = 'en', rate: string = '100'): [string, SsmlElement[]] { diff --git a/ts/ui/lazy/LazyHandler.ts b/ts/ui/lazy/LazyHandler.ts index b7b3999e5..bb9a68a9a 100644 --- a/ts/ui/lazy/LazyHandler.ts +++ b/ts/ui/lazy/LazyHandler.ts @@ -23,9 +23,9 @@ import {MathDocumentConstructor, ContainerList} from '../../core/MathDocument.js'; import {MathItem, STATE, newState} from '../../core/MathItem.js'; -import {HTMLMathItem} from '../../handlers/html/HTMLMathItem.js'; import {HTMLDocument} from '../../handlers/html/HTMLDocument.js'; import {HTMLHandler} from '../../handlers/html/HTMLHandler.js'; +import {EnrichedMathItem} from '../../a11y/semantic-enrich.js'; import {handleRetriesFor} from '../../util/Retries.js'; import {OptionList} from '../../util/Options.js'; @@ -151,7 +151,7 @@ export interface LazyMathItem extends MathItem { * @template D The Document class * @template B The MathItem class to extend */ -export function LazyMathItemMixin>>( +export function LazyMathItemMixin>>( BaseMathItem: B ): Constructor> & B { @@ -260,6 +260,19 @@ export function LazyMathItemMixin) => { + if (this.state() >= STATE.ATTACHSPEECH) return; + if (!this.lazyTypeset) { + super.attachSpeech?.(document); + } + this.state(STATE.ATTACHSPEECH); + } + }; } @@ -325,8 +338,14 @@ B extends MathDocumentConstructor>>( */ public static OPTIONS: OptionList = { ...BaseDocument.OPTIONS, - lazyMargin: '200px', + lazyMargin: '500px', lazyAlwaysTypeset: null, + speechTiming: { + ...(BaseDocument.OPTIONS.speechTiming || {}), + initial: 150, + threshold: 100, + intermediate: 10 + }, renderActions: { ...BaseDocument.OPTIONS.renderActions, lazyAlways: [STATE.LAZYALWAYS, 'lazyAlways', '', false] @@ -390,7 +409,7 @@ B extends MathDocumentConstructor>>( // Use the LazyMathItem for math items // this.options.MathItem = - LazyMathItemMixin>>(this.options.MathItem); + LazyMathItemMixin>>(this.options.MathItem); // // Allocate a process bit for lazyAlways //