Skip to content

Commit 80f1978

Browse files
authored
Merge pull request #1113 from mathjax/speech-promise
make typesetPromise() wait for speech to be attached, add synchronous option, and copy ARIA labels to internal MathML
2 parents 1bb54a4 + cc80678 commit 80f1978

File tree

3 files changed

+67
-14
lines changed

3 files changed

+67
-14
lines changed

ts/a11y/semantic-enrich.ts

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export function EnrichedMathItemMixin<N, T, D, B extends Constructor<AbstractMat
151151
public generatorPool = new GeneratorPool<N, T, D>();
152152

153153
/**
154-
* The MathML adaptor.
154+
* The MathML serializer
155155
*/
156156
public toMathML = toMathML;
157157

@@ -285,9 +285,11 @@ export function EnrichedMathItemMixin<N, T, D, B extends Constructor<AbstractMat
285285
const node = this.typesetRoot;
286286
if (speech && options.enableSpeech) {
287287
adaptor.setAttribute(node, 'aria-label', speech as string);
288+
this.root.attributes.set('aria-label', speech);
288289
}
289290
if (braille && options.enableBraille) {
290291
adaptor.setAttribute(node, 'aria-braillelabel', braille as string);
292+
this.root.attributes.set('aria-braillelabel', braille);
291293
}
292294
for (const child of adaptor.childNodes(node) as N[]) {
293295
adaptor.setAttribute(child, 'aria-hidden', 'true');
@@ -379,6 +381,7 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
379381
attachSpeech: [STATE.ATTACHSPEECH]
380382
}),
381383
speechTiming: {
384+
asynchronous: true, // true to allow screen updates while adding speech, false to not
382385
initial: 100, // initial delay until starting to add speech
383386
threshold: 250, // time (in milliseconds) to process speech before letting screen update
384387
intermediate: 10 // delay after processing speech reaches the threshold
@@ -406,6 +409,11 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
406409
*/
407410
protected speechTimeout: number = 0;
408411

412+
/**
413+
* The function to resolve when the speech loop finishes
414+
*/
415+
protected attachSpeechDone: () => void;
416+
409417
/**
410418
* Enrich the MathItem class used for this MathDocument, and create the
411419
* temporary MathItem used for enrchment
@@ -435,21 +443,46 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
435443
public attachSpeech() {
436444
if (!this.processed.isSet('attach-speech')) {
437445
if (this.options.enableSpeech || this.options.enableBraille) {
438-
if (this.speechTimeout) {
439-
clearTimeout(this.speechTimeout);
440-
this.speechTimeout = 0;
446+
if (this.options.speechTiming.asynchronous) {
447+
this.attachSpeechAsync();
448+
} else {
449+
this.attachSpeechSync();
441450
}
442-
this.awaitingSpeech = Array.from(this.math);
443-
this.speechTimeout = setTimeout(
444-
() => this.attachSpeechLoop(),
445-
this.options.speechTiming.initial
446-
);
447451
}
448452
this.processed.set('attach-speech');
449453
}
450454
return this;
451455
}
452456

457+
/**
458+
* Add speech synchronously (e.g., for use in node applications on the server)
459+
*/
460+
protected attachSpeechSync() {
461+
for (const math of this.math) {
462+
(math as EnrichedMathItem<N, T, D>).attachSpeech(this);
463+
}
464+
}
465+
466+
/**
467+
* Add speech in small chunks, allowing screen updates in between
468+
* (e.g., helpful with lazy typesetting)
469+
*/
470+
protected attachSpeechAsync() {
471+
if (this.speechTimeout) {
472+
clearTimeout(this.speechTimeout);
473+
this.speechTimeout = 0;
474+
this.attachSpeechDone();
475+
}
476+
this.awaitingSpeech = Array.from(this.math);
477+
this.renderPromises.push(new Promise<void>((ok, _fail) => {
478+
this.attachSpeechDone = ok;
479+
}));
480+
this.speechTimeout = setTimeout(
481+
() => this.attachSpeechLoop(),
482+
this.options.speechTiming.initial
483+
);
484+
}
485+
453486
/**
454487
* Loops through math items to attach speech until the timeout threshold is reached.
455488
*/
@@ -462,8 +495,12 @@ export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstru
462495
const math = awaitingSpeech.shift();
463496
(math as EnrichedMathItem<N, T, D>).attachSpeech(this);
464497
} while (awaitingSpeech.length && new Date().getTime() < timeEnd);
465-
this.speechTimeout = awaitingSpeech.length ?
466-
setTimeout(() => this.attachSpeechLoop(), timing.intermediate) : 0;
498+
if (awaitingSpeech.length) {
499+
this.speechTimeout = setTimeout(() => this.attachSpeechLoop(), timing.intermediate);
500+
} else {
501+
this.speechTimeout = 0;
502+
this.attachSpeechDone();
503+
}
467504
}
468505

469506
/**

ts/components/startup.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,8 @@ export namespace Startup {
301301

302302
/**
303303
* The default pageReady() function called when the page is ready to be processed,
304-
* which returns the function that performs the initial typesetting, if needed.
304+
* which returns a promise that does any initial font loading, plus the initial
305+
* typesetting, if needed.
305306
*
306307
* Setting Mathjax.startup.pageReady in the configuration will override this.
307308
*/
@@ -310,7 +311,8 @@ export namespace Startup {
310311
(output as COMMONJAX).font.loadDynamicFiles() : Promise.resolve())
311312
.then(CONFIG.typeset && MathJax.typesetPromise ?
312313
() => typesetPromise(CONFIG.elements) :
313-
Promise.resolve());
314+
Promise.resolve())
315+
.then(() => promiseResolve());
314316
}
315317

316318
/**
@@ -321,6 +323,9 @@ export namespace Startup {
321323
document.reset();
322324
return mathjax.handleRetriesFor(() => {
323325
document.render();
326+
const promise = Promise.all(document.renderPromises);
327+
document.renderPromises = [];
328+
return promise;
324329
});
325330
}
326331

ts/core/MathDocument.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import {MmlNode, TextNode} from './MmlTree/MmlNode.js';
3030
import {MmlFactory} from '../core/MmlTree/MmlFactory.js';
3131
import {DOMAdaptor} from '../core/DOMAdaptor.js';
3232
import {BitField, BitFieldClass} from '../util/BitField.js';
33-
3433
import {PrioritizedList} from '../util/PrioritizedList.js';
3534

3635
/*****************************************************************/
@@ -319,6 +318,11 @@ export interface MathDocument<N, T, D> {
319318
*/
320319
renderActions: RenderList<N, T, D>;
321320

321+
/**
322+
* The promises that indicate when rendering is fully complete
323+
*/
324+
renderPromises: Promise<void>[];
325+
322326
/**
323327
* This object tracks what operations have been performed, so that (when
324328
* asynchronous operations are used), the ones that have already been
@@ -602,6 +606,11 @@ export abstract class AbstractMathDocument<N, T, D> implements MathDocument<N, T
602606
*/
603607
public renderActions: RenderList<N, T, D>;
604608

609+
/**
610+
* The render action promise list
611+
*/
612+
public renderPromises: Promise<void>[];
613+
605614
/**
606615
* The bit-field used to tell what steps have been taken on the document (for retries)
607616
*/
@@ -640,6 +649,7 @@ export abstract class AbstractMathDocument<N, T, D> implements MathDocument<N, T
640649
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
641650
this.math = new (this.options['MathList'] || DefaultMathList)();
642651
this.renderActions = RenderList.create<N, T, D>(this.options['renderActions']);
652+
this.renderPromises = [];
643653
this.processed = new AbstractMathDocument.ProcessBits();
644654
this.outputJax = this.options['OutputJax'] || new DefaultOutputJax<N, T, D>();
645655
let inputJax = this.options['InputJax'] || [new DefaultInputJax<N, T, D>()];
@@ -694,6 +704,7 @@ export abstract class AbstractMathDocument<N, T, D> implements MathDocument<N, T
694704
* @override
695705
*/
696706
public render() {
707+
this.renderPromises = [];
697708
this.renderActions.renderDoc(this);
698709
return this;
699710
}

0 commit comments

Comments
 (0)