From 575d58838ea023c35b023581756d84ec5488ddb8 Mon Sep 17 00:00:00 2001 From: zorkow Date: Thu, 22 Sep 2022 15:18:54 +0200 Subject: [PATCH 01/47] Very initial attempt at getting LaTeX into mml output. --- ts/input/tex.ts | 1 + ts/input/tex/TexParser.ts | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ts/input/tex.ts b/ts/input/tex.ts index 98d9dfc46..ae754c7ee 100644 --- a/ts/input/tex.ts +++ b/ts/input/tex.ts @@ -196,6 +196,7 @@ export class TeX extends AbstractInputJax { node = this.options.formatError(this, err); } node = this.parseOptions.nodeFactory.create('node', 'math', [node]); + node.attributes.set('latex', this.latex); if (globalEnv?.indentalign) { NodeUtil.setAttribute(node, 'indentalign', globalEnv.indentalign); } diff --git a/ts/input/tex/TexParser.ts b/ts/input/tex/TexParser.ts index d0899712f..66c7cbcc6 100644 --- a/ts/input/tex/TexParser.ts +++ b/ts/input/tex/TexParser.ts @@ -74,6 +74,7 @@ export default class TexParser { * @param {ParseOptions} configuration A parser configuration. */ constructor(private _string: string, env: EnvList, public configuration: ParseOptions) { + console.log('New Parser'); const inner = env.hasOwnProperty('isInner'); const isInner = env['isInner'] as boolean; delete env['isInner']; @@ -88,6 +89,9 @@ export default class TexParser { this.stack = new Stack(this.itemFactory, ENV, inner ? isInner : true); this.Parse(); this.Push(this.itemFactory.create('stop')); + console.log('Final'); + console.log(this.string); + this.updateResult(this.string, this.i); } /** @@ -134,9 +138,38 @@ export default class TexParser { * @return {ParseResult} The output of the parsing function. */ public parse(kind: HandlerType, input: ParseInput): ParseResult { - return this.configuration.handlers.get(kind).parse(input); + let oldI = this.i; + let result = this.configuration.handlers.get(kind).parse(input); + this.updateResult(input[1], oldI); + return result; } + // Currently works without environments. + private updateResult(input: string, old: number) { + console.log('Result Start'); + console.log(this.stack.toString()); + let node = this.stack.Prev(true) as MmlNode; + console.log(node); + if (!node) { + return; + } + console.log('Has already entry: ' + node.attributes.get('latex')); + console.log(node.toString()); + let str = old !== this.i ? this.string.slice(old, this.i) : input; + str = str.trim(); + if (!str) { + return; + } + if (input === '\\') { + str = '\\' + str; + } + console.log('Full String: ' + this.string); + console.log('Save: ' + str); + console.log('Input: ' + input); + console.log('Result End'); + node.attributes.set('latex', str); + } + /** * Maps a symbol to its "parse value" if it exists. From 1f6aee5bf44d28096785fc56654f6a1aef46a95e Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 25 Sep 2022 19:32:06 -0400 Subject: [PATCH 02/47] Switch to sre v4.1.0-beta.2 --- package-lock.json | 42 +++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42ece0be1..66b08a48c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "mathjax-modern-font": "^1.0.0-alpha.0", "mhchemparser": "^4.1.0", "mj-context-menu": "^0.6.1", - "speech-rule-engine": "^4.0.6" + "speech-rule-engine": "^4.1.0-beta.1" }, "devDependencies": { "@babel/core": "^7.17.12", @@ -2257,9 +2257,9 @@ "dev": true }, "node_modules/commander": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", - "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", "engines": { "node": "^12.20.0 || >=14" } @@ -4094,11 +4094,11 @@ } }, "node_modules/speech-rule-engine": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.6.tgz", - "integrity": "sha512-Hqa4ywf7d3lX2YsnnE8BeEdqFyaTwPSyyVhVGWZlQw4XVh0NCijyVsMZD3I9HsG5JBuDXyRaMVVNZcGJlKbZxA==", + "version": "4.1.0-beta.1", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.0-beta.1.tgz", + "integrity": "sha512-a/fZTOHriKbJ+YzxfgP4wfI4t/If5CM0rZCZI0Hi5s/D3klNh9NQNLcc09ub18+kM/bF4T83oLJyo97C9pmiBw==", "dependencies": { - "commander": "9.2.0", + "commander": "9.4.0", "wicked-good-xpath": "1.3.0", "xmldom-sre": "0.1.31" }, @@ -4275,15 +4275,9 @@ } }, "node_modules/terser": { -<<<<<<< HEAD "version": "5.14.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", -======= - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", ->>>>>>> new-default-font "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -6699,9 +6693,9 @@ "dev": true }, "commander": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", - "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==" + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" }, "commondir": { "version": "1.0.1", @@ -8075,11 +8069,11 @@ } }, "speech-rule-engine": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.6.tgz", - "integrity": "sha512-Hqa4ywf7d3lX2YsnnE8BeEdqFyaTwPSyyVhVGWZlQw4XVh0NCijyVsMZD3I9HsG5JBuDXyRaMVVNZcGJlKbZxA==", + "version": "4.1.0-beta.1", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.0-beta.1.tgz", + "integrity": "sha512-a/fZTOHriKbJ+YzxfgP4wfI4t/If5CM0rZCZI0Hi5s/D3klNh9NQNLcc09ub18+kM/bF4T83oLJyo97C9pmiBw==", "requires": { - "commander": "9.2.0", + "commander": "9.4.0", "wicked-good-xpath": "1.3.0", "xmldom-sre": "0.1.31" } @@ -8216,15 +8210,9 @@ } }, "terser": { -<<<<<<< HEAD "version": "5.14.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", -======= - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", ->>>>>>> new-default-font "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", diff --git a/package.json b/package.json index ef6945e87..39190e87b 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,6 @@ "mathjax-modern-font": "^1.0.0-alpha.0", "mhchemparser": "^4.1.0", "mj-context-menu": "^0.6.1", - "speech-rule-engine": "^4.0.6" + "speech-rule-engine": "^4.1.0-beta.1" } } From b759071e8b7fd3e7caf54021eadb23bacc292ff5 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 25 Sep 2022 19:42:39 -0400 Subject: [PATCH 03/47] Change version to 4.0.0-alpha.1 --- package.json | 2 +- ts/components/version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 39190e87b..52a855026 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mathjax-full", - "version": "3.2.2", + "version": "4.0.0-alpha.1", "description": "Beautiful and accessible math in all browsers. MathJax is an open-source JavaScript display engine for LaTeX, MathML, and AsciiMath notation that works in all browsers and in server-side node applications. This package includes the source code as well as the packaged components.", "license": "Apache-2.0", "main": "components/src/node-main/node-main.js", diff --git a/ts/components/version.ts b/ts/components/version.ts index b32757d34..2ee08dc5c 100644 --- a/ts/components/version.ts +++ b/ts/components/version.ts @@ -22,4 +22,4 @@ * @author dpvc@mathjax.org (Davide Cervone) */ -export const VERSION = '3.2.2'; +export const VERSION = '4.0.0-alpha.1'; From 9397e7803d41d26f2f29ba1f37360cdd25a741bd Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 25 Sep 2022 19:45:29 -0400 Subject: [PATCH 04/47] Remove -full configurations --- .../src/tex-chtml-full/tex-chtml-full.js | 18 ------------------ .../src/tex-chtml-full/webpack.config.js | 8 -------- components/src/tex-svg-full/tex-svg-full.js | 18 ------------------ components/src/tex-svg-full/webpack.config.js | 8 -------- 4 files changed, 52 deletions(-) delete mode 100644 components/src/tex-chtml-full/tex-chtml-full.js delete mode 100644 components/src/tex-chtml-full/webpack.config.js delete mode 100644 components/src/tex-svg-full/tex-svg-full.js delete mode 100644 components/src/tex-svg-full/webpack.config.js diff --git a/components/src/tex-chtml-full/tex-chtml-full.js b/components/src/tex-chtml-full/tex-chtml-full.js deleted file mode 100644 index 10d6dffff..000000000 --- a/components/src/tex-chtml-full/tex-chtml-full.js +++ /dev/null @@ -1,18 +0,0 @@ -import {startup} from '../startup/init.js'; -import {Loader} from '../../../js/components/loader.js'; -import '../core/core.js'; -import '../input/tex-full/tex-full.js'; -import {loadFont} from '../output/chtml/chtml.js'; -import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; - -Loader.preLoad( - 'loader', 'startup', - 'core', - 'input/tex-full', - 'output/chtml', - 'ui/menu', - 'a11y/assistive-mml' -); - -loadFont(startup, true); diff --git a/components/src/tex-chtml-full/webpack.config.js b/components/src/tex-chtml-full/webpack.config.js deleted file mode 100644 index 56dda0e12..000000000 --- a/components/src/tex-chtml-full/webpack.config.js +++ /dev/null @@ -1,8 +0,0 @@ -const PACKAGE = require('../../webpack.common.js'); - -module.exports = PACKAGE( - 'tex-chtml-full', // the package to build - '../../../js', // location of the MathJax js library - [], // packages to link to - __dirname // our directory -); diff --git a/components/src/tex-svg-full/tex-svg-full.js b/components/src/tex-svg-full/tex-svg-full.js deleted file mode 100644 index b1d2bc3bd..000000000 --- a/components/src/tex-svg-full/tex-svg-full.js +++ /dev/null @@ -1,18 +0,0 @@ -import {startup} from '../startup/init.js'; -import {Loader} from '../../../js/components/loader.js'; -import '../core/core.js'; -import '../input/tex-full/tex-full.js'; -import {loadFont} from '../output/svg/svg.js'; -import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; - -Loader.preLoad( - 'loader', 'startup', - 'core', - 'input/tex-full', - 'output/svg', - 'ui/menu', - 'a11y/assistive-mml' -); - -loadFont(startup, true); diff --git a/components/src/tex-svg-full/webpack.config.js b/components/src/tex-svg-full/webpack.config.js deleted file mode 100644 index 391234bad..000000000 --- a/components/src/tex-svg-full/webpack.config.js +++ /dev/null @@ -1,8 +0,0 @@ -const PACKAGE = require('../../webpack.common.js'); - -module.exports = PACKAGE( - 'tex-svg-full', // the package to build - '../../../js', // location of the MathJax js library - [], // packages to link to - __dirname // our directory -); From 0d04d6fff7cd295479f0bf2806e527f72ea451aa Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 26 Sep 2022 18:36:48 -0400 Subject: [PATCH 05/47] Turn off assistiveMML and add setup for explorer in contextual menu --- ts/ui/menu/Menu.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/ui/menu/Menu.ts b/ts/ui/menu/Menu.ts index c3797c170..6f141c4ef 100644 --- a/ts/ui/menu/Menu.ts +++ b/ts/ui/menu/Menu.ts @@ -143,7 +143,7 @@ export class Menu { autocollapse: false, collapsible: false, inTabOrder: true, - assistiveMml: true, + assistiveMml: false, explorer: false }, jax: { @@ -700,6 +700,7 @@ export class Menu { protected applySettings() { this.setTabOrder(this.settings.inTabOrder); this.document.options.enableAssistiveMml = this.settings.assistiveMml; + this.document.options.enableExplorer = this.settings.explorer; this.document.outputJax.options.scale = parseFloat(this.settings.scale); if (this.settings.renderer !== this.defaultSettings.renderer) { this.setRenderer(this.settings.renderer); From 6c1fde57acaacc3453b008d3077c007ddc576f25 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 26 Sep 2022 18:38:33 -0400 Subject: [PATCH 06/47] Make a utility for loading SRE, and use it in combined configurations --- .../src/a11y/assistive-mml/assistive-mml.js | 10 ++++++++ components/src/a11y/explorer/explorer.js | 9 ++++++++ .../a11y/semantic-enrich/semantic-enrich.js | 2 ++ components/src/a11y/sre/sre.js | 2 ++ components/src/a11y/util.js | 23 +++++++++++++++++++ components/src/input/mml/init.js | 15 ++++++++++++ components/src/input/mml/mml.js | 14 +---------- .../src/mml-chtml-nofont/mml-chtml-nofont.js | 7 +++--- components/src/mml-chtml/mml-chtml.js | 6 ++--- .../src/mml-svg-nofont/mml-svg-nofont.js | 7 +++--- components/src/mml-svg/mml-svg.js | 7 +++--- components/src/output/chtml/chtml.js | 2 +- components/src/output/svg/svg.js | 2 +- components/src/output/util.js | 15 ++++++------ components/src/startup/init.js | 3 ++- .../src/tex-chtml-nofont/tex-chtml-nofont.js | 7 +++--- components/src/tex-chtml/tex-chtml.js | 8 +++---- .../tex-mml-chtml-nofont.js | 7 +++--- components/src/tex-mml-chtml/tex-mml-chtml.js | 7 +++--- .../tex-mml-svg-nofont/tex-mml-svg-nofont.js | 7 +++--- components/src/tex-mml-svg/tex-mml-svg.js | 7 +++--- .../src/tex-svg-nofont/tex-svg-nofont.js | 7 +++--- components/src/tex-svg/tex-svg.js | 7 +++--- 23 files changed, 111 insertions(+), 70 deletions(-) create mode 100644 components/src/a11y/util.js create mode 100644 components/src/input/mml/init.js diff --git a/components/src/a11y/assistive-mml/assistive-mml.js b/components/src/a11y/assistive-mml/assistive-mml.js index 745e65aee..3b82d3931 100644 --- a/components/src/a11y/assistive-mml/assistive-mml.js +++ b/components/src/a11y/assistive-mml/assistive-mml.js @@ -1,7 +1,17 @@ import './lib/assistive-mml.js'; +import {combineDefaults} from '../../../../js/components/global.js'; import {AssistiveMmlHandler} from '../../../../js/a11y/assistive-mml.js'; if (MathJax.startup) { + if (MathJax.config.options && MathJax.config.options.enableAssistiveMml !== false) { + combineDefaults(MathJax.config, 'options', { + menuOptions: { + settings: { + assistiveMml: true + } + } + }); + } MathJax.startup.extendHandler(handler => AssistiveMmlHandler(handler)); } diff --git a/components/src/a11y/explorer/explorer.js b/components/src/a11y/explorer/explorer.js index 25a6f7b08..ea70a44f2 100644 --- a/components/src/a11y/explorer/explorer.js +++ b/components/src/a11y/explorer/explorer.js @@ -4,5 +4,14 @@ import {combineDefaults} from '../../../../js/components/global.js'; import {ExplorerHandler} from '../../../../js/a11y/explorer.js'; if (MathJax.startup) { + if (MathJax.config.options && MathJax.config.options.enableExplorer !== false) { + combineDefaults(MathJax.config, 'options', { + menuOptions: { + settings: { + explorer: true + } + } + }); + } MathJax.startup.extendHandler(handler => ExplorerHandler(handler)); } diff --git a/components/src/a11y/semantic-enrich/semantic-enrich.js b/components/src/a11y/semantic-enrich/semantic-enrich.js index 2491174dc..a3a72caaa 100644 --- a/components/src/a11y/semantic-enrich/semantic-enrich.js +++ b/components/src/a11y/semantic-enrich/semantic-enrich.js @@ -5,6 +5,8 @@ import Sre from '../../../../js/a11y/sre.js'; import {EnrichHandler} from '../../../../js/a11y/semantic-enrich.js'; import {MathML} from '../../../../js/input/mathml.js'; +export {Sre}; + if (MathJax.loader) { combineDefaults(MathJax.config.loader, 'a11y/semantic-enrich', {checkReady: () => Sre.sreReady()}); } diff --git a/components/src/a11y/sre/sre.js b/components/src/a11y/sre/sre.js index 0327644d9..b4e9e5660 100644 --- a/components/src/a11y/sre/sre.js +++ b/components/src/a11y/sre/sre.js @@ -2,6 +2,8 @@ import './lib/sre.js'; import './sre_config.js'; import Sre from '../../../../js/a11y/sre.js'; +export {Sre}; + if (MathJax.startup) { ((typeof window !== 'undefined') ? window : global). SREfeature.custom = (loc) => Sre.preloadLocales(loc); diff --git a/components/src/a11y/util.js b/components/src/a11y/util.js new file mode 100644 index 000000000..99edbf8d9 --- /dev/null +++ b/components/src/a11y/util.js @@ -0,0 +1,23 @@ +import {Loader} from '../../../js/components/loader.js'; +import '../input/mml/init.js'; +import {Sre} from '../a11y/sre/sre.js'; +import '../a11y/semantic-enrich/semantic-enrich.js'; +import '../a11y/explorer/explorer.js'; +import MathMaps from '../../../js/a11y/mathmaps.js'; +import base from 'speech-rule-engine/lib/mathmaps/base.json'; +import en from 'speech-rule-engine/lib/mathmaps/en.json'; +import nemeth from 'speech-rule-engine/lib/mathmaps/nemeth.json'; + +Loader.preLoad( + 'a11y/sre', + 'a11y/semantic-enrich', + 'a11y/explorer' +); + +MathMaps.set('base', base); +MathMaps.set('en', en); +MathMaps.set('nemeth', nemeth); + +export function checkSre(startup) { + return () => startup(() => Sre.sreReady()); +} diff --git a/components/src/input/mml/init.js b/components/src/input/mml/init.js new file mode 100644 index 000000000..1fe5cc151 --- /dev/null +++ b/components/src/input/mml/init.js @@ -0,0 +1,15 @@ +import './lib/mml.js'; + +import {MathML} from '../../../../js/input/mathml.js'; +export {MathML}; + +if (MathJax.loader) { + // + // Install a path-filter to cause loading of an entity file to load all entities, + // since the individual files don't have individual components. + // + MathJax.loader.pathFilters.add((data) => { + data.name = data.name.replace(/\/util\/entities\/.*?\.js/, '/input/mml/entities.js'); + return true; + }); +} diff --git a/components/src/input/mml/mml.js b/components/src/input/mml/mml.js index 7e81b27f4..98770e0d8 100644 --- a/components/src/input/mml/mml.js +++ b/components/src/input/mml/mml.js @@ -1,18 +1,6 @@ -import './lib/mml.js'; - -import {MathML} from '../../../../js/input/mathml.js'; +import {MathML} from './init.js'; if (MathJax.startup) { MathJax.startup.registerConstructor('mml', MathML); MathJax.startup.useInput('mml'); } -if (MathJax.loader) { - // - // Install a path-filter to cause loading of an entity file to load all entities, - // since the individual files don't have individual components. - // - MathJax.loader.pathFilters.add((data) => { - data.name = data.name.replace(/\/util\/entities\/.*?\.js/, '/input/mml/entities.js'); - return true; - }); -} diff --git a/components/src/mml-chtml-nofont/mml-chtml-nofont.js b/components/src/mml-chtml-nofont/mml-chtml-nofont.js index 865dd768e..7696019f3 100644 --- a/components/src/mml-chtml-nofont/mml-chtml-nofont.js +++ b/components/src/mml-chtml-nofont/mml-chtml-nofont.js @@ -4,15 +4,14 @@ import '../core/core.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/chtml/chtml.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/mml', 'output/chtml', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup); +loadFont(checkSre(startup)); diff --git a/components/src/mml-chtml/mml-chtml.js b/components/src/mml-chtml/mml-chtml.js index 550a6ccd0..82f86fef7 100644 --- a/components/src/mml-chtml/mml-chtml.js +++ b/components/src/mml-chtml/mml-chtml.js @@ -4,14 +4,14 @@ import '../core/core.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/chtml/chtml.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/mml', 'output/chtml', - 'ui/menu', 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup, true); +loadFont(checkSre(startup), true); diff --git a/components/src/mml-svg-nofont/mml-svg-nofont.js b/components/src/mml-svg-nofont/mml-svg-nofont.js index 89921c9d8..b4492d576 100644 --- a/components/src/mml-svg-nofont/mml-svg-nofont.js +++ b/components/src/mml-svg-nofont/mml-svg-nofont.js @@ -4,15 +4,14 @@ import '../core/core.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/svg/svg.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/mml', 'output/svg', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup); +loadFont(checkSre(startup)); diff --git a/components/src/mml-svg/mml-svg.js b/components/src/mml-svg/mml-svg.js index 16abd01a6..684721d50 100644 --- a/components/src/mml-svg/mml-svg.js +++ b/components/src/mml-svg/mml-svg.js @@ -4,15 +4,14 @@ import '../core/core.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/svg/svg.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/mml', 'output/svg', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup, true); +loadFont(checkSre(startup), true); diff --git a/components/src/output/chtml/chtml.js b/components/src/output/chtml/chtml.js index 8a258cfa5..d34b3d643 100644 --- a/components/src/output/chtml/chtml.js +++ b/components/src/output/chtml/chtml.js @@ -7,6 +7,6 @@ import {OutputUtil} from '../util.js'; OutputUtil.config('chtml', CHTML, fontName, DefaultFont); export function loadFont(startup, preload) { - OutputUtil.loadFont(startup, 'chtml', fontName, preload); + return OutputUtil.loadFont(startup, 'chtml', fontName, preload); } diff --git a/components/src/output/svg/svg.js b/components/src/output/svg/svg.js index 091fe4707..aab7c5588 100644 --- a/components/src/output/svg/svg.js +++ b/components/src/output/svg/svg.js @@ -7,6 +7,6 @@ import {OutputUtil} from '../util.js'; OutputUtil.config('svg', SVG, fontName, DefaultFont); export function loadFont(startup, preload) { - OutputUtil.loadFont(startup, 'svg', fontName, preload); + return OutputUtil.loadFont(startup, 'svg', fontName, preload); } diff --git a/components/src/output/util.js b/components/src/output/util.js index 31cd9d45a..3986412db 100644 --- a/components/src/output/util.js +++ b/components/src/output/util.js @@ -77,14 +77,15 @@ export const OutputUtil = { }, loadFont(startup, jax, font, preload) { - if (MathJax.loader) { - if (preload) { - MathJax.loader.preLoad(`[${font}]/${jax}`); - } - const check = MathJax.config.loader[`output/${jax}`]; - const start = (check && check.checkReady ? check.checkReady().then(startup) : startup()); - start.catch(err => console.log(err)); + if (!MathJax.loader) { + return Promise.resolve(); + } + if (preload) { + MathJax.loader.preLoad(`[${font}]/${jax}`); } + const check = MathJax.config.loader[`output/${jax}`]; + const start = (check && check.checkReady ? check.checkReady().then(startup) : startup()); + return start.catch(err => console.log(err)); } }; diff --git a/components/src/startup/init.js b/components/src/startup/init.js index 5a2fa7895..28293fa59 100644 --- a/components/src/startup/init.js +++ b/components/src/startup/init.js @@ -11,8 +11,9 @@ combineDefaults(MathJax.config.loader, 'paths', paths); combineDefaults(MathJax.config.loader, 'provides', provides); combineDefaults(MathJax.config.loader, 'source', compatibility); -export function startup() { +export function startup(ready) { return Loader.load(...CONFIG.load) + .then(() => (ready || function () {})()) .then(() => CONFIG.ready()) .catch(error => CONFIG.failed(error)); } diff --git a/components/src/tex-chtml-nofont/tex-chtml-nofont.js b/components/src/tex-chtml-nofont/tex-chtml-nofont.js index d9ef13112..45ed27006 100644 --- a/components/src/tex-chtml-nofont/tex-chtml-nofont.js +++ b/components/src/tex-chtml-nofont/tex-chtml-nofont.js @@ -4,14 +4,13 @@ import '../core/core.js'; import '../input/tex/tex.js'; import {loadFont} from '../output/chtml/chtml.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'core', 'input/tex', 'output/chtml', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup); +loadFont(checkSre(startup)); diff --git a/components/src/tex-chtml/tex-chtml.js b/components/src/tex-chtml/tex-chtml.js index 1ab4a4cc4..09d7d07a8 100644 --- a/components/src/tex-chtml/tex-chtml.js +++ b/components/src/tex-chtml/tex-chtml.js @@ -4,15 +4,15 @@ import '../core/core.js'; import '../input/tex/tex.js'; import {loadFont} from '../output/chtml/chtml.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/tex', 'output/chtml', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup, true); +loadFont(checkSre(startup), true); + diff --git a/components/src/tex-mml-chtml-nofont/tex-mml-chtml-nofont.js b/components/src/tex-mml-chtml-nofont/tex-mml-chtml-nofont.js index f31f4ad87..94e1dd50b 100644 --- a/components/src/tex-mml-chtml-nofont/tex-mml-chtml-nofont.js +++ b/components/src/tex-mml-chtml-nofont/tex-mml-chtml-nofont.js @@ -5,15 +5,14 @@ import '../input/tex/tex.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/chtml/chtml.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/tex', 'input/mml', 'output/chtml', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup); +loadFont(checkSre(startup)); diff --git a/components/src/tex-mml-chtml/tex-mml-chtml.js b/components/src/tex-mml-chtml/tex-mml-chtml.js index 0beafd050..f52a4219f 100644 --- a/components/src/tex-mml-chtml/tex-mml-chtml.js +++ b/components/src/tex-mml-chtml/tex-mml-chtml.js @@ -5,15 +5,14 @@ import '../input/tex/tex.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/chtml/chtml.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/tex', 'input/mml', 'output/chtml', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup, true); +loadFont(checkSre(startup), true); diff --git a/components/src/tex-mml-svg-nofont/tex-mml-svg-nofont.js b/components/src/tex-mml-svg-nofont/tex-mml-svg-nofont.js index 380a5cefe..27314dac5 100644 --- a/components/src/tex-mml-svg-nofont/tex-mml-svg-nofont.js +++ b/components/src/tex-mml-svg-nofont/tex-mml-svg-nofont.js @@ -5,15 +5,14 @@ import '../input/tex/tex.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/svg/svg.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/tex', 'input/mml', 'output/svg', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup); +loadFont(checkSre(startup)); diff --git a/components/src/tex-mml-svg/tex-mml-svg.js b/components/src/tex-mml-svg/tex-mml-svg.js index 31616f430..cd85e8f19 100644 --- a/components/src/tex-mml-svg/tex-mml-svg.js +++ b/components/src/tex-mml-svg/tex-mml-svg.js @@ -5,15 +5,14 @@ import '../input/tex/tex.js'; import '../input/mml/mml.js'; import {loadFont} from '../output/svg/svg.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/tex', 'input/mml', 'output/svg', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup, true); +loadFont(checkSre(startup), true); diff --git a/components/src/tex-svg-nofont/tex-svg-nofont.js b/components/src/tex-svg-nofont/tex-svg-nofont.js index 66895fc5d..714f81975 100644 --- a/components/src/tex-svg-nofont/tex-svg-nofont.js +++ b/components/src/tex-svg-nofont/tex-svg-nofont.js @@ -4,14 +4,13 @@ import '../core/core.js'; import '../input/tex/tex.js'; import {loadFont} from '../output/svg/svg.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'core', 'input/tex', 'output/svg', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup); +loadFont(checkSre(startup)); diff --git a/components/src/tex-svg/tex-svg.js b/components/src/tex-svg/tex-svg.js index dac32f339..bc2708bb0 100644 --- a/components/src/tex-svg/tex-svg.js +++ b/components/src/tex-svg/tex-svg.js @@ -4,15 +4,14 @@ import '../core/core.js'; import '../input/tex/tex.js'; import {loadFont} from '../output/svg/svg.js'; import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; +import {checkSre} from '../a11y/util.js'; Loader.preLoad( 'loader', 'startup', 'core', 'input/tex', 'output/svg', - 'ui/menu', - 'a11y/assistive-mml' + 'ui/menu' ); -loadFont(startup, true); +loadFont(checkSre(startup), true); From 8c6d2797d6e4c43beef09811beb729b649ac8add Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 26 Sep 2022 18:39:32 -0400 Subject: [PATCH 07/47] Don't need tex-chtml-full-speech, since speech is on for all components --- .../tex-chtml-full-speech.js | 28 ------------------- .../tex-chtml-full-speech/webpack.config.js | 9 ------ 2 files changed, 37 deletions(-) delete mode 100644 components/src/tex-chtml-full-speech/tex-chtml-full-speech.js delete mode 100644 components/src/tex-chtml-full-speech/webpack.config.js diff --git a/components/src/tex-chtml-full-speech/tex-chtml-full-speech.js b/components/src/tex-chtml-full-speech/tex-chtml-full-speech.js deleted file mode 100644 index c4342fc40..000000000 --- a/components/src/tex-chtml-full-speech/tex-chtml-full-speech.js +++ /dev/null @@ -1,28 +0,0 @@ -import {startup} from '../startup/init.js'; -import {Loader} from '../../../js/components/loader.js'; -import '../core/core.js'; -import '../input/tex-full/tex-full.js'; -import {loadFont} from '../output/chtml/chtml.js'; -import '../ui/menu/menu.js'; -import '../a11y/assistive-mml/assistive-mml.js'; -import '../a11y/sre/sre.js'; -import MathMaps from '../../../js/a11y/mathmaps.js'; -import base from 'speech-rule-engine/lib/mathmaps/base.json'; -import en from 'speech-rule-engine/lib/mathmaps/en.json'; -import nemeth from 'speech-rule-engine/lib/mathmaps/nemeth.json'; - -Loader.preLoad( - 'loader', 'startup', - 'core', - 'input/tex-full', - 'output/chtml', - 'ui/menu', - 'a11y/assistive-mml', - 'a11y/sre' -); - -MathMaps.set('base', base); -MathMaps.set('en', en); -MathMaps.set('nemeth', nemeth); - -loadFont(startup, true); diff --git a/components/src/tex-chtml-full-speech/webpack.config.js b/components/src/tex-chtml-full-speech/webpack.config.js deleted file mode 100644 index 1c12fdedd..000000000 --- a/components/src/tex-chtml-full-speech/webpack.config.js +++ /dev/null @@ -1,9 +0,0 @@ -const PACKAGE = require('../../webpack.common.js'); - -module.exports = PACKAGE( - 'tex-chtml-full-speech', // the package to build - '../../../js', // location of the MathJax js library - [ // packages to link to - ], - __dirname // our directory -); From d10f7dcccf09f3a73e24999dab52ed0696075329 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 26 Sep 2022 19:14:59 -0400 Subject: [PATCH 08/47] Allow semantic-enrich to enrich HTML-in-MML --- components/src/a11y/semantic-enrich/semantic-enrich.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/src/a11y/semantic-enrich/semantic-enrich.js b/components/src/a11y/semantic-enrich/semantic-enrich.js index a3a72caaa..86ad04920 100644 --- a/components/src/a11y/semantic-enrich/semantic-enrich.js +++ b/components/src/a11y/semantic-enrich/semantic-enrich.js @@ -12,5 +12,5 @@ if (MathJax.loader) { } if (MathJax.startup) { - MathJax.startup.extendHandler(handler => EnrichHandler(handler, new MathML())); + MathJax.startup.extendHandler(handler => EnrichHandler(handler, new MathML({allowHtmlInTokenNodes: true}))); } From 0c6116b819265ab72c8ffff4486761609d30502d Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Tue, 27 Sep 2022 10:21:27 -0400 Subject: [PATCH 09/47] Update to latest mathjax-modern font --- package-lock.json | 18 +++++++++--------- package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66b08a48c..af32855d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "mathjax-full", - "version": "3.2.2", + "version": "4.0.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mathjax-full", - "version": "3.2.2", + "version": "4.0.0-alpha.1", "license": "Apache-2.0", "dependencies": { "esm": "^3.2.25", - "mathjax-modern-font": "^1.0.0-alpha.0", + "mathjax-modern-font": "^1.0.0-alpha.1", "mhchemparser": "^4.1.0", "mj-context-menu": "^0.6.1", "speech-rule-engine": "^4.1.0-beta.1" @@ -3484,9 +3484,9 @@ } }, "node_modules/mathjax-modern-font": { - "version": "1.0.0-alpha.0", - "resolved": "https://registry.npmjs.org/mathjax-modern-font/-/mathjax-modern-font-1.0.0-alpha.0.tgz", - "integrity": "sha512-ApA5prbkHLom2IvDyDjfilEpEK/KBJ944Wifsh3VPBUpbOYtzUbYobftFnU25G+CruY6FBnPSI2nzBX6bx57AQ==" + "version": "1.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/mathjax-modern-font/-/mathjax-modern-font-1.0.0-alpha.1.tgz", + "integrity": "sha512-0P9ToyKyRXvL3E2tSf2/O2w/xzqJjxxl2ZEU8sRf5HTuXaQ1oHGtZokyuGk88Tr3dallUhuvBGg7cskvD1l69A==" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -7594,9 +7594,9 @@ } }, "mathjax-modern-font": { - "version": "1.0.0-alpha.0", - "resolved": "https://registry.npmjs.org/mathjax-modern-font/-/mathjax-modern-font-1.0.0-alpha.0.tgz", - "integrity": "sha512-ApA5prbkHLom2IvDyDjfilEpEK/KBJ944Wifsh3VPBUpbOYtzUbYobftFnU25G+CruY6FBnPSI2nzBX6bx57AQ==" + "version": "1.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/mathjax-modern-font/-/mathjax-modern-font-1.0.0-alpha.1.tgz", + "integrity": "sha512-0P9ToyKyRXvL3E2tSf2/O2w/xzqJjxxl2ZEU8sRf5HTuXaQ1oHGtZokyuGk88Tr3dallUhuvBGg7cskvD1l69A==" }, "merge-stream": { "version": "2.0.0", diff --git a/package.json b/package.json index 52a855026..d25dde578 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ }, "dependencies": { "esm": "^3.2.25", - "mathjax-modern-font": "^1.0.0-alpha.0", + "mathjax-modern-font": "^1.0.0-alpha.1", "mhchemparser": "^4.1.0", "mj-context-menu": "^0.6.1", "speech-rule-engine": "^4.1.0-beta.1" From 85f5ecdbe513350c7e0339cac00172022d68929c Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 2 Oct 2022 08:42:21 -0400 Subject: [PATCH 10/47] Include npm link commands in travis build process. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 807461f62..0a7df2210 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,10 @@ sudo: false script: - components/bin/version - npm install -- npm run compile -- npm run make-components +- npm link +- npm link mathjax-full +- npm run -s compile +- npm run -s make-components branches: only: - "/^\\d+\\.\\d+/" From 9d1370999feba6e608271b6309cb9d304c61b54a Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 2 Oct 2022 09:52:05 -0400 Subject: [PATCH 11/47] Update npm api_key --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a7df2210..fad2884ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,8 @@ deploy: provider: npm email: manager@mathjax.org api_key: - secure: iiJYaydsd6nypSMgjvQiTYIy+YF/oil5fqfVLmMkAQtykAqE5oS/HcJ6QxQa8DKHQCmNNhmUqMnD7H5jVhXGcbhEcx7dNxr7pXkrY2Vkyj8voYFIfZ05rwfaojQQPlPdrkwiLmAcUnz1v9gP20M6uVJrhVpBSe4bfHxSHbSUBjlRu4md8fXyA9cLJ3hu/QHV2ahjRDHL+axklCH8YFMW2bN7IRYALUaFeN6gZFWQvq6A4gT1zaFo0yTsej5z+PmyvDHYGzI1KfUioNa81KMWvSMIkRooJes2JrDFdo4HYXgYhZ/wb+7FLOJGeLLQejfQiVw+MS+7R+C6Ss/TcxddeBGg+x9+pZY4AvnVjuWfYW7WSh+iMZ1ujFE3xhqjVUI6WpowSfLK2lguY/8AZ1hKuWxnWw/UwCGcy6qOG0HMyE05mkl+VvPMITckkkeM/an4k7PeR+DY9L25Imp0jmpCyfk72KXMAEgVLAjnsy+l0EnZWK7oeYzDwEaw8HdOCmKKraRYFSetbqC+25g1iKc8ylTs1IxN7HXo0e0dTGSxwtU14L26GMHrC2pVsDdeR7+b5ll0C/6kevK794eQpzSEM50Addb1+fUJ3bn1y22H7OaglnJYxfuP44AYTENOn3fDJrIgcVG/yp273MImSYZiXL+xQBK8ZPEG5z36sH5/iAI= + secure: tKo38UbinY2eEkrzOqzYskaSQ4MDo9sD6j2i+t+cL6H03uwfPI0l1cx9YXIBDLK0CeJNFvy+aT6KB2IBtwv3/VfxSY14/IlmB2hXSXnwAFmnsv3Okejh73USceb+1kH4UTKIpP1B49tL+qtJPs/y5j4cpCZPGvYAf2BqI/EQyd1ENsj7wnz0+MQ9Bu+86aA9x7uUsDVPKVpl6yr3nx+nlQbwt8NVccGNLCZIzs6Fo+au8uqh3/IEYhstXFU/SxN3vNdJHKHUCWYAQDn2sJPjBVd1jEAmQkdLAr0DfOA73JnYF6R6C3peLy4MkXOSQWv4lo50ODNi+DRns4S97UvnJw9nWOrgey2mVcIkziiMyXQnogQ0Zu6IyMsRohI/wbaqNKkXZnNTmmTxzLZjZAeBl8EEfEStS3stnh8rMsJENuBKN7eNC3Ps0xJKOBaEIZjgVKA8GH6Vot1yzCMG9Ad4vne+EurEkPw/Bk6WAZ0wXBu56vlv1OGSUOOmYWZx7aA1xKtOahSrL+Y1mIyByBAFmZL22y8BUZF8QImaielnnv5rKXjl5ilEpcnSDZX0b6VC2FqEN5EmGwXAN1PyffC++K4Q0eS3VDD0p1ZQxGRL4dpsZE/ue9AKujdNGeNXJkFTaOKXDSD47nFw+dNt0dPvWwHJb9l7sW811Xe2N9YH4bo= on: tags: true skip_cleanup: true + From 8fcfa3ed6ee28b20fe7b2bd0b4f2c2af5effcfc5 Mon Sep 17 00:00:00 2001 From: zorkow Date: Mon, 10 Oct 2022 10:34:15 +0200 Subject: [PATCH 12/47] Initial LaTeX attempt. --- ts/input/tex/TexParser.ts | 43 ++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/ts/input/tex/TexParser.ts b/ts/input/tex/TexParser.ts index 74d9252d4..fc42b2615 100644 --- a/ts/input/tex/TexParser.ts +++ b/ts/input/tex/TexParser.ts @@ -74,7 +74,6 @@ export default class TexParser { * @param {ParseOptions} configuration A parser configuration. */ constructor(private _string: string, env: EnvList, public configuration: ParseOptions) { - console.log('New Parser'); const inner = env.hasOwnProperty('isInner'); const isInner = env['isInner'] as boolean; delete env['isInner']; @@ -145,15 +144,16 @@ export default class TexParser { // Currently works without environments. private updateResult(input: string, old: number) { - console.log('Result Start'); - console.log(this.stack.toString()); + // console.log('Updating'); let node = this.stack.Prev(true) as MmlNode; - console.log(node); if (!node) { return; } - console.log('Has already entry: ' + node.attributes.get('latex')); - console.log(node.toString()); + let existing = node.attributes.get('latex'); + // if (node.attributes.get('latex') === '{}') { + // console.log('{}'); + // console.log(node); + // } let str = old !== this.i ? this.string.slice(old, this.i) : input; str = str.trim(); if (!str) { @@ -162,12 +162,31 @@ export default class TexParser { if (input === '\\') { str = '\\' + str; } - console.log('Full String: ' + this.string); - console.log('Save: ' + str); - console.log('Input: ' + input); - console.log('Result End'); + if (str === '}') { + str = this.bracing(this.composeBraces(node)); + } + // if (node.kind === 'msubsup' || node.kind === 'moverunder') { + // this.updateChild(node.childNodes[1]); + // this.updateChild(node.childNodes[2]); + // } node.attributes.set('latex', str); } + + private bracing(str: string) { + return '{' + str + '}'; + } + + private updateChild(atom: MmlNode) { + if (!atom) return; + let str = this.composeBraces(atom); + atom.attributes.set('latex', this.bracing(str)); + } + + private composeBraces(atom: MmlNode) { + // TODO: Make this more secure! + let children = atom.childNodes[0].childNodes; + return children.map(x => x.attributes?.get('latex') || '').join(' '); + } /** @@ -226,6 +245,10 @@ export default class TexParser { * @param {StackItem|MmlNode} arg The new item. */ public Push(arg: StackItem | MmlNode) { + console.log(this.string); + console.log(this.i); + console.log(arg); + console.log(this.stack.Prev(true)); if (arg instanceof AbstractMmlNode && arg.isInferred) { this.PushAll(arg.childNodes); } else { From e10483b7f56598df8b9075daf1ee59564f64362a Mon Sep 17 00:00:00 2001 From: zorkow Date: Tue, 11 Oct 2022 16:32:23 +0200 Subject: [PATCH 13/47] Includes environments and closing braces. --- ts/a11y/explorer/ExplorerPool.ts | 2 +- ts/input/tex/StackItem.ts | 20 ++++++++++++++++ ts/input/tex/TexParser.ts | 41 +++++++++++++++----------------- ts/input/tex/base/BaseItems.ts | 29 ++++++++++++++++++---- 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/ts/a11y/explorer/ExplorerPool.ts b/ts/a11y/explorer/ExplorerPool.ts index 0a668b3d8..d836ffc5b 100644 --- a/ts/a11y/explorer/ExplorerPool.ts +++ b/ts/a11y/explorer/ExplorerPool.ts @@ -90,7 +90,7 @@ let allExplorers: {[options: string]: ExplorerInit} = { let explorer = ke.SpeechExplorer.create( doc, pool, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer; explorer.speechGenerator.setOptions({automark: false as any, markup: 'none', - locale: 'nemeth', domain: 'default', + locale: 'euro', domain: 'default', style: 'default', modality: 'braille'}); explorer.showRegion = 'viewBraille'; explorer.sound = true; diff --git a/ts/input/tex/StackItem.ts b/ts/input/tex/StackItem.ts index 900979ca2..f67bf39cd 100644 --- a/ts/input/tex/StackItem.ts +++ b/ts/input/tex/StackItem.ts @@ -82,6 +82,10 @@ export interface NodeStack { */ Clear(): void; + startStr: string; + startI: number; + stopI: number; + /** * Returns nodes on the stack item's node stack as an Mml node. I.e., in case * the item contains more than one node, it creates an mrow. @@ -97,6 +101,22 @@ export interface NodeStack { export abstract class MmlStack implements NodeStack { + + /** + * @override + */ + public startStr: string = ''; + + /** + * @override + */ + public startI: number = 0; + + /** + * @override + */ + public stopI: number = 0; + /** * @constructor * @extends {NodeStack} diff --git a/ts/input/tex/TexParser.ts b/ts/input/tex/TexParser.ts index fc42b2615..5c1ba2ed1 100644 --- a/ts/input/tex/TexParser.ts +++ b/ts/input/tex/TexParser.ts @@ -32,7 +32,7 @@ import TexError from './TexError.js'; import {MmlNode, AbstractMmlNode} from '../../core/MmlTree/MmlNode.js'; import {ParseInput, ParseResult} from './Types.js'; import ParseOptions from './ParseOptions.js'; -import {StackItem, EnvList} from './StackItem.js'; +import {BaseItem, StackItem, EnvList} from './StackItem.js'; import {Symbol} from './Symbol.js'; import {OptionList} from '../../util/Options.js'; @@ -66,6 +66,8 @@ export default class TexParser { */ public currentCS: string = ''; + public saveI: number[] = [0]; + /** * @constructor * @param {string} string The string to parse. @@ -136,24 +138,23 @@ export default class TexParser { * @return {ParseResult} The output of the parsing function. */ public parse(kind: HandlerType, input: ParseInput): ParseResult { - let oldI = this.i; + this.saveI.push(this.i); let result = this.configuration.handlers.get(kind).parse(input); - this.updateResult(input[1], oldI); + this.updateResult(input[1], this.saveI.pop()); return result; } // Currently works without environments. private updateResult(input: string, old: number) { - // console.log('Updating'); let node = this.stack.Prev(true) as MmlNode; if (!node) { return; } - let existing = node.attributes.get('latex'); - // if (node.attributes.get('latex') === '{}') { - // console.log('{}'); - // console.log(node); - // } + let existing = node.attributes.get('itemLatex'); + if (existing !== undefined) { + node.attributes.set('latex', existing); + return; + } let str = old !== this.i ? this.string.slice(old, this.i) : input; str = str.trim(); if (!str) { @@ -162,13 +163,6 @@ export default class TexParser { if (input === '\\') { str = '\\' + str; } - if (str === '}') { - str = this.bracing(this.composeBraces(node)); - } - // if (node.kind === 'msubsup' || node.kind === 'moverunder') { - // this.updateChild(node.childNodes[1]); - // this.updateChild(node.childNodes[2]); - // } node.attributes.set('latex', str); } @@ -181,13 +175,13 @@ export default class TexParser { let str = this.composeBraces(atom); atom.attributes.set('latex', this.bracing(str)); } - + private composeBraces(atom: MmlNode) { // TODO: Make this more secure! let children = atom.childNodes[0].childNodes; return children.map(x => x.attributes?.get('latex') || '').join(' '); } - + /** * Maps a symbol to its "parse value" if it exists. @@ -245,10 +239,12 @@ export default class TexParser { * @param {StackItem|MmlNode} arg The new item. */ public Push(arg: StackItem | MmlNode) { - console.log(this.string); - console.log(this.i); - console.log(arg); - console.log(this.stack.Prev(true)); + if (arg instanceof BaseItem) { + arg.startI = this.saveI.pop(); + this.saveI.push(arg.startI); + arg.stopI = this.i; + arg.startStr = this.string; + } if (arg instanceof AbstractMmlNode && arg.isInferred) { this.PushAll(arg.childNodes); } else { @@ -277,6 +273,7 @@ export default class TexParser { } let node = this.stack.Top().First; this.configuration.popParser(); + node.attributes.set('latex', this.string); return node; } diff --git a/ts/input/tex/base/BaseItems.ts b/ts/input/tex/base/BaseItems.ts index 06af18b2a..50f089b4b 100644 --- a/ts/input/tex/base/BaseItems.ts +++ b/ts/input/tex/base/BaseItems.ts @@ -145,6 +145,7 @@ export class OpenItem extends BaseItem { // @test PrimeSup let mml = this.toMml(); const node = this.create('node', 'TeXAtom', [mml]); + addLatexItem(node, item); return [[this.factory.create('mml', node)], true]; } return super.checkItem(item); @@ -390,10 +391,16 @@ export class LeftItem extends BaseItem { // // Create the fenced structure as an mrow // - return [[this.factory.create('mml', ParseUtil.fenced( + let fenced = ParseUtil.fenced( this.factory.configuration, this.getProperty('delim') as string, this.toMml(), - item.getProperty('delim') as string, '', item.getProperty('color') as string))], true]; + item.getProperty('delim') as string, '', item.getProperty('color') as string); + let left = fenced.childNodes[0]; + let right = fenced.childNodes[fenced.childNodes.length - 1]; + // TODO: Do we really want to have the left/right/middle prefixes here? + addLatexItem(left, this, '\\left'); + addLatexItem(right, item, '\\right'); + return [[this.factory.create('mml', fenced)], true]; } if (item.isKind('middle')) { // @@ -403,9 +410,11 @@ export class LeftItem extends BaseItem { if (item.getProperty('color')) { def.mathcolor = item.getProperty('color'); } + let middle = this.create('token', 'mo', def, item.getProperty('delim')); + addLatexItem(middle, item, '\\middle'); this.Push( this.create('node', 'TeXAtom', [], {texClass: TEXCLASS.CLOSE}), - this.create('token', 'mo', def, item.getProperty('delim')), + middle, this.create('node', 'TeXAtom', [], {texClass: TEXCLASS.OPEN}) ); this.env = {}; // Since \middle closes the group, clear the environment @@ -561,7 +570,9 @@ export class BeginItem extends BaseItem { } if (!this.getProperty('end')) { // @test Hfill - return [[this.factory.create('mml', this.toMml())], true]; + const node = this.toMml(); + addLatexItem(node, item); + return [[this.factory.create('mml', node)], true]; } return BaseItem.fail; // TODO: This case could probably go! } @@ -1249,6 +1260,7 @@ export class ArrayItem extends BaseItem { // @test Array1, Array2 node = this.create('node', 'mtr', this.row); } + addLatexItem(node, this); this.table.push(node); this.row = []; this.atEnd = false; @@ -1529,3 +1541,12 @@ export class EquationItem extends BaseItem { } } + + +function addLatexItem(node: MmlNode, item: StackItem, prefix: string = '') { + let str = item.startStr.slice(item.startI, item.stopI); + if (str) { + node.attributes.set('itemLatex', prefix ? prefix + str : str); + node.attributes.set('latex', prefix ? prefix + str : str); + } +} From a5bc9ae7b1c73f731e41925f5887fef0d7d76493 Mon Sep 17 00:00:00 2001 From: zorkow Date: Thu, 1 Dec 2022 18:36:40 +0100 Subject: [PATCH 14/47] Additional nesting. --- ts/input/tex/TexParser.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ts/input/tex/TexParser.ts b/ts/input/tex/TexParser.ts index 5c1ba2ed1..1ffb3e581 100644 --- a/ts/input/tex/TexParser.ts +++ b/ts/input/tex/TexParser.ts @@ -160,6 +160,10 @@ export default class TexParser { if (!str) { return; } + if (str === '}') { + this.updateChild(node, 0); + return; + } if (input === '\\') { str = '\\' + str; } @@ -170,15 +174,15 @@ export default class TexParser { return '{' + str + '}'; } - private updateChild(atom: MmlNode) { + private updateChild(atom: MmlNode, pos: number) { if (!atom) return; - let str = this.composeBraces(atom); + let str = this.composeBraces(atom, pos); atom.attributes.set('latex', this.bracing(str)); } - private composeBraces(atom: MmlNode) { + private composeBraces(atom: MmlNode, pos: number) { // TODO: Make this more secure! - let children = atom.childNodes[0].childNodes; + let children = atom.childNodes[pos].childNodes; return children.map(x => x.attributes?.get('latex') || '').join(' '); } From f44e3de6515b2c7406f3c995f3576be4cad8dbcf Mon Sep 17 00:00:00 2001 From: zorkow Date: Fri, 16 Dec 2022 00:59:31 +0100 Subject: [PATCH 15/47] Sub sup cases mostly working. --- ts/input/tex/TexParser.ts | 52 +++++++++++++++++++++++++++++++--- ts/input/tex/base/BaseItems.ts | 12 +++++++- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/ts/input/tex/TexParser.ts b/ts/input/tex/TexParser.ts index 1ffb3e581..727dfe438 100644 --- a/ts/input/tex/TexParser.ts +++ b/ts/input/tex/TexParser.ts @@ -160,20 +160,54 @@ export default class TexParser { if (!str) { return; } + if (input === '\\') { + str = '\\' + str; + } + // TODO: Simplify. Add case for sub/sup sup/sub by checking number of children. + if (node.attributes.get('latex') === '^' && str !== '^') { + // console.log(53); + // console.log(node.childNodes.length); + node.childNodes.forEach(x => console.log(x)); + if (str === '}') { + this.updateChild(node.childNodes[2], 0); + this.composeLatex(node, '^', 0, 2); + return; + } + node.childNodes[2].attributes.set('latex', str); + this.composeLatex(node, '^', 0, 2); + return; + } + if (node.attributes.get('latex') === '_' && str !== '_') { + // console.log(54); + // console.log(node.childNodes.length); + node.childNodes.forEach(x => console.log(x)); + if (str === '}') { + this.updateChild(node.childNodes[1], 0); + this.composeLatex(node, '_', 0, 1); + return; + } + node.childNodes[1].attributes.set('latex', str); + this.composeLatex(node, '_', 0, 1); + return; + } if (str === '}') { this.updateChild(node, 0); return; } - if (input === '\\') { - str = '\\' + str; - } node.attributes.set('latex', str); } + private composeLatex(node: MmlNode, comp: string, pos1: number, pos2: number) { + const expr = node.childNodes[pos1].attributes.get('latex') + comp + + node.childNodes[pos2].attributes.get('latex'); + node.attributes.set('latex', expr); + } + private bracing(str: string) { return '{' + str + '}'; } + // TODO: Do we need this position for anything? private updateChild(atom: MmlNode, pos: number) { if (!atom) return; let str = this.composeBraces(atom, pos); @@ -183,7 +217,17 @@ export default class TexParser { private composeBraces(atom: MmlNode, pos: number) { // TODO: Make this more secure! let children = atom.childNodes[pos].childNodes; - return children.map(x => x.attributes?.get('latex') || '').join(' '); + // console.log('Braces'); + // console.log(atom.childNodes[pos]); + // children.map(x => console.log(x.attributes?.get('latex'))); + // TODO: Put those spaces in only if necessary! + let expr = ''; + for (const child of children) { + let att = (child.attributes?.get('latex') || '') as string; + if (!att) continue; + expr += (expr && expr.match(/[a-zA-Z]$/) && att.match(/^[a-zA-Z]/)) ? ' ' + att : att; + } + return expr; } diff --git a/ts/input/tex/base/BaseItems.ts b/ts/input/tex/base/BaseItems.ts index 50f089b4b..089f19d0d 100644 --- a/ts/input/tex/base/BaseItems.ts +++ b/ts/input/tex/base/BaseItems.ts @@ -253,12 +253,19 @@ export class SubsupItem extends BaseItem { item.First = node; } } + // console.log(JSON.stringify(item.First.attributes)); + // let first = item.First; NodeUtil.setChild(top, position, item.First); if (this.getProperty('movesupsub') != null) { // @test Limits Subsup (currently does not work! Check again!) NodeUtil.setProperty(top, 'movesupsub', this.getProperty('movesupsub') as Property); } const result = this.factory.create('mml', top); + // console.log(38); + // console.log(top.attributes.get('latex')); + // console.log(first.attributes.get('latex')); + // console.log(first); + // top.attributes.set('latexItem', top.attributes.get('latex') as string); return [[result], true]; } if (super.checkItem(item)[1]) { @@ -397,10 +404,13 @@ export class LeftItem extends BaseItem { item.getProperty('delim') as string, '', item.getProperty('color') as string); let left = fenced.childNodes[0]; let right = fenced.childNodes[fenced.childNodes.length - 1]; + let mrow = this.factory.create('mml', fenced); // TODO: Do we really want to have the left/right/middle prefixes here? addLatexItem(left, this, '\\left'); addLatexItem(right, item, '\\right'); - return [[this.factory.create('mml', fenced)], true]; + mrow.Peek()[0].attributes.set( + 'itemLatex', '\\left' + item.startStr.slice(this.startI, item.stopI)); + return [[mrow], true]; } if (item.isKind('middle')) { // From 4002833bca2f3aaed98aafe08da7d7e8a67b170a Mon Sep 17 00:00:00 2001 From: zorkow Date: Tue, 20 Dec 2022 01:20:25 +0100 Subject: [PATCH 16/47] Adds the final subsup case. --- ts/input/tex/TexParser.ts | 48 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/ts/input/tex/TexParser.ts b/ts/input/tex/TexParser.ts index 727dfe438..318f94f45 100644 --- a/ts/input/tex/TexParser.ts +++ b/ts/input/tex/TexParser.ts @@ -163,35 +163,38 @@ export default class TexParser { if (input === '\\') { str = '\\' + str; } - // TODO: Simplify. Add case for sub/sup sup/sub by checking number of children. + // TODO: Simplify. + // These are the cases to handle sub and superscripts. if (node.attributes.get('latex') === '^' && str !== '^') { - // console.log(53); - // console.log(node.childNodes.length); - node.childNodes.forEach(x => console.log(x)); if (str === '}') { - this.updateChild(node.childNodes[2], 0); + this.updateChild(node.childNodes[2]); + } else { + node.childNodes[2].attributes.set('latex', str); + } + if (node.childNodes[1]) { + const sub = node.childNodes[1].attributes.get('latex'); + this.composeLatex(node, `_${sub}^`, 0, 2); + } else { this.composeLatex(node, '^', 0, 2); - return; } - node.childNodes[2].attributes.set('latex', str); - this.composeLatex(node, '^', 0, 2); return; } if (node.attributes.get('latex') === '_' && str !== '_') { - // console.log(54); - // console.log(node.childNodes.length); - node.childNodes.forEach(x => console.log(x)); if (str === '}') { - this.updateChild(node.childNodes[1], 0); + this.updateChild(node.childNodes[1]); + } else { + node.childNodes[1].attributes.set('latex', str); + } + if (node.childNodes[2]) { + const sub = node.childNodes[2].attributes.get('latex'); + this.composeLatex(node, `^${sub}_`, 0, 1); + } else { this.composeLatex(node, '_', 0, 1); - return; } - node.childNodes[1].attributes.set('latex', str); - this.composeLatex(node, '_', 0, 1); return; } if (str === '}') { - this.updateChild(node, 0); + this.updateChild(node); return; } node.attributes.set('latex', str); @@ -207,20 +210,15 @@ export default class TexParser { return '{' + str + '}'; } - // TODO: Do we need this position for anything? - private updateChild(atom: MmlNode, pos: number) { + private updateChild(atom: MmlNode) { if (!atom) return; - let str = this.composeBraces(atom, pos); + let str = this.composeBraces(atom); atom.attributes.set('latex', this.bracing(str)); } - private composeBraces(atom: MmlNode, pos: number) { + private composeBraces(atom: MmlNode) { // TODO: Make this more secure! - let children = atom.childNodes[pos].childNodes; - // console.log('Braces'); - // console.log(atom.childNodes[pos]); - // children.map(x => console.log(x.attributes?.get('latex'))); - // TODO: Put those spaces in only if necessary! + let children = atom.childNodes[0].childNodes; let expr = ''; for (const child of children) { let att = (child.attributes?.get('latex') || '') as string; From f0c1d57b07def57b64fd963b4b099e8d3c2ce2c4 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sat, 24 Dec 2022 17:18:29 +0100 Subject: [PATCH 17/47] Initial attempt at fixing over items. --- ts/input/tex/base/BaseItems.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/input/tex/base/BaseItems.ts b/ts/input/tex/base/BaseItems.ts index 089f19d0d..5dafb5f75 100644 --- a/ts/input/tex/base/BaseItems.ts +++ b/ts/input/tex/base/BaseItems.ts @@ -333,6 +333,7 @@ export class OverItem extends BaseItem { this.getProperty('open') as string, mml, this.getProperty('close') as string); } + mml.attributes.set('itemLatex', this.getProperty('name') as string); return [[this.factory.create('mml', mml), item], true]; } return super.checkItem(item); From fed09f8f0b2ccfefe172f11db49da65564d847ec Mon Sep 17 00:00:00 2001 From: zorkow Date: Thu, 23 Feb 2023 14:56:33 +0100 Subject: [PATCH 18/47] Changes Braille to Euro for the time being. --- components/src/a11y/util.js | 4 ++-- ts/input/tex/base/BaseItems.ts | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/components/src/a11y/util.js b/components/src/a11y/util.js index 99edbf8d9..be900e40d 100644 --- a/components/src/a11y/util.js +++ b/components/src/a11y/util.js @@ -6,7 +6,7 @@ import '../a11y/explorer/explorer.js'; import MathMaps from '../../../js/a11y/mathmaps.js'; import base from 'speech-rule-engine/lib/mathmaps/base.json'; import en from 'speech-rule-engine/lib/mathmaps/en.json'; -import nemeth from 'speech-rule-engine/lib/mathmaps/nemeth.json'; +import euro from 'speech-rule-engine/lib/mathmaps/euro.json'; Loader.preLoad( 'a11y/sre', @@ -16,7 +16,7 @@ Loader.preLoad( MathMaps.set('base', base); MathMaps.set('en', en); -MathMaps.set('nemeth', nemeth); +MathMaps.set('euro', euro); export function checkSre(startup) { return () => startup(() => Sre.sreReady()); diff --git a/ts/input/tex/base/BaseItems.ts b/ts/input/tex/base/BaseItems.ts index 5dafb5f75..2bba6e62b 100644 --- a/ts/input/tex/base/BaseItems.ts +++ b/ts/input/tex/base/BaseItems.ts @@ -261,11 +261,6 @@ export class SubsupItem extends BaseItem { NodeUtil.setProperty(top, 'movesupsub', this.getProperty('movesupsub') as Property); } const result = this.factory.create('mml', top); - // console.log(38); - // console.log(top.attributes.get('latex')); - // console.log(first.attributes.get('latex')); - // console.log(first); - // top.attributes.set('latexItem', top.attributes.get('latex') as string); return [[result], true]; } if (super.checkItem(item)[1]) { From 3478ad8dc78affc332882dba3d033a898386e2f1 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sun, 18 Jun 2023 20:44:38 -0400 Subject: [PATCH 19/47] Reinstates euro as primary braille. --- components/mjs/a11y/sre/sre.js | 2 -- components/mjs/a11y/util-pack.js | 4 ++-- components/mjs/a11y/util.js | 4 ++-- ts/a11y/SpeechMenu.ts | 2 +- ts/a11y/explorer/ExplorerPool.ts | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/components/mjs/a11y/sre/sre.js b/components/mjs/a11y/sre/sre.js index 8d2274a27..163973bc6 100644 --- a/components/mjs/a11y/sre/sre.js +++ b/components/mjs/a11y/sre/sre.js @@ -4,8 +4,6 @@ import {Sre} from '#js/a11y/sre.js'; export {Sre}; -export {Sre}; - if (MathJax.startup) { ((typeof window !== 'undefined') ? window : global). SREfeature.custom = (loc) => Sre.preloadLocales(loc); diff --git a/components/mjs/a11y/util-pack.js b/components/mjs/a11y/util-pack.js index c226708c5..288e09bbe 100644 --- a/components/mjs/a11y/util-pack.js +++ b/components/mjs/a11y/util-pack.js @@ -6,7 +6,7 @@ import './explorer/explorer.js'; import {MathMaps} from '#js/a11y/mathmaps.js'; import base from 'speech-rule-engine/lib/mathmaps/base.json' assert { type: 'json' }; import en from 'speech-rule-engine/lib/mathmaps/en.json' assert {type: 'json' }; -import nemeth from 'speech-rule-engine/lib/mathmaps/nemeth.json' assert {type: 'json' }; +import euro from 'speech-rule-engine/lib/mathmaps/euro.json' assert {type: 'json' }; Loader.preLoad( 'a11y/sre', @@ -16,7 +16,7 @@ Loader.preLoad( MathMaps.set('base', base); MathMaps.set('en', en); -MathMaps.set('nemeth', nemeth); +MathMaps.set('euro', euro); export function checkSre(startup) { return () => startup(() => Sre.sreReady()); diff --git a/components/mjs/a11y/util.js b/components/mjs/a11y/util.js index c9f1de1de..830af5e73 100644 --- a/components/mjs/a11y/util.js +++ b/components/mjs/a11y/util.js @@ -7,7 +7,7 @@ import {MathMaps} from '#js/a11y/mathmaps.js'; const base = require('speech-rule-engine/lib/mathmaps/base.json'); const en = require('speech-rule-engine/lib/mathmaps/en.json'); -const nemeth = require('speech-rule-engine/lib/mathmaps/nemeth.json'); +const euro = require('speech-rule-engine/lib/mathmaps/euro.json'); Loader.preLoad( 'a11y/sre', @@ -17,7 +17,7 @@ Loader.preLoad( MathMaps.set('base', base); MathMaps.set('en', en); -MathMaps.set('nemeth', nemeth); +MathMaps.set('euro', euro); export function checkSre(startup) { return () => startup(() => Sre.sreReady()); diff --git a/ts/a11y/SpeechMenu.ts b/ts/a11y/SpeechMenu.ts index 0dfe9db18..c4e059d7b 100644 --- a/ts/a11y/SpeechMenu.ts +++ b/ts/a11y/SpeechMenu.ts @@ -204,7 +204,7 @@ export function localeMenu(menu: MJContextMenu, sub: Submenu) { let radios: {type: string, id: string, content: string, variable: string}[] = []; for (let lang of Sre.locales.keys()) { - if (lang === 'nemeth') continue; + if ((lang === 'euro') || (lang === 'nemeth')) continue; radios.push({type: 'radio', id: lang, content: Sre.locales.get(lang) || lang, variable: 'locale'}); } diff --git a/ts/a11y/explorer/ExplorerPool.ts b/ts/a11y/explorer/ExplorerPool.ts index 516870ca8..67ea89cc6 100644 --- a/ts/a11y/explorer/ExplorerPool.ts +++ b/ts/a11y/explorer/ExplorerPool.ts @@ -107,7 +107,7 @@ let allExplorers: {[options: string]: ExplorerInit} = { let explorer = ke.SpeechExplorer.create( doc, pool, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer; explorer.speechGenerator.setOptions({automark: false as any, markup: 'none', - locale: 'nemeth', domain: 'default', + locale: 'euro', domain: 'default', style: 'default', modality: 'braille'}); explorer.showRegion = 'viewBraille'; return explorer; From ec1d0c71509415f475de44f1e4392b64752272f7 Mon Sep 17 00:00:00 2001 From: zorkow Date: Fri, 23 Jun 2023 12:11:21 +0200 Subject: [PATCH 20/47] Some new explorer experiments. --- ts/a11y/explorer.ts | 3 +- ts/a11y/explorer/ExplorerPool.ts | 18 ++--- ts/a11y/explorer/KeyExplorer.ts | 70 ++++++++++++------- ts/a11y/explorer/Region.ts | 2 +- ts/a11y/explorer/Walker.ts | 114 +++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 34 deletions(-) create mode 100644 ts/a11y/explorer/Walker.ts diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts index c606069ba..6a2466b52 100644 --- a/ts/a11y/explorer.ts +++ b/ts/a11y/explorer.ts @@ -199,7 +199,8 @@ export function ExplorerMathDocumentMixin { - let explorer = ke.SpeechExplorer.create( - doc, pool, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer; - explorer.speechGenerator.setOptions({automark: false as any, markup: 'none', - locale: 'nemeth', domain: 'default', - style: 'default', modality: 'braille'}); - explorer.showRegion = 'viewBraille'; - return explorer; - }, + // braille: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => { + // let explorer = ke.SpeechExplorer.create( + // doc, pool, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer; + // explorer.speechGenerator.setOptions({automark: false as any, markup: 'none', + // locale: 'nemeth', domain: 'default', + // style: 'default', modality: 'braille'}); + // explorer.showRegion = 'viewBraille'; + // return explorer; + // }, keyMagnifier: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => ke.Magnifier.create(doc, pool, doc.explorerRegions.magnifier, node, ...rest), mouseMagnifier: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ..._rest: any[]) => diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 6d2df3f46..2c9b70830 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -28,6 +28,8 @@ import {Explorer, AbstractExplorer} from './Explorer.js'; import {ExplorerPool} from './ExplorerPool.js'; import {Sre} from '../sre.js'; +import {click, move} from './Walker.js'; + /** * Interface for keyboard explorers. Adds the necessary keyboard events. @@ -99,9 +101,13 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme */ protected events: [string, (x: Event) => void][] = super.Events().concat( - [['keydown', this.KeyDown.bind(this)], - ['focusin', this.FocusIn.bind(this)], - ['focusout', this.FocusOut.bind(this)]]); + [ + // ['keydown', move], + ['keydown', this.KeyDown.bind(this)], + ['click', ((e: Event) => click(this.node, e)).bind(this)], + ['focusin', this.FocusIn.bind(this)], + ['focusout', this.FocusOut.bind(this)] + ]); /** * The original tabindex value before explorer was attached. @@ -148,8 +154,10 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme super.Attach(); this.attached = true; this.oldIndex = this.node.tabIndex; - this.node.tabIndex = 1; - this.node.setAttribute('role', 'tree'); + this.node.tabIndex = 0; + this.node.setAttribute('role', 'application'); + // TODO: Get rid of this eventually! + this.node.setAttribute('data-shellac', ''); } /** @@ -189,7 +197,8 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme * @override */ public Move(key: number) { - let result = this.walker.move(key); + // let result = this.walker.move(key); + let result = move(key); if (result) { this.Update(); return; @@ -263,6 +272,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public Start() { + console.log(16); if (!this.attached) return; let options = this.getOptions(); if (!this.init) { @@ -274,6 +284,15 @@ export class SpeechExplorer extends AbstractKeyExplorer { // Important that both are in the same block so speech explorers // are restarted sequentially. this.Speech(this.walker); + }) + .then(() => Sre.setupEngine({automark: false as any, markup: 'none', + locale: 'nemeth', domain: 'default', + style: 'default', modality: 'braille'})) + .then(() => { + this.speechGenerator.setOptions({automark: false as any, markup: 'none', + locale: 'nemeth', domain: 'default', + style: 'default', modality: 'braille'}); + this.Speech(this.walker); this.Start(); }); }) @@ -281,17 +300,17 @@ export class SpeechExplorer extends AbstractKeyExplorer { return; } super.Start(); - this.speechGenerator = Sre.getSpeechGenerator('Direct'); - this.speechGenerator.setOptions(options); - this.walker = Sre.getWalker( - 'table', this.node, this.speechGenerator, this.highlighter, this.mml); - this.walker.activate(); - this.Update(); - if (this.document.options.a11y[this.showRegion]) { - SpeechExplorer.updatePromise.then( - () => this.region.Show(this.node, this.highlighter)); - } - this.restarted = true; + // this.speecGhenerator = Sre.getSpeechGenerator('Direct'); + // this.speechGenerator.setOptions(options); + // this.walker = Sre.getWalker( + // 'table', this.node, this.speechGenerator, this.highlighter, this.mml); + // this.walker.activate(); + // this.Update(); + // if (this.document.options.a11y[this.showRegion]) { + // SpeechExplorer.updatePromise.then( + // () => this.region.Show(this.node, this.highlighter)); + // } + // this.restarted = true; } @@ -349,6 +368,10 @@ export class SpeechExplorer extends AbstractKeyExplorer { */ public KeyDown(event: KeyboardEvent) { const code = event.keyCode; + console.log(event); + console.log(event.key); + console.log(event.code); + console.log(code); this.walker.modifier = event.shiftKey; if (code === 17) { speechSynthesis.cancel(); @@ -359,12 +382,11 @@ export class SpeechExplorer extends AbstractKeyExplorer { this.stopEvent(event); return; } - if (this.active) { - this.Move(code); - if (this.triggerLink(code)) return; - this.stopEvent(event); - return; - } + console.log(9); + move(event); + console.log(13); + if (this.triggerLink(code)) return; + this.stopEvent(event); if (code === 32 && event.shiftKey || code === 13) { this.Start(); this.stopEvent(event); @@ -376,6 +398,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @param {number} code The keycode of the last key pressed. */ protected triggerLink(code: number) { + console.log(15); if (code !== 13) { return false; } @@ -384,6 +407,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { getAttribute('data-semantic-postfix')?. match(/(^| )link($| )/); if (focus) { + console.log(14); node.parentNode.dispatchEvent(new MouseEvent('click')); return true; } diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index cf351c93a..26fd99b0f 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -361,7 +361,7 @@ export class LiveRegion extends StringRegion { */ constructor(public document: A11yDocument) { super(document); - this.div.setAttribute('aria-live', 'assertive'); + // this.div.setAttribute('aria-live', 'assertive'); } } diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts new file mode 100644 index 000000000..9ea4c8047 --- /dev/null +++ b/ts/a11y/explorer/Walker.ts @@ -0,0 +1,114 @@ +/************************************************************* + * + * Copyright (c) 2009-2023 The MathJax Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** + * @fileoverview Aria Tree Walker. + * + * @author v.sorge@mathjax.org (Volker Sorge) + */ + +// Based on the shellac walker. + +const codeSelector = 'mjx-container[role="application"][data-shellac]'; +const nav = '[role="application"][data-shellac],[role="tree"],[role="group"],[role="treeitem"]'; + +function isCodeBlock(el) { + return el.matches(codeSelector); +} + +export function click(snippet, e) { + const clicked = e.target.closest(nav); + if (snippet.contains(clicked)) { + const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); + if (prev) { + prev.removeAttribute("tabindex"); + } + clicked.setAttribute("tabindex", "0"); + clicked.focus(); + e.preventDefault(); + } +} + +export function move(e) { + + function nextFocus() { + function nextSibling(el) { + const sib = el.nextElementSibling; + if (sib) { + if (sib.matches(nav)) { + return sib; + } else { + const sibChild = sib.querySelector(nav); + return sibChild ?? nextSibling(sib); + } + } else { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return nextSibling(el.parentElement); + } else { + return null; + } + } + } + + function prevSibling(el) { + const sib = el.previousElementSibling; + if (sib) { + if (sib.matches(nav)) { + return sib; + } else { + const sibChild = sib.querySelector(nav); + return sibChild ?? prevSibling(sib); + } + } else { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return prevSibling(el.parentElement); + } else { + return null; + } + } + } + + switch (event.key) { + case "ArrowDown": + e.preventDefault(); + return e.target.querySelector(nav); + case "ArrowUp": + e.preventDefault(); + return e.target.parentElement.closest(nav); + case "ArrowLeft": + e.preventDefault(); + return prevSibling(e.target); + case "ArrowRight": + e.preventDefault(); + return nextSibling(e.target); + default: + return; + } + } + + const next = nextFocus(); + + + if (next) { + e.target.removeAttribute("tabindex"); + next.setAttribute("tabindex", "0"); + next.focus(); + return true; + } + return false; +} From 229121f62dc5ede163bed77facc7b4aed66a9234 Mon Sep 17 00:00:00 2001 From: zorkow Date: Mon, 17 Jul 2023 13:12:05 +0200 Subject: [PATCH 21/47] Updates chtml focus outline. --- ts/output/chtml.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/output/chtml.ts b/ts/output/chtml.ts index 235f17cac..3bf8f1a4b 100644 --- a/ts/output/chtml.ts +++ b/ts/output/chtml.ts @@ -85,6 +85,7 @@ CommonOutputJax< 'white-space': 'nowrap' }, + 'mjx-container[jax="CHTML"] :focus': {'outline': 'solid 3px'}, 'mjx-container [space="1"]': {'margin-left': '.111em'}, 'mjx-container [space="2"]': {'margin-left': '.167em'}, 'mjx-container [space="3"]': {'margin-left': '.222em'}, From 044e7acbcfa3ab7597d320e32a31f02b15845869 Mon Sep 17 00:00:00 2001 From: zorkow Date: Mon, 17 Jul 2023 13:24:52 +0200 Subject: [PATCH 22/47] Adds corrected types for Walker. --- ts/a11y/explorer/KeyExplorer.ts | 38 +++++++++++++------------------ ts/a11y/explorer/Walker.ts | 40 ++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 2c9b70830..3b6c80a16 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -104,7 +104,7 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme [ // ['keydown', move], ['keydown', this.KeyDown.bind(this)], - ['click', ((e: Event) => click(this.node, e)).bind(this)], + ['click', ((e: MouseEvent) => click(this.node, e)).bind(this)], ['focusin', this.FocusIn.bind(this)], ['focusout', this.FocusOut.bind(this)] ]); @@ -196,16 +196,10 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme /** * @override */ - public Move(key: number) { - // let result = this.walker.move(key); - let result = move(key); - if (result) { - this.Update(); - return; - } - if (this.sound) { - this.NoMove(); - } + public Move(_key: number) { + // // let result = this.walker.move(key); + // let result = false; + // // let result = move(key); } /** @@ -272,7 +266,6 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public Start() { - console.log(16); if (!this.attached) return; let options = this.getOptions(); if (!this.init) { @@ -296,7 +289,6 @@ export class SpeechExplorer extends AbstractKeyExplorer { this.Start(); }); }) - .catch((error: Error) => console.log(error.message)); return; } super.Start(); @@ -368,10 +360,6 @@ export class SpeechExplorer extends AbstractKeyExplorer { */ public KeyDown(event: KeyboardEvent) { const code = event.keyCode; - console.log(event); - console.log(event.key); - console.log(event.code); - console.log(code); this.walker.modifier = event.shiftKey; if (code === 17) { speechSynthesis.cancel(); @@ -382,9 +370,17 @@ export class SpeechExplorer extends AbstractKeyExplorer { this.stopEvent(event); return; } - console.log(9); - move(event); - console.log(13); + // + let result = move(event); + if (result) { + this.region.Show(this.node, this.highlighter); + this.region.Update('hello'); + return; + } + if (this.sound) { + this.NoMove(); + } + // if (this.triggerLink(code)) return; this.stopEvent(event); if (code === 32 && event.shiftKey || code === 13) { @@ -398,7 +394,6 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @param {number} code The keycode of the last key pressed. */ protected triggerLink(code: number) { - console.log(15); if (code !== 13) { return false; } @@ -407,7 +402,6 @@ export class SpeechExplorer extends AbstractKeyExplorer { getAttribute('data-semantic-postfix')?. match(/(^| )link($| )/); if (focus) { - console.log(14); node.parentNode.dispatchEvent(new MouseEvent('click')); return true; } diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts index 9ea4c8047..1a54251f1 100644 --- a/ts/a11y/explorer/Walker.ts +++ b/ts/a11y/explorer/Walker.ts @@ -27,12 +27,12 @@ const codeSelector = 'mjx-container[role="application"][data-shellac]'; const nav = '[role="application"][data-shellac],[role="tree"],[role="group"],[role="treeitem"]'; -function isCodeBlock(el) { +function isCodeBlock(el: HTMLElement) { return el.matches(codeSelector); } -export function click(snippet, e) { - const clicked = e.target.closest(nav); +export function click(snippet: HTMLElement, e: MouseEvent) { + const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; if (snippet.contains(clicked)) { const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); if (prev) { @@ -44,16 +44,16 @@ export function click(snippet, e) { } } -export function move(e) { +export function move(e: KeyboardEvent) { - function nextFocus() { - function nextSibling(el) { - const sib = el.nextElementSibling; + function nextFocus(): HTMLElement { + function nextSibling(el: HTMLElement): HTMLElement { + const sib = el.nextElementSibling as HTMLElement; if (sib) { if (sib.matches(nav)) { return sib; } else { - const sibChild = sib.querySelector(nav); + const sibChild = sib.querySelector(nav) as HTMLElement; return sibChild ?? nextSibling(sib); } } else { @@ -65,13 +65,13 @@ export function move(e) { } } - function prevSibling(el) { - const sib = el.previousElementSibling; + function prevSibling(el: HTMLElement): HTMLElement { + const sib = el.previousElementSibling as HTMLElement; if (sib) { if (sib.matches(nav)) { return sib; } else { - const sibChild = sib.querySelector(nav); + const sibChild = sib.querySelector(nav) as HTMLElement; return sibChild ?? prevSibling(sib); } } else { @@ -83,31 +83,35 @@ export function move(e) { } } - switch (event.key) { + const target = e.target as HTMLElement; + switch (e.key) { case "ArrowDown": e.preventDefault(); - return e.target.querySelector(nav); + return target.querySelector(nav); case "ArrowUp": e.preventDefault(); - return e.target.parentElement.closest(nav); + return target.parentElement.closest(nav); case "ArrowLeft": e.preventDefault(); - return prevSibling(e.target); + return prevSibling(target); case "ArrowRight": e.preventDefault(); - return nextSibling(e.target); + return nextSibling(target); default: - return; + return null; } } const next = nextFocus(); + const target = e.target as HTMLElement; if (next) { - e.target.removeAttribute("tabindex"); + target.removeAttribute("tabindex"); next.setAttribute("tabindex", "0"); next.focus(); + console.log(next.getAttribute('data-semantic-speech')); + console.log(next.getAttribute('data-semantic-braille')); return true; } return false; From e1dc71be5c42c5e089619d7c7adb2db648dcce1a Mon Sep 17 00:00:00 2001 From: zorkow Date: Tue, 25 Jul 2023 22:33:15 +0200 Subject: [PATCH 23/47] Walker cleanup. --- ts/a11y/explorer/Walker.ts | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts index 1a54251f1..5ebda8fb7 100644 --- a/ts/a11y/explorer/Walker.ts +++ b/ts/a11y/explorer/Walker.ts @@ -36,65 +36,65 @@ export function click(snippet: HTMLElement, e: MouseEvent) { if (snippet.contains(clicked)) { const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); if (prev) { - prev.removeAttribute("tabindex"); + prev.removeAttribute('tabindex'); } - clicked.setAttribute("tabindex", "0"); + clicked.setAttribute('tabindex', '0'); clicked.focus(); e.preventDefault(); } } export function move(e: KeyboardEvent) { - + function nextFocus(): HTMLElement { function nextSibling(el: HTMLElement): HTMLElement { const sib = el.nextElementSibling as HTMLElement; if (sib) { - if (sib.matches(nav)) { + if (sib.matches(nav)) { return sib; - } else { + } else { const sibChild = sib.querySelector(nav) as HTMLElement; return sibChild ?? nextSibling(sib); - } + } } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { return nextSibling(el.parentElement); - } else { + } else { return null; - } + } } } function prevSibling(el: HTMLElement): HTMLElement { const sib = el.previousElementSibling as HTMLElement; if (sib) { - if (sib.matches(nav)) { + if (sib.matches(nav)) { return sib; - } else { + } else { const sibChild = sib.querySelector(nav) as HTMLElement; return sibChild ?? prevSibling(sib); - } + } } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { return prevSibling(el.parentElement); - } else { + } else { return null; - } + } } } const target = e.target as HTMLElement; switch (e.key) { - case "ArrowDown": + case 'ArrowDown': e.preventDefault(); return target.querySelector(nav); - case "ArrowUp": + case 'ArrowUp': e.preventDefault(); return target.parentElement.closest(nav); - case "ArrowLeft": + case 'ArrowLeft': e.preventDefault(); return prevSibling(target); - case "ArrowRight": + case 'ArrowRight': e.preventDefault(); return nextSibling(target); default: @@ -103,12 +103,13 @@ export function move(e: KeyboardEvent) { } const next = nextFocus(); - - + + const target = e.target as HTMLElement; + console.log(0); if (next) { - target.removeAttribute("tabindex"); - next.setAttribute("tabindex", "0"); + target.removeAttribute('tabindex'); + next.setAttribute('tabindex', '0'); next.focus(); console.log(next.getAttribute('data-semantic-speech')); console.log(next.getAttribute('data-semantic-braille')); From 448a9d5f8b9524959060e9b2650bb9a915c5e22d Mon Sep 17 00:00:00 2001 From: zorkow Date: Fri, 28 Jul 2023 23:13:13 +0200 Subject: [PATCH 24/47] Initial attempt at providing subtitles. --- ts/a11y/explorer/Walker.ts | 4 +- ts/a11y/semantic-enrich.ts | 122 ++++++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts index 5ebda8fb7..63e744182 100644 --- a/ts/a11y/explorer/Walker.ts +++ b/ts/a11y/explorer/Walker.ts @@ -111,8 +111,8 @@ export function move(e: KeyboardEvent) { target.removeAttribute('tabindex'); next.setAttribute('tabindex', '0'); next.focus(); - console.log(next.getAttribute('data-semantic-speech')); - console.log(next.getAttribute('data-semantic-braille')); + // console.log(next.getAttribute('data-semantic-speech')); + // console.log(next.getAttribute('data-semantic-braille')); return true; } return false; diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 7387b53be..810496432 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -26,6 +26,7 @@ import {Handler} from '../core/Handler.js'; import {MathDocument, AbstractMathDocument, MathDocumentConstructor} from '../core/MathDocument.js'; import {MathItem, AbstractMathItem, STATE, newState} from '../core/MathItem.js'; import {MmlNode} from '../core/MmlTree/MmlNode.js'; +import {Attributes} from '../core/MmlTree/Attributes.js'; import {MathML} from '../input/mathml.js'; import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js'; import {OptionList, expandable} from '../util/Options.js'; @@ -37,6 +38,8 @@ import {Sre} from './sre.js'; * The current speech setting for Sre */ let currentSpeech = 'none'; +let currentLocale = 'none'; +let currentBraille = 'none'; /** * Generic constructor for Mixins @@ -60,7 +63,7 @@ newState('ATTACHSPEECH', 155); export class enrichVisitor extends SerializedMmlVisitor { protected mactionId: number; - + public visitTree(node: MmlNode, math?: MathItem) { this.mactionId = 1; const mml = super.visitTree(node); @@ -134,6 +137,8 @@ export function EnrichedMathItemMixin, force: boolean = false) { if (this.state() >= STATE.ENRICHED) return; if (!this.isEscaped && (document.options.enableEnrichment || force)) { - if (document.options.sre.speech !== currentSpeech) { - currentSpeech = document.options.sre.speech; + // TODO: Sort out the loading of the locales better + // if (document.options.sre.speech !== currentSpeech) { + // currentSpeech = document.options.sre.speech; + // mathjax.retryAfter( + // Sre.setupEngine(document.options.sre).then( + // () => Sre.sreReady())); + // } + if (document.options.sre.locale !== currentLocale) { + currentLocale = document.options.sre.locale; + // TODO: Sort out the loading of the locales better mathjax.retryAfter( Sre.setupEngine(document.options.sre).then( () => Sre.sreReady())); } + if (document.options.sre.braille !== currentBraille) { + currentBraille = document.options.sre.braille; + // TODO: Sort out the loading of the locales better + mathjax.retryAfter( + Sre.setupEngine({ + locale: document.options.sre.braille, + domain: 'default', // speech rules domain + style: 'default', // speech rules style + modality: 'braille', + markup: 'none', + }) + .then(() => Sre.sreReady())); + } const math = new document.options.MathItem('', MmlJax); try { let mml; @@ -177,7 +203,20 @@ export function EnrichedMathItemMixin) { if (this.state() >= STATE.ATTACHSPEECH) return; const attributes = this.root.attributes; - const speech = (attributes.get('aria-label') || - this.getSpeech(this.root)) as string; + const speech = (attributes.get('aria-label') || this.label); + const braille = (attributes.get('aria-braillelabel') || this.braillelabel); + if (!speech && !braille) { + this.state(STATE.ATTACHSPEECH); + return; + } + const adaptor = document.adaptor; + const node = this.typesetRoot; if (speech) { - const adaptor = document.adaptor; - const node = this.typesetRoot; - adaptor.setAttribute(node, 'aria-label', speech); - for (const child of adaptor.childNodes(node) as N[]) { - adaptor.setAttribute(child, 'aria-hidden', 'true'); - } - this.outputData.speech = speech; + adaptor.setAttribute(node, 'aria-label', speech as string); + } + if (braille) { + adaptor.setAttribute(node, 'aria-braillelabel', braille as string); } + for (const child of adaptor.childNodes(node) as N[]) { + adaptor.setAttribute(child, 'aria-hidden', 'true'); + } + this.outputData.speech = speech; + this.outputData.braille = braille; this.state(STATE.ATTACHSPEECH); } @@ -249,6 +298,48 @@ export function EnrichedMathItemMixin).attachSpeech(this); From f0182f2723c6e6f37e4780173600102825b6c974 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sun, 30 Jul 2023 19:42:01 +0200 Subject: [PATCH 25/47] Refactors ssml computations to speech util. --- ts/a11y/SpeechUtil.ts | 137 ++++++++++++++++++++++++++++++ ts/a11y/explorer/KeyExplorer.ts | 6 ++ ts/a11y/explorer/Region.ts | 142 +------------------------------- ts/a11y/explorer/Walker.ts | 8 +- ts/a11y/semantic-enrich.ts | 16 ++-- 5 files changed, 162 insertions(+), 147 deletions(-) create mode 100644 ts/a11y/SpeechUtil.ts diff --git a/ts/a11y/SpeechUtil.ts b/ts/a11y/SpeechUtil.ts new file mode 100644 index 000000000..f94ef270f --- /dev/null +++ b/ts/a11y/SpeechUtil.ts @@ -0,0 +1,137 @@ +const ProsodyKeys = [ 'pitch', 'rate', 'volume' ]; + +interface ProsodyElement { + [propName: string]: string | boolean | number; + pitch?: number; + rate?: number; + volume?: number; +} + +export interface SsmlElement extends ProsodyElement { + [propName: string]: string | boolean | number; + pause?: string; + text?: string; + mark?: string; + character?: boolean; + kind?: string; +} + + /** + * Parses a string containing an ssml structure into a list of text strings + * with associated ssml annotation elements. + * + * @param {string} speech The speech string. + * @return {[string, SsmlElement[]]} The annotation structure. + */ + export function ssmlParsing(speech: string): [string, SsmlElement[]] { + let dp = new DOMParser(); + let xml = dp.parseFromString(speech, 'text/xml'); + let instr: SsmlElement[] = []; + let text: String[] = []; + recurseSsml(Array.from(xml.documentElement.childNodes), instr, text); + return [text.join(' '), instr]; + } + + /** + * Tail recursive combination of SSML components. + * + * @param {Node[]} nodes A list of SSML nodes. + * @param {SsmlElement[]} instr Accumulator for collating Ssml annotation + * elements. + * @param {String[]} text A list of text elements. + * @param {ProsodyElement?} prosody The currently active prosody elements. + */ +function recurseSsml(nodes: Node[], instr: SsmlElement[], text: String[], + prosody: ProsodyElement = {}) { + for (let node of nodes) { + if (node.nodeType === 3) { + let content = node.textContent.trim(); + if (content) { + text.push(content); + instr.push(Object.assign({text: content}, prosody)); + } + continue; + } + if (node.nodeType === 1) { + let element = node as Element; + let tag = element.tagName; + if (tag === 'speak') { + continue; + } + if (tag === 'prosody') { + recurseSsml( + Array.from(node.childNodes), instr, text, + getProsody(element, prosody)); + continue; + } + switch (tag) { + case 'break': + instr.push({pause: element.getAttribute('time')}); + break; + case 'mark': + instr.push({mark: element.getAttribute('name')}); + break; + case 'say-as': + let txt = element.textContent; + instr.push(Object.assign({text: txt, character: true}, prosody)); + text.push(txt); + break; + default: + break; + } + } + } + } + + /** + * Maps prosody types to scaling functions. + */ + // TODO: These should be tweaked after more testing. +const combinePros: {[key: string]: (x: number, sign: string) => number} = { + pitch: (x: number, _sign: string) => 1 * (x / 100), + volume: (x: number, _sign: string) => .5 * (x / 100), + rate: (x: number, _sign: string) => 1 * (x / 100) + }; + + /** + * Retrieves prosody annotations from and SSML node. + * @param {Element} element The SSML node. + * @param {ProsodyElement} prosody The prosody annotation. + */ + function getProsody(element: Element, prosody: ProsodyElement) { + let combine: ProsodyElement = {}; + for (let pros of ProsodyKeys) { + if (element.hasAttribute(pros)) { + let [sign, value] = extractProsody(element.getAttribute(pros)); + if (!sign) { + // TODO: Sort out the base value. It is .5 for volume! + combine[pros] = (pros === 'volume') ? .5 : 1; + continue; + } + let orig = prosody[pros] as number; + orig = orig ? orig : ((pros === 'volume') ? .5 : 1); + let relative = combinePros[pros](parseInt(value, 10), sign); + combine[pros] = (sign === '-') ? orig - relative : orig + relative; + } + } + return combine; + } + + /** + * Extracts the prosody value from an attribute. + */ +const prosodyRegexp = /([\+|-]*)([0-9]+)%/; + +/** + * Extracts the prosody value from an attribute. + * @param {string} attr + */ +function extractProsody(attr: string) { + let match = attr.match(prosodyRegexp); + if (!match) { + console.warn('Something went wrong with the prosody matching.'); + return ['', '100']; + } + return [match[1], match[2]]; +} + diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 3d0f23314..cd7f4cdcf 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -359,7 +359,9 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public KeyDown(event: KeyboardEvent) { + console.log(1); const code = event.keyCode; + console.log(2); this.walker.modifier = event.shiftKey; if (code === 17) { speechSynthesis.cancel(); @@ -372,7 +374,11 @@ export class SpeechExplorer extends AbstractKeyExplorer { } // let result = move(event); + console.log(3); if (result) { + console.log(4); + console.log(result); + console.log(this.region); this.region.Show(this.node, this.highlighter); this.region.Update('hello'); return; diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index 33f139e6b..3cf088601 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -26,6 +26,7 @@ import {MathDocument} from '../../core/MathDocument.js'; import {CssStyles} from '../../util/StyleList.js'; import {Sre} from '../sre.js'; +import {SsmlElement, ssmlParsing} from '../SpeechUtil.js'; export type A11yDocument = MathDocument; @@ -278,6 +279,8 @@ export class StringRegion extends AbstractRegion { * @override */ public Update(speech: string) { + console.log(6); + console.log(speech); this.inner.textContent = ''; this.inner.textContent = speech; } @@ -367,24 +370,6 @@ export class LiveRegion extends StringRegion { } -const ProsodyKeys = [ 'pitch', 'rate', 'volume' ]; - -interface ProsodyElement { - [propName: string]: string | boolean | number; - pitch?: number; - rate?: number; - volume?: number; -} - -interface SsmlElement extends ProsodyElement { - [propName: string]: string | boolean | number; - pause?: string; - text?: string; - mark?: string; - character?: boolean; - kind?: string; -} - /** * Region class that enables auto voicing of content via SSML markup. */ @@ -432,7 +417,7 @@ export class SpeechRegion extends LiveRegion { !!speechSynthesis.getVoices().length; speechSynthesis.cancel(); this.clear = true; - let [text, ssml] = this.ssmlParsing(speech); + let [text, ssml] = ssmlParsing(speech); super.Update(text); if (this.active && text) { this.makeUtterances(ssml, this.document.options.sre.locale); @@ -504,125 +489,6 @@ export class SpeechRegion extends LiveRegion { } - /** - * Parses a string containing an ssml structure into a list of text strings - * with associated ssml annotation elements. - * - * @param {string} speech The speech string. - * @return {[string, SsmlElement[]]} The annotation structure. - */ - private ssmlParsing(speech: string): [string, SsmlElement[]] { - let dp = new DOMParser(); - let xml = dp.parseFromString(speech, 'text/xml'); - let instr: SsmlElement[] = []; - let text: String[] = []; - this.recurseSsml(Array.from(xml.documentElement.childNodes), instr, text); - return [text.join(' '), instr]; - } - - /** - * Tail recursive combination of SSML components. - * - * @param {Node[]} nodes A list of SSML nodes. - * @param {SsmlElement[]} instr Accumulator for collating Ssml annotation - * elements. - * @param {String[]} text A list of text elements. - * @param {ProsodyElement?} prosody The currently active prosody elements. - */ - private recurseSsml(nodes: Node[], instr: SsmlElement[], text: String[], - prosody: ProsodyElement = {}) { - for (let node of nodes) { - if (node.nodeType === 3) { - let content = node.textContent.trim(); - if (content) { - text.push(content); - instr.push(Object.assign({text: content}, prosody)); - } - continue; - } - if (node.nodeType === 1) { - let element = node as Element; - let tag = element.tagName; - if (tag === 'speak') { - continue; - } - if (tag === 'prosody') { - this.recurseSsml( - Array.from(node.childNodes), instr, text, - this.getProsody(element, prosody)); - continue; - } - switch (tag) { - case 'break': - instr.push({pause: element.getAttribute('time')}); - break; - case 'mark': - instr.push({mark: element.getAttribute('name')}); - break; - case 'say-as': - let txt = element.textContent; - instr.push(Object.assign({text: txt, character: true}, prosody)); - text.push(txt); - break; - default: - break; - } - } - } - } - - /** - * Maps prosody types to scaling functions. - */ - // TODO: These should be tweaked after more testing. - private static combinePros: {[key: string]: (x: number, sign: string) => number} = { - pitch: (x: number, _sign: string) => 1 * (x / 100), - volume: (x: number, _sign: string) => .5 * (x / 100), - rate: (x: number, _sign: string) => 1 * (x / 100) - }; - - /** - * Retrieves prosody annotations from and SSML node. - * @param {Element} element The SSML node. - * @param {ProsodyElement} prosody The prosody annotation. - */ - private getProsody(element: Element, prosody: ProsodyElement) { - let combine: ProsodyElement = {}; - for (let pros of ProsodyKeys) { - if (element.hasAttribute(pros)) { - let [sign, value] = SpeechRegion.extractProsody(element.getAttribute(pros)); - if (!sign) { - // TODO: Sort out the base value. It is .5 for volume! - combine[pros] = (pros === 'volume') ? .5 : 1; - continue; - } - let orig = prosody[pros] as number; - orig = orig ? orig : ((pros === 'volume') ? .5 : 1); - let relative = SpeechRegion.combinePros[pros](parseInt(value, 10), sign); - combine[pros] = (sign === '-') ? orig - relative : orig + relative; - } - } - return combine; - } - - /** - * Extracts the prosody value from an attribute. - */ - private static prosodyRegexp = /([\+|-]*)([0-9]+)%/; - - /** - * Extracts the prosody value from an attribute. - * @param {string} attr - */ - private static extractProsody(attr: string) { - let match = attr.match(SpeechRegion.prosodyRegexp); - if (!match) { - console.warn('Something went wrong with the prosody matching.'); - return ['', '100']; - } - return [match[1], match[2]]; - } - } diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts index 63e744182..e1202db73 100644 --- a/ts/a11y/explorer/Walker.ts +++ b/ts/a11y/explorer/Walker.ts @@ -111,9 +111,11 @@ export function move(e: KeyboardEvent) { target.removeAttribute('tabindex'); next.setAttribute('tabindex', '0'); next.focus(); - // console.log(next.getAttribute('data-semantic-speech')); - // console.log(next.getAttribute('data-semantic-braille')); - return true; + console.log(next.getAttribute('data-semantic-speech')); + console.log(next.getAttribute('aria-label')); + console.log(next.getAttribute('data-semantic-braille')); + console.log(next.getAttribute('aria-braillelabel')); + return [next]; } return false; } diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 810496432..0023767de 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -31,13 +31,14 @@ import {MathML} from '../input/mathml.js'; import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js'; import {OptionList, expandable} from '../util/Options.js'; import {Sre} from './sre.js'; +import { ssmlParsing } from './SpeechUtil.js'; /*==========================================================================*/ /** * The current speech setting for Sre */ -let currentSpeech = 'none'; +// let currentSpeech = 'none'; let currentLocale = 'none'; let currentBraille = 'none'; @@ -299,18 +300,19 @@ export function EnrichedMathItemMixin Date: Mon, 31 Jul 2023 18:56:49 +0200 Subject: [PATCH 26/47] SSML extraction works. --- ts/a11y/SpeechUtil.ts | 214 ++++++++++++++++++-------------- ts/a11y/explorer/KeyExplorer.ts | 2 + ts/a11y/explorer/Walker.ts | 5 - ts/a11y/semantic-enrich.ts | 52 +++----- 4 files changed, 146 insertions(+), 127 deletions(-) diff --git a/ts/a11y/SpeechUtil.ts b/ts/a11y/SpeechUtil.ts index f94ef270f..baa377bd3 100644 --- a/ts/a11y/SpeechUtil.ts +++ b/ts/a11y/SpeechUtil.ts @@ -1,3 +1,6 @@ +import {MmlNode} from '../core/MmlTree/MmlNode.js'; +import {Sre} from './sre.js'; + const ProsodyKeys = [ 'pitch', 'rate', 'volume' ]; interface ProsodyElement { @@ -16,110 +19,112 @@ export interface SsmlElement extends ProsodyElement { kind?: string; } - /** - * Parses a string containing an ssml structure into a list of text strings - * with associated ssml annotation elements. - * - * @param {string} speech The speech string. - * @return {[string, SsmlElement[]]} The annotation structure. - */ - export function ssmlParsing(speech: string): [string, SsmlElement[]] { - let dp = new DOMParser(); - let xml = dp.parseFromString(speech, 'text/xml'); - let instr: SsmlElement[] = []; - let text: String[] = []; - recurseSsml(Array.from(xml.documentElement.childNodes), instr, text); - return [text.join(' '), instr]; - } +/** + * Parses a string containing an ssml structure into a list of text strings + * with associated ssml annotation elements. + * + * @param {string} speech The speech string. + * @return {[string, SsmlElement[]]} The annotation structure. + */ +export function ssmlParsing(speech: string): [string, SsmlElement[]] { + console.log(Sre.engineSetup()); - /** - * Tail recursive combination of SSML components. - * - * @param {Node[]} nodes A list of SSML nodes. - * @param {SsmlElement[]} instr Accumulator for collating Ssml annotation - * elements. - * @param {String[]} text A list of text elements. - * @param {ProsodyElement?} prosody The currently active prosody elements. - */ + let dp = new DOMParser(); + let xml = dp.parseFromString(speech, 'text/xml'); + let instr: SsmlElement[] = []; + let text: String[] = []; + recurseSsml(Array.from(xml.documentElement.childNodes), instr, text); + return [text.join(' '), instr]; +} + +/** + * Tail recursive combination of SSML components. + * + * @param {Node[]} nodes A list of SSML nodes. + * @param {SsmlElement[]} instr Accumulator for collating Ssml annotation + * elements. + * @param {String[]} text A list of text elements. + * @param {ProsodyElement?} prosody The currently active prosody elements. + */ function recurseSsml(nodes: Node[], instr: SsmlElement[], text: String[], - prosody: ProsodyElement = {}) { - for (let node of nodes) { - if (node.nodeType === 3) { - let content = node.textContent.trim(); - if (content) { - text.push(content); - instr.push(Object.assign({text: content}, prosody)); - } + prosody: ProsodyElement = {}) { + for (let node of nodes) { + if (node.nodeType === 3) { + let content = node.textContent.trim(); + if (content) { + text.push(content); + instr.push(Object.assign({text: content}, prosody)); + } + continue; + } + if (node.nodeType === 1) { + let element = node as Element; + let tag = element.tagName; + if (tag === 'speak') { + continue; + } + if (tag === 'prosody') { + recurseSsml( + Array.from(node.childNodes), instr, text, + getProsody(element, prosody)); continue; } - if (node.nodeType === 1) { - let element = node as Element; - let tag = element.tagName; - if (tag === 'speak') { - continue; - } - if (tag === 'prosody') { - recurseSsml( - Array.from(node.childNodes), instr, text, - getProsody(element, prosody)); - continue; - } - switch (tag) { - case 'break': - instr.push({pause: element.getAttribute('time')}); - break; - case 'mark': - instr.push({mark: element.getAttribute('name')}); - break; - case 'say-as': - let txt = element.textContent; - instr.push(Object.assign({text: txt, character: true}, prosody)); - text.push(txt); - break; - default: - break; - } + switch (tag) { + case 'break': + instr.push({pause: element.getAttribute('time')}); + break; + case 'mark': + instr.push({mark: element.getAttribute('name')}); + break; + case 'say-as': + let txt = element.textContent; + instr.push(Object.assign({text: txt, character: true}, prosody)); + text.push(txt); + break; + default: + break; } } } +} - /** - * Maps prosody types to scaling functions. - */ - // TODO: These should be tweaked after more testing. +/** + * Maps prosody types to scaling functions. + */ +// TODO: These should be tweaked after more testing. const combinePros: {[key: string]: (x: number, sign: string) => number} = { - pitch: (x: number, _sign: string) => 1 * (x / 100), - volume: (x: number, _sign: string) => .5 * (x / 100), - rate: (x: number, _sign: string) => 1 * (x / 100) - }; + pitch: (x: number, _sign: string) => 1 * (x / 100), + volume: (x: number, _sign: string) => .5 * (x / 100), + rate: (x: number, _sign: string) => 1 * (x / 100) +}; - /** - * Retrieves prosody annotations from and SSML node. - * @param {Element} element The SSML node. - * @param {ProsodyElement} prosody The prosody annotation. - */ - function getProsody(element: Element, prosody: ProsodyElement) { - let combine: ProsodyElement = {}; - for (let pros of ProsodyKeys) { - if (element.hasAttribute(pros)) { - let [sign, value] = extractProsody(element.getAttribute(pros)); - if (!sign) { - // TODO: Sort out the base value. It is .5 for volume! - combine[pros] = (pros === 'volume') ? .5 : 1; - continue; - } - let orig = prosody[pros] as number; - orig = orig ? orig : ((pros === 'volume') ? .5 : 1); - let relative = combinePros[pros](parseInt(value, 10), sign); - combine[pros] = (sign === '-') ? orig - relative : orig + relative; +/** + * Retrieves prosody annotations from and SSML node. + * @param {Element} element The SSML node. + * @param {ProsodyElement} prosody The prosody annotation. + */ +function getProsody(element: Element, prosody: ProsodyElement) { + let combine: ProsodyElement = {}; + for (let pros of ProsodyKeys) { + if (element.hasAttribute(pros)) { + let [sign, value] = extractProsody(element.getAttribute(pros)); + if (!sign) { + // TODO: Sort out the base value. It is .5 for volume! + combine[pros] = (pros === 'volume') ? .5 : 1; + continue; } + let orig = prosody[pros] as number; + orig = orig ? orig : ((pros === 'volume') ? .5 : 1); + let relative = combinePros[pros](parseInt(value, 10), sign); + combine[pros] = (sign === '-') ? orig - relative : orig + relative; } - return combine; } + return combine; +} - /** - * Extracts the prosody value from an attribute. - */ +/** + * Extracts the prosody value from an attribute. + */ const prosodyRegexp = /([\+|-]*)([0-9]+)%/; /** @@ -135,3 +140,32 @@ function extractProsody(attr: string) { return [match[1], match[2]]; } +export function getLabel(node: MmlNode, sep: string = ' ') { + const attributes = node.attributes; + const speech = attributes.getExplicit('data-semantic-speech') as string; + if (!speech) { + return ''; + } + const label = [speech]; + const prefix = attributes.getExplicit('data-semantic-prefix') as string; + if (prefix) { + label.unshift(prefix); + } + // TODO: check if we need this or if is automatic by the screen readers. + const postfix = attributes.getExplicit('data-semantic-postfix') as string; + if (postfix) { + label.push(postfix); + } + // TODO: Do we need to merge wrt. locale in SRE. + return label.join(sep); +} + + +export function buildSpeech(speech: string, locale: string = 'en', + rate: string = '100') { + return ssmlParsing('` + + `${speech}`+ + ''); +} diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index cd7f4cdcf..3067b099d 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -310,6 +310,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public Update(force: boolean = false) { + console.log(9); // TODO (v4): This is a hack to avoid double voicing on initial startup! // Make that cleaner and remove force as it is not really used! let noUpdate = force; @@ -330,6 +331,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { modality: options.modality, locale: options.locale})) .then(() => { + console.log(10); if (!noUpdate) { let speech = this.walker.speech(); this.region.Update(speech); diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts index e1202db73..380bda3b0 100644 --- a/ts/a11y/explorer/Walker.ts +++ b/ts/a11y/explorer/Walker.ts @@ -106,15 +106,10 @@ export function move(e: KeyboardEvent) { const target = e.target as HTMLElement; - console.log(0); if (next) { target.removeAttribute('tabindex'); next.setAttribute('tabindex', '0'); next.focus(); - console.log(next.getAttribute('data-semantic-speech')); - console.log(next.getAttribute('aria-label')); - console.log(next.getAttribute('data-semantic-braille')); - console.log(next.getAttribute('aria-braillelabel')); return [next]; } return false; diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 0023767de..53800647c 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -26,12 +26,11 @@ import {Handler} from '../core/Handler.js'; import {MathDocument, AbstractMathDocument, MathDocumentConstructor} from '../core/MathDocument.js'; import {MathItem, AbstractMathItem, STATE, newState} from '../core/MathItem.js'; import {MmlNode} from '../core/MmlTree/MmlNode.js'; -import {Attributes} from '../core/MmlTree/Attributes.js'; import {MathML} from '../input/mathml.js'; import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js'; import {OptionList, expandable} from '../util/Options.js'; import {Sre} from './sre.js'; -import { ssmlParsing } from './SpeechUtil.js'; +import { buildSpeech, getLabel } from './SpeechUtil.js'; /*==========================================================================*/ @@ -181,8 +180,11 @@ export function EnrichedMathItemMixin Sre.sreReady())); + () => { + console.log(18); + return Sre.sreReady(); })); } + console.log(Sre.engineSetup()); if (document.options.sre.braille !== currentBraille) { currentBraille = document.options.sre.braille; // TODO: Sort out the loading of the locales better @@ -194,7 +196,9 @@ export function EnrichedMathItemMixin Sre.sreReady())); + .then(() => { + console.log(19); + Sre.sreReady();})); } const math = new document.options.MathItem('', MmlJax); try { @@ -208,7 +212,9 @@ export function EnrichedMathItemMixin Date: Tue, 1 Aug 2023 00:31:28 +0200 Subject: [PATCH 27/47] Initial regions mainly working. --- ts/a11y/explorer/ExplorerPool.ts | 2 + ts/a11y/explorer/KeyExplorer.ts | 27 +++-- ts/a11y/explorer/Region.ts | 24 +++-- ts/a11y/explorer/Walker.ts | 166 +++++++++++++++++++------------ 4 files changed, 133 insertions(+), 86 deletions(-) diff --git a/ts/a11y/explorer/ExplorerPool.ts b/ts/a11y/explorer/ExplorerPool.ts index 9d3d7e50b..61edeae80 100644 --- a/ts/a11y/explorer/ExplorerPool.ts +++ b/ts/a11y/explorer/ExplorerPool.ts @@ -101,6 +101,8 @@ let allExplorers: {[options: string]: ExplorerInit} = { } explorer.sound = true; explorer.showRegion = 'subtitles'; + explorer.newWalker.speechRegion = doc.explorerRegions.speechRegion; + explorer.newWalker.brailleRegion = doc.explorerRegions.brailleRegion; return explorer; }, // braille: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => { diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 3067b099d..ff10ec65a 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -28,7 +28,7 @@ import {Explorer, AbstractExplorer} from './Explorer.js'; import {ExplorerPool} from './ExplorerPool.js'; import {Sre} from '../sre.js'; -import {click, move} from './Walker.js'; +import { Walker } from './Walker.js'; /** @@ -78,6 +78,8 @@ export interface KeyExplorer extends Explorer { */ export abstract class AbstractKeyExplorer extends AbstractExplorer implements KeyExplorer { + public newWalker = new Walker(); + /** * Flag indicating if the explorer is attached to an object. */ @@ -104,7 +106,7 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme [ // ['keydown', move], ['keydown', this.KeyDown.bind(this)], - ['click', ((e: MouseEvent) => click(this.node, e)).bind(this)], + ['click', ((e: MouseEvent) => this.newWalker.click(this.node, e)).bind(this)], ['focusin', this.FocusIn.bind(this)], ['focusout', this.FocusOut.bind(this)] ]); @@ -310,7 +312,6 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public Update(force: boolean = false) { - console.log(9); // TODO (v4): This is a hack to avoid double voicing on initial startup! // Make that cleaner and remove force as it is not really used! let noUpdate = force; @@ -331,7 +332,6 @@ export class SpeechExplorer extends AbstractKeyExplorer { modality: options.modality, locale: options.locale})) .then(() => { - console.log(10); if (!noUpdate) { let speech = this.walker.speech(); this.region.Update(speech); @@ -361,9 +361,8 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public KeyDown(event: KeyboardEvent) { - console.log(1); const code = event.keyCode; - console.log(2); + console.log(event.key); this.walker.modifier = event.shiftKey; if (code === 17) { speechSynthesis.cancel(); @@ -372,17 +371,17 @@ export class SpeechExplorer extends AbstractKeyExplorer { if (code === 27) { this.Stop(); this.stopEvent(event); + this.newWalker.HideRegions(); return; } - // - let result = move(event); - console.log(3); + + let result = this.newWalker.move(event); + this.newWalker.ShowRegions(this.node, this.highlighter); if (result) { - console.log(4); - console.log(result); - console.log(this.region); - this.region.Show(this.node, this.highlighter); - this.region.Update('hello'); + // console.log(4); + // console.log(result); + // console.log(this.region); + // this.region.Update('hello'); return; } if (this.sound) { diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index 3cf088601..f26f6a92e 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -91,7 +91,7 @@ export abstract class AbstractRegion implements Region { * The outer div node. * @type {HTMLElement} */ - protected div: HTMLElement; + public div: HTMLElement; /** * The inner node. @@ -413,15 +413,19 @@ export class SpeechRegion extends LiveRegion { * @override */ public Update(speech: string) { - this.active = this.document.options.a11y.voicing && - !!speechSynthesis.getVoices().length; - speechSynthesis.cancel(); - this.clear = true; - let [text, ssml] = ssmlParsing(speech); - super.Update(text); - if (this.active && text) { - this.makeUtterances(ssml, this.document.options.sre.locale); - } + // console.log(speech); + // this.active = this.document.options.a11y.voicing && + // !!speechSynthesis.getVoices().length; + // speechSynthesis.cancel(); + // this.clear = true; + // let [text, ssml] = ssmlParsing(speech); + // console.log(27); + // console.log(text); + // super.Update(text); + // if (this.active && text) { + // this.makeUtterances(ssml, this.document.options.sre.locale); + // } + super.Update(speech); } /** diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts index 380bda3b0..bc9cc377c 100644 --- a/ts/a11y/explorer/Walker.ts +++ b/ts/a11y/explorer/Walker.ts @@ -22,6 +22,9 @@ * @author v.sorge@mathjax.org (Volker Sorge) */ +import { SpeechRegion, LiveRegion } from './Region.js'; +import {Sre} from '../sre.js'; + // Based on the shellac walker. const codeSelector = 'mjx-container[role="application"][data-shellac]'; @@ -31,86 +34,125 @@ function isCodeBlock(el: HTMLElement) { return el.matches(codeSelector); } -export function click(snippet: HTMLElement, e: MouseEvent) { - const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; - if (snippet.contains(clicked)) { - const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); - if (prev) { - prev.removeAttribute('tabindex'); +export class Walker { + + public shown: boolean = false; + public speechRegion: SpeechRegion; + public brailleRegion: LiveRegion; + + public click(snippet: HTMLElement, e: MouseEvent) { + const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; + if (snippet.contains(clicked)) { + const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); + if (prev) { + prev.removeAttribute('tabindex'); + } + clicked.setAttribute('tabindex', '0'); + clicked.focus(); + this.UpdateRegions(clicked); + e.preventDefault(); } - clicked.setAttribute('tabindex', '0'); - clicked.focus(); - e.preventDefault(); } -} -export function move(e: KeyboardEvent) { + public move(e: KeyboardEvent) { - function nextFocus(): HTMLElement { - function nextSibling(el: HTMLElement): HTMLElement { - const sib = el.nextElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; - } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? nextSibling(sib); - } - } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return nextSibling(el.parentElement); + function nextFocus(): HTMLElement { + function nextSibling(el: HTMLElement): HTMLElement { + const sib = el.nextElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; + } else { + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? nextSibling(sib); + } } else { - return null; + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return nextSibling(el.parentElement); + } else { + return null; + } } } - } - function prevSibling(el: HTMLElement): HTMLElement { - const sib = el.previousElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; + function prevSibling(el: HTMLElement): HTMLElement { + const sib = el.previousElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; + } else { + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? prevSibling(sib); + } } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? prevSibling(sib); + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return prevSibling(el.parentElement); + } else { + return null; + } } - } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return prevSibling(el.parentElement); - } else { + } + + const target = e.target as HTMLElement; + console.log(e.key); + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + return target.querySelector(nav); + case 'ArrowUp': + e.preventDefault(); + return target.parentElement.closest(nav); + case 'ArrowLeft': + e.preventDefault(); + return prevSibling(target); + case 'ArrowRight': + e.preventDefault(); + return nextSibling(target); + case 'Esc': + e.preventDefault(); + return this.hideRegions(); + default: return null; - } } } + const next = nextFocus(); + const target = e.target as HTMLElement; - switch (e.key) { - case 'ArrowDown': - e.preventDefault(); - return target.querySelector(nav); - case 'ArrowUp': - e.preventDefault(); - return target.parentElement.closest(nav); - case 'ArrowLeft': - e.preventDefault(); - return prevSibling(target); - case 'ArrowRight': - e.preventDefault(); - return nextSibling(target); - default: - return null; + if (next) { + target.removeAttribute('tabindex'); + next.setAttribute('tabindex', '0'); + next.focus(); + this.UpdateRegions(next); + return [next]; } + return false; } - const next = nextFocus(); - + private UpdateRegions(element: HTMLElement) { + console.log(25); + console.log(element); + console.log(element.getAttribute('aria-label')); + console.log(element.getAttribute('aria-braillelabel')); + console.log(this.speechRegion); + this.speechRegion.Update(element.getAttribute('aria-label')); + this.brailleRegion.Update(element.getAttribute('aria-braillelabel')); + } - const target = e.target as HTMLElement; - if (next) { - target.removeAttribute('tabindex'); - next.setAttribute('tabindex', '0'); - next.focus(); - return [next]; + public ShowRegions(element: HTMLElement, highlighter: Sre.highlighter) { + if (!this.shown) { + this.speechRegion.Show(element, highlighter); + this.brailleRegion.Show(element, highlighter); + } + this.shown = true; + } + + public HideRegions() { + if (this.shown) { + this.speechRegion.Hide(); + this.brailleRegion.Hide(); + } + this.shown = false; } - return false; } + From 2ca47b46e19e695167792d4e9401e78e3c2349b1 Mon Sep 17 00:00:00 2001 From: zorkow Date: Tue, 1 Aug 2023 13:18:56 +0200 Subject: [PATCH 28/47] Messing with regions. --- ts/a11y/SpeechUtil.ts | 2 - ts/a11y/explorer/KeyExplorer.ts | 34 ++++++++-------- ts/a11y/explorer/Walker.ts | 72 ++++++++++++++++++++++++++++----- ts/a11y/semantic-enrich.ts | 7 ---- 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/ts/a11y/SpeechUtil.ts b/ts/a11y/SpeechUtil.ts index baa377bd3..a1f03a6ce 100644 --- a/ts/a11y/SpeechUtil.ts +++ b/ts/a11y/SpeechUtil.ts @@ -27,8 +27,6 @@ export interface SsmlElement extends ProsodyElement { * @return {[string, SsmlElement[]]} The annotation structure. */ export function ssmlParsing(speech: string): [string, SsmlElement[]] { - console.log(Sre.engineSetup()); - let dp = new DOMParser(); let xml = dp.parseFromString(speech, 'text/xml'); let instr: SsmlElement[] = []; diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index ff10ec65a..a94d232e7 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -132,7 +132,7 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme * @override */ public FocusOut(_event: FocusEvent) { - this.Stop(); + // this.Stop(); } /** @@ -260,6 +260,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { protected node: HTMLElement, private mml: string) { super(document, pool, region, node); + this.newWalker.highlighter = this.highlighter; this.initWalker(); } @@ -268,6 +269,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public Start() { + console.log(this.attached); if (!this.attached) return; let options = this.getOptions(); if (!this.init) { @@ -361,36 +363,36 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public KeyDown(event: KeyboardEvent) { - const code = event.keyCode; - console.log(event.key); + console.log('active: ' + this.active); + const code = event.key; this.walker.modifier = event.shiftKey; - if (code === 17) { + if (code === 'Control') { speechSynthesis.cancel(); return; } - if (code === 27) { + if (code === 'Escape') { this.Stop(); this.stopEvent(event); this.newWalker.HideRegions(); return; } - - let result = this.newWalker.move(event); - this.newWalker.ShowRegions(this.node, this.highlighter); + let result = null; + if (this.active) { + result = this.newWalker.move(event); + this.newWalker.ShowRegions(this.node, this.highlighter); + this.stopEvent(event); + } if (result) { - // console.log(4); - // console.log(result); - // console.log(this.region); // this.region.Update('hello'); return; } - if (this.sound) { + if (!result && this.sound) { this.NoMove(); } // if (this.triggerLink(code)) return; - this.stopEvent(event); - if (code === 32 && event.shiftKey || code === 13) { + // this.stopEvent(event); + if (code === 'Space' && event.shiftKey || code === 'Enter') { this.Start(); this.stopEvent(event); } @@ -400,8 +402,8 @@ export class SpeechExplorer extends AbstractKeyExplorer { * Programmatically triggers a link if the focused node contains one. * @param {number} code The keycode of the last key pressed. */ - protected triggerLink(code: number) { - if (code !== 13) { + protected triggerLink(code: string) { + if (code !== 'Enter') { return false; } let node = this.walker.getFocus().getNodes()?.[0]; diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts index bc9cc377c..20b4a29c9 100644 --- a/ts/a11y/explorer/Walker.ts +++ b/ts/a11y/explorer/Walker.ts @@ -34,12 +34,49 @@ function isCodeBlock(el: HTMLElement) { return el.matches(codeSelector); } +type rgbColor = {red: number, green: number, blue: number}; +type channelColor = {name: string, alpha: number}; + +export class Highlighter { + + public foreground: string; + public background: string; + + public static namedColors: { [key: string]: rgbColor } = { + red: { red: 255, green: 0, blue: 0 }, + green: { red: 0, green: 255, blue: 0 }, + blue: { red: 0, green: 0, blue: 255 }, + yellow: { red: 255, green: 255, blue: 0 }, + cyan: { red: 0, green: 255, blue: 255 }, + magenta: { red: 255, green: 0, blue: 255 }, + white: { red: 255, green: 255, blue: 255 }, + black: { red: 0, green: 0, blue: 0 } + }; + + constructor(foreground: channelColor, background: channelColor) { + this.foreground = this.makeColor(foreground); + this.background = this.makeColor(background); + } + + // public highlight(node: HTMLElement) { + + // } + + private makeColor({name: name, alpha: alpha}: channelColor) { + let {red: red, green: green, blue: blue} = + Highlighter.namedColors[name] || Highlighter.namedColors['blue']; + return `rgba(${red},${green},${blue},${alpha})`; + } + +} + export class Walker { public shown: boolean = false; public speechRegion: SpeechRegion; public brailleRegion: LiveRegion; - + public highlighter: Sre.highlighter; + public click(snippet: HTMLElement, e: MouseEvent) { const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; if (snippet.contains(clicked)) { @@ -94,7 +131,6 @@ export class Walker { } const target = e.target as HTMLElement; - console.log(e.key); switch (e.key) { case 'ArrowDown': e.preventDefault(); @@ -108,9 +144,9 @@ export class Walker { case 'ArrowRight': e.preventDefault(); return nextSibling(target); - case 'Esc': - e.preventDefault(); - return this.hideRegions(); + // case 'Esc': + // e.preventDefault(); + // return this.hideRegions(); default: return null; } @@ -123,18 +159,23 @@ export class Walker { target.removeAttribute('tabindex'); next.setAttribute('tabindex', '0'); next.focus(); + this.UpdateHighlight(target, next); this.UpdateRegions(next); return [next]; } return false; } + private info: Highlight; + + private UpdateHighlight(_target: HTMLElement, next: HTMLElement) { + if (this.info) { + (this.highlighter as any).unhighlightNode(this.info); + } + this.info = (this.highlighter as any).highlightNode(next); + } + private UpdateRegions(element: HTMLElement) { - console.log(25); - console.log(element); - console.log(element.getAttribute('aria-label')); - console.log(element.getAttribute('aria-braillelabel')); - console.log(this.speechRegion); this.speechRegion.Update(element.getAttribute('aria-label')); this.brailleRegion.Update(element.getAttribute('aria-braillelabel')); } @@ -146,7 +187,7 @@ export class Walker { } this.shown = true; } - + public HideRegions() { if (this.shown) { this.speechRegion.Hide(); @@ -156,3 +197,12 @@ export class Walker { } } +export interface Highlight { + node: HTMLElement; + opacity?: string; + background?: string; + foreground?: string; + // The following is for the CSS highlighter + box?: HTMLElement; + position?: string; +} diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 53800647c..206122c26 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -181,10 +181,8 @@ export function EnrichedMathItemMixin { - console.log(18); return Sre.sreReady(); })); } - console.log(Sre.engineSetup()); if (document.options.sre.braille !== currentBraille) { currentBraille = document.options.sre.braille; // TODO: Sort out the loading of the locales better @@ -197,7 +195,6 @@ export function EnrichedMathItemMixin { - console.log(19); Sre.sreReady();})); } const math = new document.options.MathItem('', MmlJax); @@ -227,13 +224,10 @@ export function EnrichedMathItemMixin).attachSpeech(this); From 6b7747d396c6685fa7c56054eeee88c969364c32 Mon Sep 17 00:00:00 2001 From: zorkow Date: Thu, 3 Aug 2023 15:43:09 +0200 Subject: [PATCH 29/47] Remove as much as possible. --- ts/a11y/explorer/ExplorerPool.ts | 20 ++++++------- ts/a11y/explorer/KeyExplorer.ts | 49 ++++++++++++++++---------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/ts/a11y/explorer/ExplorerPool.ts b/ts/a11y/explorer/ExplorerPool.ts index 61edeae80..526b9f513 100644 --- a/ts/a11y/explorer/ExplorerPool.ts +++ b/ts/a11y/explorer/ExplorerPool.ts @@ -89,16 +89,16 @@ let allExplorers: {[options: string]: ExplorerInit} = { speech: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => { let explorer = ke.SpeechExplorer.create( doc, pool, doc.explorerRegions.speechRegion, node, ...rest) as ke.SpeechExplorer; - explorer.speechGenerator.setOptions({ - automark: true as any, markup: 'ssml', - locale: doc.options.sre.locale, domain: doc.options.sre.domain, - style: doc.options.sre.style, modality: 'speech'}); - // This weeds out the case of providing a non-existent locale option. - let locale = explorer.speechGenerator.getOptions().locale; - if (locale !== Sre.engineSetup().locale) { - doc.options.sre.locale = Sre.engineSetup().locale; - explorer.speechGenerator.setOptions({locale: doc.options.sre.locale}); - } + // explorer.speechGenerator.setOptions({ + // automark: true as any, markup: 'ssml', + // locale: doc.options.sre.locale, domain: doc.options.sre.domain, + // style: doc.options.sre.style, modality: 'speech'}); + // // This weeds out the case of providing a non-existent locale option. + // let locale = explorer.speechGenerator.getOptions().locale; + // if (locale !== Sre.engineSetup().locale) { + // doc.options.sre.locale = Sre.engineSetup().locale; + // explorer.speechGenerator.setOptions({locale: doc.options.sre.locale}); + // } explorer.sound = true; explorer.showRegion = 'subtitles'; explorer.newWalker.speechRegion = doc.explorerRegions.speechRegion; diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index a94d232e7..6e2eca388 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -269,33 +269,32 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public Start() { - console.log(this.attached); if (!this.attached) return; - let options = this.getOptions(); - if (!this.init) { - this.init = true; - SpeechExplorer.updatePromise = SpeechExplorer.updatePromise.then(async () => { - return Sre.sreReady() - .then(() => Sre.setupEngine({locale: options.locale})) - .then(() => { - // Important that both are in the same block so speech explorers - // are restarted sequentially. - this.Speech(this.walker); - }) - .then(() => Sre.setupEngine({automark: false as any, markup: 'none', - locale: 'nemeth', domain: 'default', - style: 'default', modality: 'braille'})) - .then(() => { - this.speechGenerator.setOptions({automark: false as any, markup: 'none', - locale: 'nemeth', domain: 'default', - style: 'default', modality: 'braille'}); - this.Speech(this.walker); - this.Start(); - }); - }) - return; - } super.Start(); + // let options = this.getOptions(); + // if (!this.init) { + // this.init = true; + // SpeechExplorer.updatePromise = SpeechExplorer.updatePromise.then(async () => { + // return Sre.sreReady() + // .then(() => Sre.setupEngine({locale: options.locale})) + // .then(() => { + // // Important that both are in the same block so speech explorers + // // are restarted sequentially. + // this.Speech(this.walker); + // }) + // .then(() => Sre.setupEngine({automark: false as any, markup: 'none', + // locale: 'nemeth', domain: 'default', + // style: 'default', modality: 'braille'})) + // .then(() => { + // this.speechGenerator.setOptions({automark: false as any, markup: 'none', + // locale: 'nemeth', domain: 'default', + // style: 'default', modality: 'braille'}); + // this.Speech(this.walker); + // this.Start(); + // }); + // }) + // return; + // } // this.speecGhenerator = Sre.getSpeechGenerator('Direct'); // this.speechGenerator.setOptions(options); // this.walker = Sre.getWalker( From 7dec3d75d9c40f175bbefaf1bd958d30d2b6ba1a Mon Sep 17 00:00:00 2001 From: zorkow Date: Fri, 4 Aug 2023 18:23:00 +0200 Subject: [PATCH 30/47] Basics of new key explorer working. --- ts/a11y/explorer/ExplorerPool.ts | 12 +- ts/a11y/explorer/KeyExplorer.ts | 412 +++++++++++++++++++------------ ts/a11y/explorer/Region.ts | 6 +- 3 files changed, 259 insertions(+), 171 deletions(-) diff --git a/ts/a11y/explorer/ExplorerPool.ts b/ts/a11y/explorer/ExplorerPool.ts index 526b9f513..0892af401 100644 --- a/ts/a11y/explorer/ExplorerPool.ts +++ b/ts/a11y/explorer/ExplorerPool.ts @@ -88,7 +88,8 @@ type ExplorerInit = (doc: ExplorerMathDocument, pool: ExplorerPool, let allExplorers: {[options: string]: ExplorerInit} = { speech: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => { let explorer = ke.SpeechExplorer.create( - doc, pool, doc.explorerRegions.speechRegion, node, ...rest) as ke.SpeechExplorer; + doc, pool, doc.explorerRegions.speechRegion, node, + doc.explorerRegions.brailleRegion, doc.explorerRegions.magnifier, rest[0]) as ke.SpeechExplorer; // explorer.speechGenerator.setOptions({ // automark: true as any, markup: 'ssml', // locale: doc.options.sre.locale, domain: doc.options.sre.domain, @@ -100,9 +101,6 @@ let allExplorers: {[options: string]: ExplorerInit} = { // explorer.speechGenerator.setOptions({locale: doc.options.sre.locale}); // } explorer.sound = true; - explorer.showRegion = 'subtitles'; - explorer.newWalker.speechRegion = doc.explorerRegions.speechRegion; - explorer.newWalker.brailleRegion = doc.explorerRegions.brailleRegion; return explorer; }, // braille: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => { @@ -114,8 +112,8 @@ let allExplorers: {[options: string]: ExplorerInit} = { // explorer.showRegion = 'viewBraille'; // return explorer; // }, - keyMagnifier: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => - ke.Magnifier.create(doc, pool, doc.explorerRegions.magnifier, node, ...rest), + // keyMagnifier: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => + // ke.Magnifier.create(doc, pool, doc.explorerRegions.magnifier, node, ...rest), mouseMagnifier: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ..._rest: any[]) => me.ContentHoverer.create(doc, pool, doc.explorerRegions.magnifier, node, (x: HTMLElement) => x.hasAttribute('data-semantic-type'), @@ -235,7 +233,7 @@ export class ExplorerPool { let keyExplorers = []; for (let key of Object.keys(this.explorers)) { let explorer = this.explorers[key]; - if (explorer instanceof ke.AbstractKeyExplorer) { + if (explorer instanceof ke.SpeechExplorer) { explorer.AddEvents(); explorer.stoppable = false; keyExplorers.unshift(explorer); diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 6e2eca388..40392d733 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -23,12 +23,12 @@ */ -import {A11yDocument, Region} from './Region.js'; +import {A11yDocument, Region, HoverRegion, SpeechRegion, LiveRegion} from './Region.js'; import {Explorer, AbstractExplorer} from './Explorer.js'; import {ExplorerPool} from './ExplorerPool.js'; import {Sre} from '../sre.js'; -import { Walker } from './Walker.js'; +// import { Walker } from './Walker.js'; /** @@ -60,7 +60,7 @@ export interface KeyExplorer extends Explorer { * Move made on keypress. * @param key The key code of the pressed key. */ - Move(key: number): void; + Move(event: KeyboardEvent): void; /** * A method that is executed if no move is executed. @@ -70,15 +70,22 @@ export interface KeyExplorer extends Explorer { } +const codeSelector = 'mjx-container[role="application"][data-shellac]'; +const nav = '[role="application"][data-shellac],[role="tree"],[role="group"],[role="treeitem"]'; + +function isCodeBlock(el: HTMLElement) { + return el.matches(codeSelector); +} + /** * @constructor * @extends {AbstractExplorer} * * @template T The type that is consumed by the Region of this explorer. */ -export abstract class AbstractKeyExplorer extends AbstractExplorer implements KeyExplorer { +export class SpeechExplorer extends AbstractExplorer implements KeyExplorer { - public newWalker = new Walker(); + // public newWalker = new Walker(); /** * Flag indicating if the explorer is attached to an object. @@ -98,6 +105,8 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme private eventsAttached: boolean = false; + protected current: HTMLElement = null; + /** * @override */ @@ -106,22 +115,31 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme [ // ['keydown', move], ['keydown', this.KeyDown.bind(this)], - ['click', ((e: MouseEvent) => this.newWalker.click(this.node, e)).bind(this)], + ['click', ((e: MouseEvent) => this.click(this.node, e)).bind(this)], ['focusin', this.FocusIn.bind(this)], ['focusout', this.FocusOut.bind(this)] ]); + public click(snippet: HTMLElement, e: MouseEvent) { + const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; + if (snippet.contains(clicked)) { + const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); + if (prev) { + prev.removeAttribute('tabindex'); + } + clicked.setAttribute('tabindex', '0'); + clicked.focus(); + this.current = clicked; + e.preventDefault(); + } + } + /** * The original tabindex value before explorer was attached. * @type {boolean} */ private oldIndex: number = null; - /** - * @override - */ - public abstract KeyDown(event: KeyboardEvent): void; - /** * @override */ @@ -135,20 +153,6 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme // this.Stop(); } - /** - * @override - */ - public Update(force: boolean = false) { - if (!this.active && !force) return; - this.pool.unhighlight(); - let nodes = this.walker.getFocus(true).getNodes(); - if (!nodes.length) { - this.walker.refocus(); - nodes = this.walker.getFocus().getNodes(); - } - this.pool.highlight(nodes as HTMLElement[]); - } - /** * @override */ @@ -187,21 +191,77 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme /** * @override */ - public Stop() { - if (this.active) { - this.walker.deactivate(); - this.pool.unhighlight(); + public Move(e: KeyboardEvent) { + function nextFocus(): HTMLElement { + function nextSibling(el: HTMLElement): HTMLElement { + const sib = el.nextElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; + } else { + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? nextSibling(sib); + } + } else { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return nextSibling(el.parentElement); + } else { + return null; + } + } + } + + function prevSibling(el: HTMLElement): HTMLElement { + const sib = el.previousElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; + } else { + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? prevSibling(sib); + } + } else { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return prevSibling(el.parentElement); + } else { + return null; + } + } + } + + const target = e.target as HTMLElement; + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + return target.querySelector(nav); + case 'ArrowUp': + e.preventDefault(); + return target.parentElement.closest(nav); + case 'ArrowLeft': + e.preventDefault(); + return prevSibling(target); + case 'ArrowRight': + e.preventDefault(); + return nextSibling(target); + // case 'Esc': + // e.preventDefault(); + // return this.hideRegions(); + default: + return null; + } } - super.Stop(); - } - /** - * @override - */ - public Move(_key: number) { - // // let result = this.walker.move(key); - // let result = false; - // // let result = move(key); + const next = nextFocus(); + + const target = e.target as HTMLElement; + if (next) { + target.removeAttribute('tabindex'); + next.setAttribute('tabindex', '0'); + next.focus(); + this.current = next; + return true; + } + return false; } /** @@ -216,16 +276,6 @@ export abstract class AbstractKeyExplorer extends AbstractExplorer impleme os.stop(ac.currentTime + .05); } -} - - -/** - * Explorer that pushes speech to live region. - * @constructor - * @extends {AbstractKeyExplorer} - */ -export class SpeechExplorer extends AbstractKeyExplorer { - private static updatePromise = Promise.resolve(); /** @@ -256,12 +306,13 @@ export class SpeechExplorer extends AbstractKeyExplorer { */ constructor(public document: A11yDocument, public pool: ExplorerPool, - public region: Region, + public region: SpeechRegion, protected node: HTMLElement, - private mml: string) { - super(document, pool, region, node); - this.newWalker.highlighter = this.highlighter; - this.initWalker(); + public brailleRegion: LiveRegion, + public magnifyRegion: HoverRegion, + private _mml: string) { + super(document, pool, null, node); + // this.initWalker(); } @@ -270,6 +321,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { */ public Start() { if (!this.attached) return; + if (this.active) return; super.Start(); // let options = this.getOptions(); // if (!this.init) { @@ -295,16 +347,26 @@ export class SpeechExplorer extends AbstractKeyExplorer { // }) // return; // } - // this.speecGhenerator = Sre.getSpeechGenerator('Direct'); + // this.speechGenerator = Sre.getSpeechGenerator('Direct'); // this.speechGenerator.setOptions(options); // this.walker = Sre.getWalker( // 'table', this.node, this.speechGenerator, this.highlighter, this.mml); // this.walker.activate(); - // this.Update(); - // if (this.document.options.a11y[this.showRegion]) { - // SpeechExplorer.updatePromise.then( - // () => this.region.Show(this.node, this.highlighter)); - // } + if (this.document.options.a11y.subtitles) { + console.log(0); + SpeechExplorer.updatePromise.then( + () => this.region.Show(this.node, this.highlighter)) + } + if (this.document.options.a11y.viewBraille) { + console.log(1); + SpeechExplorer.updatePromise.then( + () => this.brailleRegion.Show(this.node, this.highlighter)) + } + if (this.document.options.a11y.keyMagnifier) { + console.log(2); + this.magnifyRegion.Show(this.node, this.highlighter); + } + this.Update(); // this.restarted = true; } @@ -317,28 +379,40 @@ export class SpeechExplorer extends AbstractKeyExplorer { // Make that cleaner and remove force as it is not really used! let noUpdate = force; force = false; - super.Update(force); - let options = this.speechGenerator.getOptions(); + if (!this.active && !force) return; + this.pool.unhighlight(); + // let nodes = this.walker.getFocus(true).getNodes(); + // if (!nodes.length) { + // this.walker.refocus(); + // nodes = this.walker.getFocus().getNodes(); + // } + this.pool.highlight([this.current]); + this.region.Update(this.current.getAttribute('aria-label')); + this.brailleRegion.Update(this.current.getAttribute('aria-braillelabel')); + console.log(this.magnifyRegion); + this.magnifyRegion.Update(this.current); + // let options = this.speechGenerator.getOptions(); // This is a necessary in case speech options have changed via keypress // during walking. - if (options.modality === 'speech') { - this.document.options.sre.domain = options.domain; - this.document.options.sre.style = options.style; - this.document.options.a11y.speechRules = - options.domain + '-' + options.style; - } - SpeechExplorer.updatePromise = SpeechExplorer.updatePromise.then(async () => { - return Sre.sreReady() - .then(() => Sre.setupEngine({markup: options.markup, - modality: options.modality, - locale: options.locale})) - .then(() => { - if (!noUpdate) { - let speech = this.walker.speech(); - this.region.Update(speech); - } - }); - }); + // if (options.modality === 'speech') { + // this.document.options.sre.domain = options.domain; + // this.document.options.sre.style = options.style; + // this.document.options.a11y.speechRules = + // options.domain + '-' + options.style; + // } + // Ensure this autovoicing is retained later: + // SpeechExplorer.updatePromise = SpeechExplorer.updatePromise.then(async () => { + // return Sre.sreReady() + // .then(() => Sre.setupEngine({markup: options.markup, + // modality: options.modality, + // locale: options.locale})) + // .then(() => { + // if (!noUpdate) { + // let speech = this.walker.speech(); + // this.region.Update(speech); + // } + // }); + // }); } @@ -362,9 +436,8 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @override */ public KeyDown(event: KeyboardEvent) { - console.log('active: ' + this.active); const code = event.key; - this.walker.modifier = event.shiftKey; + // this.walker.modifier = event.shiftKey; if (code === 'Control') { speechSynthesis.cancel(); return; @@ -372,24 +445,26 @@ export class SpeechExplorer extends AbstractKeyExplorer { if (code === 'Escape') { this.Stop(); this.stopEvent(event); - this.newWalker.HideRegions(); return; } let result = null; if (this.active) { - result = this.newWalker.move(event); - this.newWalker.ShowRegions(this.node, this.highlighter); + result = this.Move(event); this.stopEvent(event); } if (result) { - // this.region.Update('hello'); + this.Update(); return; } if (!result && this.sound) { this.NoMove(); } // - if (this.triggerLink(code)) return; + if (this.triggerLink(code)) { + this.Stop() + return; + } + // this.stopEvent(event); if (code === 'Space' && event.shiftKey || code === 'Enter') { this.Start(); @@ -402,10 +477,10 @@ export class SpeechExplorer extends AbstractKeyExplorer { * @param {number} code The keycode of the last key pressed. */ protected triggerLink(code: string) { - if (code !== 'Enter') { + if (code !== 'Enter' || !this.active) { return false; } - let node = this.walker.getFocus().getNodes()?.[0]; + let node = this.current; let focus = node?. getAttribute('data-semantic-postfix')?. match(/(^| )link($| )/); @@ -419,12 +494,12 @@ export class SpeechExplorer extends AbstractKeyExplorer { /** * Initialises the Sre walker. */ - private initWalker() { - this.speechGenerator = Sre.getSpeechGenerator('Tree'); - let dummy = Sre.getWalker( - 'dummy', this.node, this.speechGenerator, this.highlighter, this.mml); - this.walker = dummy; - } + // private initWalker() { + // this.speechGenerator = Sre.getSpeechGenerator('Tree'); + // let dummy = Sre.getWalker( + // 'dummy', this.node, this.speechGenerator, this.highlighter, this.mml); + // this.walker = dummy; + // } /** * Retrieves the speech options to sync with document options. @@ -446,78 +521,93 @@ export class SpeechExplorer extends AbstractKeyExplorer { return options; } -} - - -/** - * Explorer that magnifies what is currently explored. Uses a hover region. - * @constructor - * @extends {AbstractKeyExplorer} - */ -export class Magnifier extends AbstractKeyExplorer { - - /** - * @constructor - * @extends {AbstractKeyExplorer} - */ - constructor(public document: A11yDocument, - public pool: ExplorerPool, - public region: Region, - protected node: HTMLElement, - private mml: string) { - super(document, pool, region, node); - this.walker = Sre.getWalker( - 'table', this.node, Sre.getSpeechGenerator('Dummy'), - this.highlighter, this.mml); - } - /** * @override */ - public Update(force: boolean = false) { - super.Update(force); - this.showFocus(); - } - - /** - * @override - */ - public Start() { - super.Start(); - if (!this.attached) return; - this.region.Show(this.node, this.highlighter); - this.walker.activate(); - this.Update(); + public Stop() { + if (this.active) { + this.pool.unhighlight(); + this.magnifyRegion.Hide(); + this.region.Hide(); + this.brailleRegion.Hide(); + } + super.Stop(); } - /** - * Shows the nodes that are currently focused. - */ - private showFocus() { - let node = this.walker.getFocus().getNodes()[0] as HTMLElement; - this.region.Show(node, this.highlighter); - } - /** - * @override - */ - public KeyDown(event: KeyboardEvent) { - const code = event.keyCode; - this.walker.modifier = event.shiftKey; - if (code === 27) { - this.Stop(); - this.stopEvent(event); - return; - } - if (this.active && code !== 13) { - this.Move(code); - this.stopEvent(event); - return; - } - if (code === 32 && event.shiftKey || code === 13) { - this.Start(); - this.stopEvent(event); - } - } } + + +/** + * Explorer that magnifies what is currently explored. Uses a hover region. + * @constructor + * @extends {AbstractKeyExplorer} + */ +// export class Magnifier extends AbstractKeyExplorer { + +// /** +// * @constructor +// * @extends {AbstractKeyExplorer} +// */ +// constructor(public document: A11yDocument, +// public pool: ExplorerPool, +// public region: Region, +// protected node: HTMLElement, +// private mml: string) { +// super(document, pool, region, node); +// this.walker = Sre.getWalker( +// 'table', this.node, Sre.getSpeechGenerator('Dummy'), +// this.highlighter, this.mml); +// } + +// /** +// * @override +// */ +// public Update(force: boolean = false) { +// super.Update(force); +// this.showFocus(); +// } + +// /** +// * @override +// */ +// public Start() { +// super.Start(); +// if (!this.attached) return; +// this.region.Show(this.node, this.highlighter); +// this.walker.activate(); +// this.Update(); +// } + +// /** +// * Shows the nodes that are currently focused. +// */ +// private showFocus() { +// let node = this.walker.getFocus().getNodes()[0] as HTMLElement; +// this.region.Show(node, this.highlighter); +// } + +// /** +// * @override +// */ +// public KeyDown(event: KeyboardEvent) { +// const code = event.keyCode; +// this.walker.modifier = event.shiftKey; +// if (code === 27) { +// this.Stop(); +// this.stopEvent(event); +// return; +// } +// if (this.active && code !== 13) { +// this.Move(code); +// this.stopEvent(event); +// return; +// } +// if (code === 32 && event.shiftKey || code === 13) { +// this.Start(); +// this.stopEvent(event); +// } +// } + +// } diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index f26f6a92e..0732a25de 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -279,8 +279,6 @@ export class StringRegion extends AbstractRegion { * @override */ public Update(speech: string) { - console.log(6); - console.log(speech); this.inner.textContent = ''; this.inner.textContent = speech; } @@ -413,10 +411,12 @@ export class SpeechRegion extends LiveRegion { * @override */ public Update(speech: string) { + console.log('In Speech region: ' + speech); + // Temporarily removed! // console.log(speech); // this.active = this.document.options.a11y.voicing && // !!speechSynthesis.getVoices().length; - // speechSynthesis.cancel(); + // speechSynthesis.cancel(); // this.clear = true; // let [text, ssml] = ssmlParsing(speech); // console.log(27); From 337d4a86051d44b88351e2cd32bea2fdd3b63482 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sat, 5 Aug 2023 14:27:54 +0200 Subject: [PATCH 31/47] Link triggering on click. --- ts/a11y/explorer/KeyExplorer.ts | 35 +++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 40392d733..235642d6e 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -115,21 +115,24 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore [ // ['keydown', move], ['keydown', this.KeyDown.bind(this)], - ['click', ((e: MouseEvent) => this.click(this.node, e)).bind(this)], + ['click', this.Click.bind(this)], ['focusin', this.FocusIn.bind(this)], ['focusout', this.FocusOut.bind(this)] ]); - public click(snippet: HTMLElement, e: MouseEvent) { + public Click(e: MouseEvent) { const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; - if (snippet.contains(clicked)) { - const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); + if (this.node.contains(clicked)) { + const prev = this.node.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); if (prev) { prev.removeAttribute('tabindex'); } clicked.setAttribute('tabindex', '0'); clicked.focus(); this.current = clicked; + if (!this.triggerLinkMouse()) { + this.Start() + } e.preventDefault(); } } @@ -460,7 +463,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore this.NoMove(); } // - if (this.triggerLink(code)) { + if (this.triggerLinkKeyboard(code)) { this.Stop() return; } @@ -476,11 +479,15 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore * Programmatically triggers a link if the focused node contains one. * @param {number} code The keycode of the last key pressed. */ - protected triggerLink(code: string) { + protected triggerLinkKeyboard(code: string) { if (code !== 'Enter' || !this.active) { return false; } let node = this.current; + return this.triggerLink(node); + } + + protected triggerLink(node: HTMLElement) { let focus = node?. getAttribute('data-semantic-postfix')?. match(/(^| )link($| )/); @@ -491,6 +498,22 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore return false; } + + /** + * Programmatically triggers a link if the clicked mouse contains one. + */ + protected triggerLinkMouse() { + let node = this.current; + while (node && node !== this.node) { + if (this.triggerLink(node)) { + return true; + } + node = node.parentNode as HTMLElement; + } + return false; + } + + /** * Initialises the Sre walker. */ From e1cfff030cfbca3f80090c2d9fa2afac8b2d2329 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sat, 5 Aug 2023 17:14:46 +0200 Subject: [PATCH 32/47] Corrects keyboard triggering of links. --- ts/a11y/explorer/KeyExplorer.ts | 69 +++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 235642d6e..738cd86a1 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -71,7 +71,7 @@ export interface KeyExplorer extends Explorer { const codeSelector = 'mjx-container[role="application"][data-shellac]'; -const nav = '[role="application"][data-shellac],[role="tree"],[role="group"],[role="treeitem"]'; +const nav = '[role="tree"],[role="group"],[role="treeitem"]'; function isCodeBlock(el: HTMLElement) { return el.matches(codeSelector); @@ -383,7 +383,9 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore let noUpdate = force; force = false; if (!this.active && !force) return; + console.log(6); this.pool.unhighlight(); + console.log(7); // let nodes = this.walker.getFocus(true).getNodes(); // if (!nodes.length) { // this.walker.refocus(); @@ -450,43 +452,60 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore this.stopEvent(event); return; } + if (code === 'Enter') { + if (!this.active && event.target instanceof HTMLAnchorElement) { + event.target.dispatchEvent(new MouseEvent('click')); + this.stopEvent(event); + return; + } + if (this.active && this.triggerLinkKeyboard(event)) { + this.Stop() + this.stopEvent(event); + return; + } + if (!this.active) { + if (!this.current) { + this.current = this.node.querySelector('[role="tree"]'); + this.current.setAttribute('tabindex', '0'); + this.current.focus(); + } + this.Start(); + this.stopEvent(event); + return; + } + } let result = null; if (this.active) { result = this.Move(event); this.stopEvent(event); - } - if (result) { - this.Update(); - return; - } - if (!result && this.sound) { - this.NoMove(); - } - // - if (this.triggerLinkKeyboard(code)) { - this.Stop() - return; - } - - // this.stopEvent(event); - if (code === 'Space' && event.shiftKey || code === 'Enter') { - this.Start(); - this.stopEvent(event); + if (result) { + this.Update(); + return; + } + if (this.sound) { + this.NoMove(); + } } } /** * Programmatically triggers a link if the focused node contains one. - * @param {number} code The keycode of the last key pressed. + * @param {KeyboardEvent} event The keyboard event for the last keydown event. */ - protected triggerLinkKeyboard(code: string) { - if (code !== 'Enter' || !this.active) { + protected triggerLinkKeyboard(event: KeyboardEvent) { + if (event.code !== 'Enter') { return false; } - let node = this.current; - return this.triggerLink(node); + if (!this.current) { + if (event.target instanceof HTMLAnchorElement) { + event.target.dispatchEvent(new MouseEvent('click')); + return true; + } + return false; + } + return this.triggerLink(this.current); } - + protected triggerLink(node: HTMLElement) { let focus = node?. getAttribute('data-semantic-postfix')?. From 42eb5b20be806e1cb3950204809ba3e338c735cc Mon Sep 17 00:00:00 2001 From: zorkow Date: Sat, 5 Aug 2023 17:51:54 +0200 Subject: [PATCH 33/47] Improved tabindex and focus handling. --- ts/a11y/explorer/KeyExplorer.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 738cd86a1..bb6163185 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -23,7 +23,7 @@ */ -import {A11yDocument, Region, HoverRegion, SpeechRegion, LiveRegion} from './Region.js'; +import {A11yDocument, HoverRegion, SpeechRegion, LiveRegion} from './Region.js'; import {Explorer, AbstractExplorer} from './Explorer.js'; import {ExplorerPool} from './ExplorerPool.js'; import {Sre} from '../sre.js'; @@ -83,7 +83,7 @@ function isCodeBlock(el: HTMLElement) { * * @template T The type that is consumed by the Region of this explorer. */ -export class SpeechExplorer extends AbstractExplorer implements KeyExplorer { +export class SpeechExplorer extends AbstractExplorer implements KeyExplorer { // public newWalker = new Walker(); @@ -107,6 +107,8 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore protected current: HTMLElement = null; + private move = false; + /** * @override */ @@ -127,8 +129,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore if (prev) { prev.removeAttribute('tabindex'); } - clicked.setAttribute('tabindex', '0'); - clicked.focus(); this.current = clicked; if (!this.triggerLinkMouse()) { this.Start() @@ -153,7 +153,10 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore * @override */ public FocusOut(_event: FocusEvent) { - // this.Stop(); + console.log(19); + if (!this.move) { + this.Stop(); + } } /** @@ -195,6 +198,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore * @override */ public Move(e: KeyboardEvent) { + console.log(22); function nextFocus(): HTMLElement { function nextSibling(el: HTMLElement): HTMLElement { const sib = el.nextElementSibling as HTMLElement; @@ -254,6 +258,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore } } + this.move = true; const next = nextFocus(); const target = e.target as HTMLElement; @@ -262,8 +267,10 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore next.setAttribute('tabindex', '0'); next.focus(); this.current = next; + this.move = false; return true; } + this.move = false; return false; } @@ -325,6 +332,8 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore public Start() { if (!this.attached) return; if (this.active) return; + this.current.setAttribute('tabindex', '0'); + this.current.focus(); super.Start(); // let options = this.getOptions(); // if (!this.init) { @@ -356,17 +365,14 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore // 'table', this.node, this.speechGenerator, this.highlighter, this.mml); // this.walker.activate(); if (this.document.options.a11y.subtitles) { - console.log(0); SpeechExplorer.updatePromise.then( () => this.region.Show(this.node, this.highlighter)) } if (this.document.options.a11y.viewBraille) { - console.log(1); SpeechExplorer.updatePromise.then( () => this.brailleRegion.Show(this.node, this.highlighter)) } if (this.document.options.a11y.keyMagnifier) { - console.log(2); this.magnifyRegion.Show(this.node, this.highlighter); } this.Update(); @@ -383,9 +389,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore let noUpdate = force; force = false; if (!this.active && !force) return; - console.log(6); this.pool.unhighlight(); - console.log(7); // let nodes = this.walker.getFocus(true).getNodes(); // if (!nodes.length) { // this.walker.refocus(); @@ -394,7 +398,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore this.pool.highlight([this.current]); this.region.Update(this.current.getAttribute('aria-label')); this.brailleRegion.Update(this.current.getAttribute('aria-braillelabel')); - console.log(this.magnifyRegion); this.magnifyRegion.Update(this.current); // let options = this.speechGenerator.getOptions(); // This is a necessary in case speech options have changed via keypress @@ -466,8 +469,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore if (!this.active) { if (!this.current) { this.current = this.node.querySelector('[role="tree"]'); - this.current.setAttribute('tabindex', '0'); - this.current.focus(); } this.Start(); this.stopEvent(event); @@ -505,7 +506,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore } return this.triggerLink(this.current); } - + protected triggerLink(node: HTMLElement) { let focus = node?. getAttribute('data-semantic-postfix')?. @@ -532,7 +533,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore return false; } - + /** * Initialises the Sre walker. */ @@ -568,6 +569,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplore */ public Stop() { if (this.active) { + this.current.removeAttribute('tabindex'); this.pool.unhighlight(); this.magnifyRegion.Hide(); this.region.Hide(); From cd5199d8042018d8501811d5d1ab03b75d2ff224 Mon Sep 17 00:00:00 2001 From: zorkow Date: Thu, 10 Aug 2023 16:59:39 +0200 Subject: [PATCH 34/47] Removes unused Walker. --- ts/a11y/explorer/Walker.ts | 208 ------------------------------------- ts/a11y/semantic-enrich.ts | 2 +- 2 files changed, 1 insertion(+), 209 deletions(-) delete mode 100644 ts/a11y/explorer/Walker.ts diff --git a/ts/a11y/explorer/Walker.ts b/ts/a11y/explorer/Walker.ts deleted file mode 100644 index 20b4a29c9..000000000 --- a/ts/a11y/explorer/Walker.ts +++ /dev/null @@ -1,208 +0,0 @@ -/************************************************************* - * - * Copyright (c) 2009-2023 The MathJax Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -/** - * @fileoverview Aria Tree Walker. - * - * @author v.sorge@mathjax.org (Volker Sorge) - */ - -import { SpeechRegion, LiveRegion } from './Region.js'; -import {Sre} from '../sre.js'; - -// Based on the shellac walker. - -const codeSelector = 'mjx-container[role="application"][data-shellac]'; -const nav = '[role="application"][data-shellac],[role="tree"],[role="group"],[role="treeitem"]'; - -function isCodeBlock(el: HTMLElement) { - return el.matches(codeSelector); -} - -type rgbColor = {red: number, green: number, blue: number}; -type channelColor = {name: string, alpha: number}; - -export class Highlighter { - - public foreground: string; - public background: string; - - public static namedColors: { [key: string]: rgbColor } = { - red: { red: 255, green: 0, blue: 0 }, - green: { red: 0, green: 255, blue: 0 }, - blue: { red: 0, green: 0, blue: 255 }, - yellow: { red: 255, green: 255, blue: 0 }, - cyan: { red: 0, green: 255, blue: 255 }, - magenta: { red: 255, green: 0, blue: 255 }, - white: { red: 255, green: 255, blue: 255 }, - black: { red: 0, green: 0, blue: 0 } - }; - - constructor(foreground: channelColor, background: channelColor) { - this.foreground = this.makeColor(foreground); - this.background = this.makeColor(background); - } - - // public highlight(node: HTMLElement) { - - // } - - private makeColor({name: name, alpha: alpha}: channelColor) { - let {red: red, green: green, blue: blue} = - Highlighter.namedColors[name] || Highlighter.namedColors['blue']; - return `rgba(${red},${green},${blue},${alpha})`; - } - -} - -export class Walker { - - public shown: boolean = false; - public speechRegion: SpeechRegion; - public brailleRegion: LiveRegion; - public highlighter: Sre.highlighter; - - public click(snippet: HTMLElement, e: MouseEvent) { - const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; - if (snippet.contains(clicked)) { - const prev = snippet.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); - if (prev) { - prev.removeAttribute('tabindex'); - } - clicked.setAttribute('tabindex', '0'); - clicked.focus(); - this.UpdateRegions(clicked); - e.preventDefault(); - } - } - - public move(e: KeyboardEvent) { - - function nextFocus(): HTMLElement { - function nextSibling(el: HTMLElement): HTMLElement { - const sib = el.nextElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; - } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? nextSibling(sib); - } - } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return nextSibling(el.parentElement); - } else { - return null; - } - } - } - - function prevSibling(el: HTMLElement): HTMLElement { - const sib = el.previousElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; - } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? prevSibling(sib); - } - } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return prevSibling(el.parentElement); - } else { - return null; - } - } - } - - const target = e.target as HTMLElement; - switch (e.key) { - case 'ArrowDown': - e.preventDefault(); - return target.querySelector(nav); - case 'ArrowUp': - e.preventDefault(); - return target.parentElement.closest(nav); - case 'ArrowLeft': - e.preventDefault(); - return prevSibling(target); - case 'ArrowRight': - e.preventDefault(); - return nextSibling(target); - // case 'Esc': - // e.preventDefault(); - // return this.hideRegions(); - default: - return null; - } - } - - const next = nextFocus(); - - const target = e.target as HTMLElement; - if (next) { - target.removeAttribute('tabindex'); - next.setAttribute('tabindex', '0'); - next.focus(); - this.UpdateHighlight(target, next); - this.UpdateRegions(next); - return [next]; - } - return false; - } - - private info: Highlight; - - private UpdateHighlight(_target: HTMLElement, next: HTMLElement) { - if (this.info) { - (this.highlighter as any).unhighlightNode(this.info); - } - this.info = (this.highlighter as any).highlightNode(next); - } - - private UpdateRegions(element: HTMLElement) { - this.speechRegion.Update(element.getAttribute('aria-label')); - this.brailleRegion.Update(element.getAttribute('aria-braillelabel')); - } - - public ShowRegions(element: HTMLElement, highlighter: Sre.highlighter) { - if (!this.shown) { - this.speechRegion.Show(element, highlighter); - this.brailleRegion.Show(element, highlighter); - } - this.shown = true; - } - - public HideRegions() { - if (this.shown) { - this.speechRegion.Hide(); - this.brailleRegion.Hide(); - } - this.shown = false; - } -} - -export interface Highlight { - node: HTMLElement; - opacity?: string; - background?: string; - foreground?: string; - // The following is for the CSS highlighter - box?: HTMLElement; - position?: string; -} diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 206122c26..e9d6cf2cc 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -397,7 +397,7 @@ export function EnrichedMathDocumentMixin Date: Fri, 11 Aug 2023 12:56:52 +0200 Subject: [PATCH 35/47] Refactor set aria function. --- ts/a11y/SpeechUtil.ts | 29 +++++++++++++++++++++++++++-- ts/a11y/semantic-enrich.ts | 34 +++------------------------------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/ts/a11y/SpeechUtil.ts b/ts/a11y/SpeechUtil.ts index a1f03a6ce..7873c8430 100644 --- a/ts/a11y/SpeechUtil.ts +++ b/ts/a11y/SpeechUtil.ts @@ -1,5 +1,5 @@ import {MmlNode} from '../core/MmlTree/MmlNode.js'; -import {Sre} from './sre.js'; +// import {Sre} from './sre.js'; const ProsodyKeys = [ 'pitch', 'rate', 'volume' ]; @@ -138,7 +138,12 @@ function extractProsody(attr: string) { return [match[1], match[2]]; } -export function getLabel(node: MmlNode, sep: string = ' ') { +/** + * Computes the aria-label from the node. + * @param {MmlNode} node The Math element. + * @param {string=} sep The speech separator. Defaults to space. + */ +function getLabel(node: MmlNode, sep: string = ' ') { const attributes = node.attributes; const speech = attributes.getExplicit('data-semantic-speech') as string; if (!speech) { @@ -167,3 +172,23 @@ export function buildSpeech(speech: string, locale: string = 'en', `${speech}`+ ''); } + +/** + * Retrieve and sets aria and braille labels recursively. + * @param {MmlNode} node The root node to search from. + */ +export function setAria(node: MmlNode, locale: string) { + const attributes = node.attributes; + if (!attributes) return; + const speech = getLabel(node); + if (speech) { + attributes.set('aria-label', buildSpeech(speech, locale)[0]); + } + const braille = node.attributes.getExplicit('data-semantic-braille') as string; + if (braille) { + attributes.set('aria-braillelabel', braille); + } + for (let child of node.childNodes) { + setAria(child, locale); + } +} diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index e9d6cf2cc..45e8ecdb3 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -30,14 +30,13 @@ import {MathML} from '../input/mathml.js'; import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js'; import {OptionList, expandable} from '../util/Options.js'; import {Sre} from './sre.js'; -import { buildSpeech, getLabel } from './SpeechUtil.js'; +import { buildSpeech, setAria } from './SpeechUtil.js'; /*==========================================================================*/ /** * The current speech setting for Sre */ -// let currentSpeech = 'none'; let currentLocale = 'none'; let currentBraille = 'none'; @@ -169,12 +168,6 @@ export function EnrichedMathItemMixin= STATE.ENRICHED) return; if (!this.isEscaped && (document.options.enableEnrichment || force)) { // TODO: Sort out the loading of the locales better - // if (document.options.sre.speech !== currentSpeech) { - // currentSpeech = document.options.sre.speech; - // mathjax.retryAfter( - // Sre.setupEngine(document.options.sre).then( - // () => Sre.sreReady())); - // } if (document.options.sre.locale !== currentLocale) { currentLocale = document.options.sre.locale; // TODO: Sort out the loading of the locales better @@ -228,7 +221,7 @@ export function EnrichedMathItemMixin) { + console.log(0); if (this.state() >= STATE.ATTACHSPEECH) return; const attributes = this.root.attributes; const speech = (attributes.get('aria-label') || this.label); @@ -302,28 +296,6 @@ export function EnrichedMathItemMixin Date: Fri, 11 Aug 2023 14:04:32 +0200 Subject: [PATCH 36/47] Refactoring speech computation to utilities. --- ts/a11y/explorer.ts | 3 ++- ts/a11y/explorer/KeyExplorer.ts | 6 +---- ts/a11y/semantic-enrich.ts | 40 +++++++++++++-------------------- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts index 160e71bc6..8e8ba23f8 100644 --- a/ts/a11y/explorer.ts +++ b/ts/a11y/explorer.ts @@ -199,7 +199,8 @@ export function ExplorerMathDocumentMixin implements KeyExplo * @override */ public FocusOut(_event: FocusEvent) { - console.log(19); if (!this.move) { this.Stop(); } @@ -168,8 +167,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo this.oldIndex = this.node.tabIndex; this.node.tabIndex = 0; this.node.setAttribute('role', 'application'); - // TODO: Get rid of this eventually! - this.node.setAttribute('data-shellac', ''); } /** @@ -198,7 +195,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo * @override */ public Move(e: KeyboardEvent) { - console.log(22); function nextFocus(): HTMLElement { function nextSibling(el: HTMLElement): HTMLElement { const sib = el.nextElementSibling as HTMLElement; diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 45e8ecdb3..89230a1b6 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -62,7 +62,7 @@ newState('ATTACHSPEECH', 155); export class enrichVisitor extends SerializedMmlVisitor { protected mactionId: number; - + public visitTree(node: MmlNode, math?: MathItem) { this.mactionId = 1; const mml = super.visitTree(node); @@ -172,23 +172,15 @@ export function EnrichedMathItemMixin { return Sre.sreReady(); })); } if (document.options.sre.braille !== currentBraille) { currentBraille = document.options.sre.braille; - // TODO: Sort out the loading of the locales better mathjax.retryAfter( - Sre.setupEngine({ - locale: document.options.sre.braille, - domain: 'default', // speech rules domain - style: 'default', // speech rules style - modality: 'braille', - markup: 'none', - }) - .then(() => { - Sre.sreReady();})); + Sre.setupEngine({locale: document.options.sre.braille}) + .then(() => Sre.sreReady())); } const math = new document.options.MathItem('', MmlJax); try { @@ -198,19 +190,23 @@ export function EnrichedMathItemMixin) { - console.log(0); if (this.state() >= STATE.ATTACHSPEECH) return; const attributes = this.root.attributes; const speech = (attributes.get('aria-label') || this.label); @@ -367,15 +362,12 @@ export function EnrichedMathDocumentMixin Date: Sat, 12 Aug 2023 13:09:14 +0200 Subject: [PATCH 37/47] Refactoring moves to class level methods. --- ts/a11y/explorer.ts | 2 +- ts/a11y/explorer/ExplorerPool.ts | 9 +-- ts/a11y/explorer/KeyExplorer.ts | 111 +++++++++++++++---------------- ts/a11y/explorer/Region.ts | 5 +- 4 files changed, 61 insertions(+), 66 deletions(-) diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts index 8e8ba23f8..bacecbd83 100644 --- a/ts/a11y/explorer.ts +++ b/ts/a11y/explorer.ts @@ -123,7 +123,7 @@ export function ExplorerMathItemMixin>( if (!this.explorers) { this.explorers = new ExplorerPool(); } - this.explorers.init(document, node, mml); + this.explorers.init(document, node, mml, this); } this.state(STATE.EXPLORER); } diff --git a/ts/a11y/explorer/ExplorerPool.ts b/ts/a11y/explorer/ExplorerPool.ts index 0892af401..8d3ae8c50 100644 --- a/ts/a11y/explorer/ExplorerPool.ts +++ b/ts/a11y/explorer/ExplorerPool.ts @@ -23,7 +23,7 @@ */ import {LiveRegion, SpeechRegion, ToolTip, HoverRegion} from './Region.js'; -import type { ExplorerMathDocument } from '../explorer.js'; +import type { ExplorerMathDocument, ExplorerMathItem } from '../explorer.js'; import {Explorer} from './Explorer.js'; import * as ke from './KeyExplorer.js'; @@ -89,7 +89,7 @@ let allExplorers: {[options: string]: ExplorerInit} = { speech: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => { let explorer = ke.SpeechExplorer.create( doc, pool, doc.explorerRegions.speechRegion, node, - doc.explorerRegions.brailleRegion, doc.explorerRegions.magnifier, rest[0]) as ke.SpeechExplorer; + doc.explorerRegions.brailleRegion, doc.explorerRegions.magnifier, rest[0], rest[1]) as ke.SpeechExplorer; // explorer.speechGenerator.setOptions({ // automark: true as any, markup: 'ssml', // locale: doc.options.sre.locale, domain: doc.options.sre.domain, @@ -212,13 +212,14 @@ export class ExplorerPool { * @param mml The corresponding Mathml node as a string. */ public init(document: ExplorerMathDocument, - node: HTMLElement, mml: string) { + node: HTMLElement, mml: string, + item: ExplorerMathItem) { this.document = document; this.mml = mml; this.node = node; this.setPrimaryHighlighter(); for (let key of Object.keys(allExplorers)) { - this.explorers[key] = allExplorers[key](this.document, this, this.node, this.mml); + this.explorers[key] = allExplorers[key](this.document, this, this.node, this.mml, item); } this.setSecondaryHighlighter(); this.attach(); diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index fd6000e73..1152b9283 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -24,8 +24,10 @@ import {A11yDocument, HoverRegion, SpeechRegion, LiveRegion} from './Region.js'; +import type { ExplorerMathItem } from '../explorer.js'; import {Explorer, AbstractExplorer} from './Explorer.js'; import {ExplorerPool} from './ExplorerPool.js'; +import {MmlNode} from '../../core/MmlTree/MmlNode.js'; import {Sre} from '../sre.js'; // import { Walker } from './Walker.js'; @@ -191,73 +193,64 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo this.attached = false; } - /** - * @override - */ - public Move(e: KeyboardEvent) { - function nextFocus(): HTMLElement { - function nextSibling(el: HTMLElement): HTMLElement { - const sib = el.nextElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; - } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? nextSibling(sib); - } + protected nextSibling(el: HTMLElement): HTMLElement { + const sib = el.nextElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return nextSibling(el.parentElement); - } else { - return null; - } + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? this.nextSibling(sib); } - } - - function prevSibling(el: HTMLElement): HTMLElement { - const sib = el.previousElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; - } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? prevSibling(sib); - } + } else { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return this.nextSibling(el.parentElement); } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return prevSibling(el.parentElement); - } else { - return null; - } + return null; } } + } - const target = e.target as HTMLElement; - switch (e.key) { - case 'ArrowDown': - e.preventDefault(); - return target.querySelector(nav); - case 'ArrowUp': - e.preventDefault(); - return target.parentElement.closest(nav); - case 'ArrowLeft': - e.preventDefault(); - return prevSibling(target); - case 'ArrowRight': - e.preventDefault(); - return nextSibling(target); - // case 'Esc': - // e.preventDefault(); - // return this.hideRegions(); - default: + protected prevSibling(el: HTMLElement): HTMLElement { + const sib = el.previousElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; + } else { + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? this.prevSibling(sib); + } + } else { + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return this.prevSibling(el.parentElement); + } else { return null; + } } } + protected moves: Map HTMLElement | null> = new Map([ + ['ArrowDown', (node: HTMLElement) => node.querySelector(nav)], + ['ArrowUp', (node: HTMLElement) => node.parentElement.closest(nav)], + ['ArrowLeft', this.prevSibling], + ['ArrowRight', this.nextSibling], + ['>', (_node: HTMLElement) => { + return null; + }], + ]); + + /** + * @override + */ + public Move(e: KeyboardEvent) { this.move = true; - const next = nextFocus(); - const target = e.target as HTMLElement; + const move = this.moves.get(e.key); + let next = null; + if (move) { + e.preventDefault(); + next = move(target); + } if (next) { target.removeAttribute('tabindex'); next.setAttribute('tabindex', '0'); @@ -296,7 +289,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo */ public showRegion: string = 'subtitles'; - private init: boolean = false; + // private init: boolean = false; /** * Flag in case the start method is triggered before the walker is fully @@ -316,7 +309,9 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo protected node: HTMLElement, public brailleRegion: LiveRegion, public magnifyRegion: HoverRegion, - private _mml: string) { + _mml: MmlNode, + private item: ExplorerMathItem + ) { super(document, pool, null, node); // this.initWalker(); } @@ -545,7 +540,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo * @return {{[key: string]: string}} The options settings for the speech * generator. */ - private getOptions(): {[key: string]: string} { + protected getOptions(): {[key: string]: string} { let options = this.speechGenerator.getOptions(); let sreOptions = this.document.options.sre; if (options.modality === 'speech' && diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index 0732a25de..d0672167f 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -26,7 +26,7 @@ import {MathDocument} from '../../core/MathDocument.js'; import {CssStyles} from '../../util/StyleList.js'; import {Sre} from '../sre.js'; -import {SsmlElement, ssmlParsing} from '../SpeechUtil.js'; +import {SsmlElement} from '../SpeechUtil.js'; export type A11yDocument = MathDocument; @@ -411,7 +411,6 @@ export class SpeechRegion extends LiveRegion { * @override */ public Update(speech: string) { - console.log('In Speech region: ' + speech); // Temporarily removed! // console.log(speech); // this.active = this.document.options.a11y.voicing && @@ -433,7 +432,7 @@ export class SpeechRegion extends LiveRegion { * @param {SsmlElement[]} ssml The list of ssml annotations. * @param {string} locale The locale to use. */ - private makeUtterances(ssml: SsmlElement[], locale: string) { + protected makeUtterances(ssml: SsmlElement[], locale: string) { let utterance = null; for (let utter of ssml) { if (utter.mark) { From 44db9697470b3030f85dc8b6304015cc21efa175 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sat, 12 Aug 2023 13:44:35 +0200 Subject: [PATCH 38/47] Better setting of speech and braille. --- ts/a11y/semantic-enrich.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 89230a1b6..211a3afef 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -136,8 +136,6 @@ export function EnrichedMathItemMixin) { if (this.state() >= STATE.ATTACHSPEECH) return; const attributes = this.root.attributes; - const speech = (attributes.get('aria-label') || this.label); - const braille = (attributes.get('aria-braillelabel') || this.braillelabel); + const speech = (attributes.get('aria-label') || this.outputData.speech); + const braille = (attributes.get('aria-braillelabel') || this.outputData.braille); if (!speech && !braille) { this.state(STATE.ATTACHSPEECH); return; From 39168e0f0afb13ae5e3ffc85fa17fc8929636479 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sun, 13 Aug 2023 09:06:41 +0200 Subject: [PATCH 39/47] Key explorer cleanup. --- ts/a11y/explorer/KeyExplorer.ts | 144 ++++++-------------------------- 1 file changed, 26 insertions(+), 118 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 1152b9283..77004674e 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -194,46 +194,40 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo } protected nextSibling(el: HTMLElement): HTMLElement { - const sib = el.nextElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; - } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? this.nextSibling(sib); - } - } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return this.nextSibling(el.parentElement); - } else { - return null; - } - } + const sib = el.nextElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; + } + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? this.nextSibling(sib); } + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return this.nextSibling(el.parentElement); + } + return null; + } protected prevSibling(el: HTMLElement): HTMLElement { - const sib = el.previousElementSibling as HTMLElement; - if (sib) { - if (sib.matches(nav)) { - return sib; - } else { - const sibChild = sib.querySelector(nav) as HTMLElement; - return sibChild ?? this.prevSibling(sib); - } - } else { - if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { - return this.prevSibling(el.parentElement); - } else { - return null; - } + const sib = el.previousElementSibling as HTMLElement; + if (sib) { + if (sib.matches(nav)) { + return sib; } + const sibChild = sib.querySelector(nav) as HTMLElement; + return sibChild ?? this.prevSibling(sib); } + if (!isCodeBlock(el) && !el.parentElement.matches(nav)) { + return this.prevSibling(el.parentElement); + } + return null; + } protected moves: Map HTMLElement | null> = new Map([ ['ArrowDown', (node: HTMLElement) => node.querySelector(nav)], ['ArrowUp', (node: HTMLElement) => node.parentElement.closest(nav)], - ['ArrowLeft', this.prevSibling], - ['ArrowRight', this.nextSibling], + ['ArrowLeft', this.prevSibling.bind(this)], + ['ArrowRight', this.nextSibling.bind(this)], ['>', (_node: HTMLElement) => { return null; }], @@ -310,10 +304,9 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo public brailleRegion: LiveRegion, public magnifyRegion: HoverRegion, _mml: MmlNode, - private item: ExplorerMathItem + public item: ExplorerMathItem ) { super(document, pool, null, node); - // this.initWalker(); } @@ -524,17 +517,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo return false; } - - /** - * Initialises the Sre walker. - */ - // private initWalker() { - // this.speechGenerator = Sre.getSpeechGenerator('Tree'); - // let dummy = Sre.getWalker( - // 'dummy', this.node, this.speechGenerator, this.highlighter, this.mml); - // this.walker = dummy; - // } - /** * Retrieves the speech options to sync with document options. * @return {{[key: string]: string}} The options settings for the speech @@ -572,77 +554,3 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo } - - -/** - * Explorer that magnifies what is currently explored. Uses a hover region. - * @constructor - * @extends {AbstractKeyExplorer} - */ -// export class Magnifier extends AbstractKeyExplorer { - -// /** -// * @constructor -// * @extends {AbstractKeyExplorer} -// */ -// constructor(public document: A11yDocument, -// public pool: ExplorerPool, -// public region: Region, -// protected node: HTMLElement, -// private mml: string) { -// super(document, pool, region, node); -// this.walker = Sre.getWalker( -// 'table', this.node, Sre.getSpeechGenerator('Dummy'), -// this.highlighter, this.mml); -// } - -// /** -// * @override -// */ -// public Update(force: boolean = false) { -// super.Update(force); -// this.showFocus(); -// } - -// /** -// * @override -// */ -// public Start() { -// super.Start(); -// if (!this.attached) return; -// this.region.Show(this.node, this.highlighter); -// this.walker.activate(); -// this.Update(); -// } - -// /** -// * Shows the nodes that are currently focused. -// */ -// private showFocus() { -// let node = this.walker.getFocus().getNodes()[0] as HTMLElement; -// this.region.Show(node, this.highlighter); -// } - -// /** -// * @override -// */ -// public KeyDown(event: KeyboardEvent) { -// const code = event.keyCode; -// this.walker.modifier = event.shiftKey; -// if (code === 27) { -// this.Stop(); -// this.stopEvent(event); -// return; -// } -// if (this.active && code !== 13) { -// this.Move(code); -// this.stopEvent(event); -// return; -// } -// if (code === 32 && event.shiftKey || code === 13) { -// this.Start(); -// this.stopEvent(event); -// } -// } - -// } From 023df73ba395fde92cd93ccae11b5efeb5a885a0 Mon Sep 17 00:00:00 2001 From: zorkow Date: Sun, 13 Aug 2023 09:44:10 +0200 Subject: [PATCH 40/47] Refactors generator to be attached to item. --- ts/a11y/explorer/KeyExplorer.ts | 3 +++ ts/a11y/semantic-enrich.ts | 14 +++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 77004674e..e5c1f15c3 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -467,6 +467,9 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo this.Update(); return; } + if (event.getModifierState(code)) { + return; + } if (this.sound) { this.NoMove(); } diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index 211a3afef..f61d6aa59 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -136,6 +136,11 @@ export function EnrichedMathItemMixin Date: Sun, 13 Aug 2023 18:51:25 +0200 Subject: [PATCH 41/47] Reinstantiates auto voicing. --- ts/a11y/explorer/KeyExplorer.ts | 4 ++- ts/a11y/explorer/Region.ts | 53 +++++++++++++++++++++++---------- ts/a11y/semantic-enrich.ts | 3 +- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index e5c1f15c3..51a31bc24 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -380,7 +380,8 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo // nodes = this.walker.getFocus().getNodes(); // } this.pool.highlight([this.current]); - this.region.Update(this.current.getAttribute('aria-label')); + this.region.node = this.current; + this.region.Update(this.current.getAttribute('data-semantic-speech')); this.brailleRegion.Update(this.current.getAttribute('aria-braillelabel')); this.magnifyRegion.Update(this.current); // let options = this.speechGenerator.getOptions(); @@ -547,6 +548,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo if (this.active) { this.current.removeAttribute('tabindex'); this.pool.unhighlight(); + this.region.highlighter.unhighlight(); this.magnifyRegion.Hide(); this.region.Hide(); this.brailleRegion.Hide(); diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index d0672167f..c98d48335 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -26,7 +26,7 @@ import {MathDocument} from '../../core/MathDocument.js'; import {CssStyles} from '../../util/StyleList.js'; import {Sre} from '../sre.js'; -import {SsmlElement} from '../SpeechUtil.js'; +import {SsmlElement, buildSpeech} from '../SpeechUtil.js'; export type A11yDocument = MathDocument; @@ -407,24 +407,47 @@ export class SpeechRegion extends LiveRegion { super.Show(node, highlighter); } + /** + * Have we already requested voices from the browser? + */ + private voiceRequest = false; + /** * @override */ public Update(speech: string) { - // Temporarily removed! - // console.log(speech); - // this.active = this.document.options.a11y.voicing && - // !!speechSynthesis.getVoices().length; - // speechSynthesis.cancel(); - // this.clear = true; - // let [text, ssml] = ssmlParsing(speech); - // console.log(27); - // console.log(text); - // super.Update(text); - // if (this.active && text) { - // this.makeUtterances(ssml, this.document.options.sre.locale); - // } - super.Update(speech); + if (this.voiceRequest) { + this.makeVoice(speech); + return; + } + speechSynthesis.onvoiceschanged = (() => this.voiceRequest = true).bind(this); + super.Update('\u00a0'); // Ensures region shown and cannot be overwritten. + const promise = new Promise((resolve) => { + setTimeout(() => { + if (this.voiceRequest) { + resolve(true); + } + }, 100); + }); + promise.then( + () => this.makeVoice(speech) + ); + } + + private makeVoice(speech: string) { + this.active = this.document.options.a11y.voicing && + !!speechSynthesis.getVoices().length; + speechSynthesis.cancel(); + this.clear = true; + let [text, ssml] = buildSpeech( + speech, + this.document.options.sre.locale, + this.document.options.sre.rate + ); + super.Update(text); + if (this.active && text) { + this.makeUtterances(ssml, this.document.options.sre.locale); + } } /** diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index f61d6aa59..fa3fce47d 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -202,7 +202,8 @@ export function EnrichedMathItemMixin Date: Mon, 14 Aug 2023 11:43:46 +0200 Subject: [PATCH 42/47] Code cleanup. --- ts/a11y/SpeechUtil.ts | 12 ++++++++++++ ts/a11y/explorer/KeyExplorer.ts | 13 +++---------- ts/a11y/explorer/Region.ts | 10 ++++++---- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ts/a11y/SpeechUtil.ts b/ts/a11y/SpeechUtil.ts index 7873c8430..bb59b93b6 100644 --- a/ts/a11y/SpeechUtil.ts +++ b/ts/a11y/SpeechUtil.ts @@ -192,3 +192,15 @@ export function setAria(node: MmlNode, locale: string) { setAria(child, locale); } } + +/** + * Creates a honking sound. + */ +export function honk() { + let ac = new AudioContext(); + let os = ac.createOscillator(); + os.frequency.value = 300; + os.connect(ac.destination); + os.start(ac.currentTime); + os.stop(ac.currentTime + .05); +} diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 51a31bc24..1f1232b45 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -28,6 +28,7 @@ import type { ExplorerMathItem } from '../explorer.js'; import {Explorer, AbstractExplorer} from './Explorer.js'; import {ExplorerPool} from './ExplorerPool.js'; import {MmlNode} from '../../core/MmlTree/MmlNode.js'; +import { honk } from '../SpeechUtil.js'; import {Sre} from '../sre.js'; // import { Walker } from './Walker.js'; @@ -87,8 +88,6 @@ function isCodeBlock(el: HTMLElement) { */ export class SpeechExplorer extends AbstractExplorer implements KeyExplorer { - // public newWalker = new Walker(); - /** * Flag indicating if the explorer is attached to an object. */ @@ -261,12 +260,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo * @override */ public NoMove() { - let ac = new AudioContext(); - let os = ac.createOscillator(); - os.frequency.value = 300; - os.connect(ac.destination); - os.start(ac.currentTime); - os.stop(ac.currentTime + .05); + honk(); } private static updatePromise = Promise.resolve(); @@ -380,7 +374,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo // nodes = this.walker.getFocus().getNodes(); // } this.pool.highlight([this.current]); - this.region.node = this.current; + this.region.node = this.node; this.region.Update(this.current.getAttribute('data-semantic-speech')); this.brailleRegion.Update(this.current.getAttribute('aria-braillelabel')); this.magnifyRegion.Update(this.current); @@ -548,7 +542,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo if (this.active) { this.current.removeAttribute('tabindex'); this.pool.unhighlight(); - this.region.highlighter.unhighlight(); this.magnifyRegion.Hide(); this.region.Hide(); this.brailleRegion.Hide(); diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index c98d48335..73d90ddfc 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -211,8 +211,9 @@ export abstract class AbstractRegion implements Region { baseLeft = Math.min(region.getBoundingClientRect().left, baseLeft); } } - const bot = (baseBottom ? baseBottom : rect.bottom + 10) + window.pageYOffset; - const left = (baseLeft < Number.POSITIVE_INFINITY ? baseLeft : rect.left) + window.pageXOffset; + + const bot = (baseBottom ? baseBottom : rect.bottom + 10) + window.scrollY; + const left = (baseLeft < Number.POSITIVE_INFINITY ? baseLeft : rect.left) + window.scrollX; this.div.style.top = bot + 'px'; this.div.style.left = left + 'px'; } @@ -460,6 +461,7 @@ export class SpeechRegion extends LiveRegion { for (let utter of ssml) { if (utter.mark) { if (!utterance) { + // First utterance, call with init = true. this.highlightNode(utter.mark, true); continue; } @@ -566,7 +568,7 @@ export class HoverRegion extends AbstractRegion { const xCenter = nodeRect.left + (nodeRect.width / 2); let left = xCenter - (divRect.width / 2); left = (left < 0) ? 0 : left; - left = left + window.pageXOffset; + left = left + window.scrollX; let top; switch (this.document.options.a11y.align) { case 'top': @@ -580,7 +582,7 @@ export class HoverRegion extends AbstractRegion { const yCenter = nodeRect.top + (nodeRect.height / 2); top = yCenter - (divRect.height / 2); } - top = top + window.pageYOffset; + top = top + window.scrollY; top = (top < 0) ? 0 : top; this.div.style.top = top + 'px'; this.div.style.left = left + 'px'; From cc801cb9af501a2442c3777120a326d24dffbdc6 Mon Sep 17 00:00:00 2001 From: zorkow Date: Mon, 14 Aug 2023 18:12:04 +0200 Subject: [PATCH 43/47] Code cleanup and missing commenting. --- .../a11y/semantic-enrich/semantic-enrich.js | 2 - ts/a11y/SpeechMenu.ts | 2 +- ts/input/tex/StackItem.ts | 14 +- ts/input/tex/TexParser.ts | 193 ++++++++++-------- ts/input/tex/base/BaseItems.ts | 10 +- 5 files changed, 128 insertions(+), 93 deletions(-) diff --git a/components/mjs/a11y/semantic-enrich/semantic-enrich.js b/components/mjs/a11y/semantic-enrich/semantic-enrich.js index 5df8cccd0..0b7794efd 100644 --- a/components/mjs/a11y/semantic-enrich/semantic-enrich.js +++ b/components/mjs/a11y/semantic-enrich/semantic-enrich.js @@ -5,8 +5,6 @@ import {Sre} from '#js/a11y/sre.js'; import {EnrichHandler} from '#js/a11y/semantic-enrich.js'; import {MathML} from '#js/input/mathml.js'; -export {Sre}; - if (MathJax.loader) { combineDefaults(MathJax.config.loader, 'a11y/semantic-enrich', {checkReady: () => Sre.sreReady()}); } diff --git a/ts/a11y/SpeechMenu.ts b/ts/a11y/SpeechMenu.ts index f8f82b110..b06b469e9 100644 --- a/ts/a11y/SpeechMenu.ts +++ b/ts/a11y/SpeechMenu.ts @@ -204,7 +204,7 @@ export function localeMenu(menu: MJContextMenu, sub: Submenu) { let radios: {type: string, id: string, content: string, variable: string}[] = []; for (let lang of Sre.locales.keys()) { - if ((lang === 'euro') || (lang === 'nemeth')) continue; + if (lang === 'nemeth' || lang === 'euro') continue; radios.push({type: 'radio', id: lang, content: Sre.locales.get(lang) || lang, variable: 'locale'}); } diff --git a/ts/input/tex/StackItem.ts b/ts/input/tex/StackItem.ts index 3e3e53823..2e1cabcc4 100644 --- a/ts/input/tex/StackItem.ts +++ b/ts/input/tex/StackItem.ts @@ -82,8 +82,20 @@ export interface NodeStack { */ Clear(): void; + + /** + * The LaTeX string at the moment item is called. + */ startStr: string; + + /** + * Parser position in the global LaTeX string when item is called. + */ startI: number; + + /** + * Parser position in the global LaTeX string when item is pushed. + */ stopI: number; /** @@ -120,7 +132,7 @@ export abstract class MmlStack implements NodeStack { /** * @constructor * @extends {NodeStack} - * @param {MmlNode[]} nodes An initial list of nodes to put on the stack. + * @param {MmlNode[]} _nodes An initial list of nodes to put on the stack. */ constructor(private _nodes: MmlNode[]) { } diff --git a/ts/input/tex/TexParser.ts b/ts/input/tex/TexParser.ts index cf9e95847..b6919d499 100644 --- a/ts/input/tex/TexParser.ts +++ b/ts/input/tex/TexParser.ts @@ -66,7 +66,10 @@ export default class TexParser { */ public currentCS: string = ''; - public saveI: number[] = [0]; + /** + * A stack to save the string positions when we restart the parser. + */ + private saveI: number[] = [0]; /** * @constructor @@ -144,91 +147,6 @@ export default class TexParser { return result; } - // Currently works without environments. - private updateResult(input: string, old: number) { - let node = this.stack.Prev(true) as MmlNode; - if (!node) { - return; - } - let existing = node.attributes.get('itemLatex'); - if (existing !== undefined) { - node.attributes.set('latex', existing); - return; - } - let str = old !== this.i ? this.string.slice(old, this.i) : input; - str = str.trim(); - if (!str) { - return; - } - if (input === '\\') { - str = '\\' + str; - } - // TODO: Simplify. - // These are the cases to handle sub and superscripts. - if (node.attributes.get('latex') === '^' && str !== '^') { - if (str === '}') { - this.updateChild(node.childNodes[2]); - } else { - node.childNodes[2].attributes.set('latex', str); - } - if (node.childNodes[1]) { - const sub = node.childNodes[1].attributes.get('latex'); - this.composeLatex(node, `_${sub}^`, 0, 2); - } else { - this.composeLatex(node, '^', 0, 2); - } - return; - } - if (node.attributes.get('latex') === '_' && str !== '_') { - if (str === '}') { - this.updateChild(node.childNodes[1]); - } else { - node.childNodes[1].attributes.set('latex', str); - } - if (node.childNodes[2]) { - const sub = node.childNodes[2].attributes.get('latex'); - this.composeLatex(node, `^${sub}_`, 0, 1); - } else { - this.composeLatex(node, '_', 0, 1); - } - return; - } - if (str === '}') { - this.updateChild(node); - return; - } - node.attributes.set('latex', str); - } - - private composeLatex(node: MmlNode, comp: string, pos1: number, pos2: number) { - const expr = node.childNodes[pos1].attributes.get('latex') + comp + - node.childNodes[pos2].attributes.get('latex'); - node.attributes.set('latex', expr); - } - - private bracing(str: string) { - return '{' + str + '}'; - } - - private updateChild(atom: MmlNode) { - if (!atom) return; - let str = this.composeBraces(atom); - atom.attributes.set('latex', this.bracing(str)); - } - - private composeBraces(atom: MmlNode) { - // TODO: Make this more secure! - let children = atom.childNodes[0].childNodes; - let expr = ''; - for (const child of children) { - let att = (child.attributes?.get('latex') || '') as string; - if (!att) continue; - expr += (expr && expr.match(/[a-zA-Z]$/) && att.match(/^[a-zA-Z]/)) ? ' ' + att : att; - } - return expr; - } - - /** * Maps a symbol to its "parse value" if it exists. * @param {HandlerType} kind Configuration name. @@ -608,5 +526,108 @@ export default class TexParser { return this.configuration.nodeFactory.create(kind, ...rest); } + /** + * Finalizes the LaTeX for the topmost Mml element on the stack after parsing + * has been completed. + * + * @param {string} input The LaTeX input string for the parser. + * @param {number} old The last parsing position. + */ + // Currently works without translating environments that generate typesetting. + private updateResult(input: string, old: number) { + let node = this.stack.Prev(true) as MmlNode; + if (!node) { + return; + } + let existing = node.attributes.get('itemLatex'); + if (existing !== undefined) { + node.attributes.set('latex', existing); + return; + } + let str = old !== this.i ? this.string.slice(old, this.i) : input; + str = str.trim(); + if (!str) { + return; + } + if (input === '\\') { + str = '\\' + str; + } + // These are the cases to handle sub and superscripts. + if (node.attributes.get('latex') === '^' && str !== '^') { + if (str === '}') { + this.composeBraces(node.childNodes[2]); + } else { + node.childNodes[2].attributes.set('latex', str); + } + if (node.childNodes[1]) { + const sub = node.childNodes[1].attributes.get('latex'); + this.composeLatex(node, `_${sub}^`, 0, 2); + } else { + this.composeLatex(node, '^', 0, 2); + } + return; + } + if (node.attributes.get('latex') === '_' && str !== '_') { + if (str === '}') { + this.composeBraces(node.childNodes[1]); + } else { + node.childNodes[1].attributes.set('latex', str); + } + if (node.childNodes[2]) { + const sub = node.childNodes[2].attributes.get('latex'); + this.composeLatex(node, `^${sub}_`, 0, 1); + } else { + this.composeLatex(node, '_', 0, 1); + } + return; + } + if (str === '}') { + this.composeBraces(node); + return; + } + node.attributes.set('latex', str); + } + + /** + * Composing the LaTeX expression for sub or superscript elements. + * + * @param {MmlNode} node The Mml node. + * @param {string} comp Intermediate string. + * @param {number} pos1 Position of child for lefthand side of string. + * @param {number} pos2 Position of child for righthand side of string. + */ + private composeLatex( + node: MmlNode, comp: string, pos1: number, pos2: number) { + const expr = node.childNodes[pos1].attributes.get('latex') + comp + + node.childNodes[pos2].attributes.get('latex'); + node.attributes.set('latex', expr); + } + + /** + * Adds the LaTeX content for this node as a braced expression. + * + * @param {MmlNode} atom The current Mml node. + */ + private composeBraces(atom: MmlNode) { + if (!atom) return; + let str = this.composeBracedContent(atom); + atom.attributes.set('latex', `{${str}}`); + } + + /** + * Composes the content of a braced expression. + * + * @param {MmlNode} atom The current Mml node. + */ + private composeBracedContent(atom: MmlNode) { + let children = atom.childNodes[0]?.childNodes; + let expr = ''; + for (const child of children) { + let att = (child.attributes?.get('latex') || '') as string; + if (!att) continue; + expr += (expr && expr.match(/[a-zA-Z]$/) && att.match(/^[a-zA-Z]/)) ? ' ' + att : att; + } + return expr; + } } diff --git a/ts/input/tex/base/BaseItems.ts b/ts/input/tex/base/BaseItems.ts index 06b1c7c49..3ef5b751f 100644 --- a/ts/input/tex/base/BaseItems.ts +++ b/ts/input/tex/base/BaseItems.ts @@ -253,8 +253,6 @@ export class SubsupItem extends BaseItem { item.First = node; } } - // console.log(JSON.stringify(item.First.attributes)); - // let first = item.First; NodeUtil.setChild(top, position, item.First); if (this.getProperty('movesupsub') != null) { // @test Limits Subsup (currently does not work! Check again!) @@ -1588,7 +1586,13 @@ export class EquationItem extends BaseItem { } - +/** + * Adds auxiliary attributes for LaTeX output to node. + * + * @param {MmlNode} node The current node. + * @param {StackItem} item The stack item. + * @param {string=} prefix A prefix for the LaTeX command. + */ function addLatexItem(node: MmlNode, item: StackItem, prefix: string = '') { let str = item.startStr.slice(item.startI, item.stopI); if (str) { From 7927b028172c988f688a98414acc698beaa26730 Mon Sep 17 00:00:00 2001 From: zorkow Date: Mon, 14 Aug 2023 19:07:54 +0200 Subject: [PATCH 44/47] Code cleanup. --- ts/a11y/SpeechUtil.ts | 13 ++++++++++--- ts/a11y/explorer/ExplorerPool.ts | 21 --------------------- ts/a11y/explorer/Region.ts | 10 ---------- ts/a11y/semantic-enrich.ts | 1 - 4 files changed, 10 insertions(+), 35 deletions(-) diff --git a/ts/a11y/SpeechUtil.ts b/ts/a11y/SpeechUtil.ts index bb59b93b6..e0de19094 100644 --- a/ts/a11y/SpeechUtil.ts +++ b/ts/a11y/SpeechUtil.ts @@ -154,7 +154,7 @@ function getLabel(node: MmlNode, sep: string = ' ') { if (prefix) { label.unshift(prefix); } - // TODO: check if we need this or if is automatic by the screen readers. + // TODO: check if we need this or if it is automatic by the screen readers. const postfix = attributes.getExplicit('data-semantic-postfix') as string; if (postfix) { label.push(postfix); @@ -163,9 +163,16 @@ function getLabel(node: MmlNode, sep: string = ' ') { return label.join(sep); } - +/** + * Builds speechs from SSML markup strings. + * + * @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 + */ export function buildSpeech(speech: string, locale: string = 'en', - rate: string = '100') { + rate: string = '100'): [string, SsmlElement[]] { return ssmlParsing('` + diff --git a/ts/a11y/explorer/ExplorerPool.ts b/ts/a11y/explorer/ExplorerPool.ts index 8d3ae8c50..949416c55 100644 --- a/ts/a11y/explorer/ExplorerPool.ts +++ b/ts/a11y/explorer/ExplorerPool.ts @@ -90,30 +90,9 @@ let allExplorers: {[options: string]: ExplorerInit} = { let explorer = ke.SpeechExplorer.create( doc, pool, doc.explorerRegions.speechRegion, node, doc.explorerRegions.brailleRegion, doc.explorerRegions.magnifier, rest[0], rest[1]) as ke.SpeechExplorer; - // explorer.speechGenerator.setOptions({ - // automark: true as any, markup: 'ssml', - // locale: doc.options.sre.locale, domain: doc.options.sre.domain, - // style: doc.options.sre.style, modality: 'speech'}); - // // This weeds out the case of providing a non-existent locale option. - // let locale = explorer.speechGenerator.getOptions().locale; - // if (locale !== Sre.engineSetup().locale) { - // doc.options.sre.locale = Sre.engineSetup().locale; - // explorer.speechGenerator.setOptions({locale: doc.options.sre.locale}); - // } explorer.sound = true; return explorer; }, - // braille: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => { - // let explorer = ke.SpeechExplorer.create( - // doc, pool, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer; - // explorer.speechGenerator.setOptions({automark: false as any, markup: 'none', - // locale: 'nemeth', domain: 'default', - // style: 'default', modality: 'braille'}); - // explorer.showRegion = 'viewBraille'; - // return explorer; - // }, - // keyMagnifier: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ...rest: any[]) => - // ke.Magnifier.create(doc, pool, doc.explorerRegions.magnifier, node, ...rest), mouseMagnifier: (doc: ExplorerMathDocument, pool: ExplorerPool, node: HTMLElement, ..._rest: any[]) => me.ContentHoverer.create(doc, pool, doc.explorerRegions.magnifier, node, (x: HTMLElement) => x.hasAttribute('data-semantic-type'), diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index 73d90ddfc..aeafde224 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -356,16 +356,6 @@ export class LiveRegion extends StringRegion { } }); - - /** - * @constructor - * @param {A11yDocument} document The document the live region is added to. - */ - constructor(public document: A11yDocument) { - super(document); - // this.div.setAttribute('aria-live', 'assertive'); - } - } diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index fa3fce47d..99bb77136 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -242,7 +242,6 @@ export function EnrichedMathItemMixin Date: Wed, 6 Sep 2023 10:40:32 +0200 Subject: [PATCH 45/47] Starts explorer on focus as suggested by @pkra. --- ts/a11y/explorer/KeyExplorer.ts | 37 ++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts index 1f1232b45..ba02f1cf8 100644 --- a/ts/a11y/explorer/KeyExplorer.ts +++ b/ts/a11y/explorer/KeyExplorer.ts @@ -73,8 +73,10 @@ export interface KeyExplorer extends Explorer { } -const codeSelector = 'mjx-container[role="application"]'; -const nav = '[role="tree"],[role="group"],[role="treeitem"]'; +const codeSelector = 'mjx-container'; +const roles = ['tree', 'group', 'treeitem']; +const nav = roles.map(x => `[role="${x}"]`).join(','); +const prevNav = roles.map(x => `[tabindex="0"][role="${x}"]`).join(','); function isCodeBlock(el: HTMLElement) { return el.matches(codeSelector); @@ -110,23 +112,36 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo private move = false; + private mousedown = false; + /** * @override */ protected events: [string, (x: Event) => void][] = super.Events().concat( [ - // ['keydown', move], ['keydown', this.KeyDown.bind(this)], + ['mousedown', this.MouseDown.bind(this)], ['click', this.Click.bind(this)], ['focusin', this.FocusIn.bind(this)], ['focusout', this.FocusOut.bind(this)] ]); + /** + * Records a mouse down event on the element. This ensures that focus events + * only fire if they were not triggered by a mouse click. + * + * @param e The mouse event. + */ + private MouseDown(e: MouseEvent) { + this.mousedown = true; + e.preventDefault(); + } + public Click(e: MouseEvent) { const clicked = (e.target as HTMLElement).closest(nav) as HTMLElement; if (this.node.contains(clicked)) { - const prev = this.node.querySelector('[tabindex="0"][role="tree"],[tabindex="0"][role="group"],[tabindex="0"][role="treeitem"]'); + const prev = this.node.querySelector(prevNav); if (prev) { prev.removeAttribute('tabindex'); } @@ -147,7 +162,14 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo /** * @override */ - public FocusIn(_event: FocusEvent) { + public FocusIn(event: FocusEvent) { + if (this.mousedown) { + this.mousedown = false; + return; + } + this.current = this.current || this.node.querySelector('[role="tree"]'); + this.Start(); + event.preventDefault(); } /** @@ -167,7 +189,6 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo this.attached = true; this.oldIndex = this.node.tabIndex; this.node.tabIndex = 0; - this.node.setAttribute('role', 'application'); } /** @@ -197,7 +218,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo if (sib) { if (sib.matches(nav)) { return sib; - } + } const sibChild = sib.querySelector(nav) as HTMLElement; return sibChild ?? this.nextSibling(sib); } @@ -231,7 +252,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo return null; }], ]); - + /** * @override */ From 9cfc3fe70d20b6945a30284e5cb643da7b01bc3b Mon Sep 17 00:00:00 2001 From: zorkow Date: Wed, 6 Dec 2023 14:11:10 +0100 Subject: [PATCH 46/47] PR #987 incorporate review suggestions --- ts/a11y/SpeechUtil.ts | 35 +++++++++++++---- ts/a11y/explorer.ts | 1 + ts/a11y/explorer/KeyExplorer.ts | 7 +--- ts/a11y/semantic-enrich.ts | 70 ++++++++++++++++++--------------- ts/a11y/sre.ts | 3 ++ ts/output/svg.ts | 4 ++ 6 files changed, 75 insertions(+), 45 deletions(-) diff --git a/ts/a11y/SpeechUtil.ts b/ts/a11y/SpeechUtil.ts index e0de19094..2b66bbee1 100644 --- a/ts/a11y/SpeechUtil.ts +++ b/ts/a11y/SpeechUtil.ts @@ -1,5 +1,28 @@ +/************************************************************* + * + * Copyright (c) 2018-2023 The MathJax Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Provides utility functions for speech handling. + * + * @author v.sorge@mathjax.org (Volker Sorge) + */ + import {MmlNode} from '../core/MmlTree/MmlNode.js'; -// import {Sre} from './sre.js'; +import Sre from './sre.js'; const ProsodyKeys = [ 'pitch', 'rate', 'volume' ]; @@ -11,7 +34,6 @@ interface ProsodyElement { } export interface SsmlElement extends ProsodyElement { - [propName: string]: string | boolean | number; pause?: string; text?: string; mark?: string; @@ -27,11 +49,10 @@ export interface SsmlElement extends ProsodyElement { * @return {[string, SsmlElement[]]} The annotation structure. */ export function ssmlParsing(speech: string): [string, SsmlElement[]] { - let dp = new DOMParser(); - let xml = dp.parseFromString(speech, 'text/xml'); + let xml = Sre.parseDOM(speech); let instr: SsmlElement[] = []; let text: String[] = []; - recurseSsml(Array.from(xml.documentElement.childNodes), instr, text); + recurseSsml(Array.from(xml.childNodes), instr, text); return [text.join(' '), instr]; } @@ -79,8 +100,6 @@ function recurseSsml(nodes: Node[], instr: SsmlElement[], text: String[], instr.push(Object.assign({text: txt, character: true}, prosody)); text.push(txt); break; - default: - break; } } } @@ -123,7 +142,7 @@ function getProsody(element: Element, prosody: ProsodyElement) { /** * Extracts the prosody value from an attribute. */ -const prosodyRegexp = /([\+|-]*)([0-9]+)%/; +const prosodyRegexp = /([\+-]?)([0-9]+)%/; /** * Extracts the prosody value from an attribute. diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts index bacecbd83..f3b44457a 100644 --- a/ts/a11y/explorer.ts +++ b/ts/a11y/explorer.ts @@ -261,6 +261,7 @@ export function ExplorerMathDocumentMixin implements KeyExplo public Update(force: boolean = false) { // TODO (v4): This is a hack to avoid double voicing on initial startup! // Make that cleaner and remove force as it is not really used! - let noUpdate = force; - force = false; + // let noUpdate = force; if (!this.active && !force) return; this.pool.unhighlight(); // let nodes = this.walker.getFocus(true).getNodes(); @@ -475,11 +474,9 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo return; } } - let result = null; if (this.active) { - result = this.Move(event); this.stopEvent(event); - if (result) { + if (this.Move(event)) { this.Update(); return; } diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts index ea6b8400c..31e62438c 100644 --- a/ts/a11y/semantic-enrich.ts +++ b/ts/a11y/semantic-enrich.ts @@ -170,20 +170,21 @@ export function EnrichedMathItemMixin, force: boolean = false) { if (this.state() >= STATE.ENRICHED) return; if (!this.isEscaped && (document.options.enableEnrichment || force)) { - // TODO: Sort out the loading of the locales better - if (document.options.sre.locale !== currentLocale) { - currentLocale = document.options.sre.locale; - // TODO: Sort out the loading of the locales better - mathjax.retryAfter( - Sre.setupEngine({locale: document.options.sre.locale}).then( - () => { - return Sre.sreReady(); })); - } - if (document.options.sre.braille !== currentBraille) { - currentBraille = document.options.sre.braille; - mathjax.retryAfter( - Sre.setupEngine({locale: document.options.sre.braille}) - .then(() => Sre.sreReady())); + // TODO: Sort out the loading of the locales better + if (document.options.enableSpeech) { + if (document.options.sre.locale !== currentLocale) { + currentLocale = document.options.sre.locale; + // TODO: Sort out the loading of the locales better + mathjax.retryAfter( + Sre.setupEngine({locale: document.options.sre.locale}) + .then(() => Sre.sreReady())); + } + if (document.options.sre.braille !== currentBraille) { + currentBraille = document.options.sre.braille; + mathjax.retryAfter( + Sre.setupEngine({locale: document.options.sre.braille}) + .then(() => Sre.sreReady())); + } } const math = new document.options.MathItem('', MmlJax); try { @@ -195,23 +196,25 @@ export function EnrichedMathItemMixin, math: EnrichedMathItem, err: Error) => doc.enrichError(doc, math, err), diff --git a/ts/a11y/sre.ts b/ts/a11y/sre.ts index c39b0b73b..ae8b6b975 100644 --- a/ts/a11y/sre.ts +++ b/ts/a11y/sre.ts @@ -31,6 +31,7 @@ import {ClearspeakPreferences} from '#sre/speech_rules/clearspeak_preferences.js import {Highlighter} from '#sre/highlighter/highlighter.js'; import * as HighlighterFactory from '#sre/highlighter/highlighter_factory.js'; import {SpeechGenerator} from '#sre/speech_generator/speech_generator.js'; +import { parseInput } from '#sre/common/dom_util.js'; import {Variables} from '#sre/common/variables.js'; import MathMaps from './mathmaps.js'; @@ -65,6 +66,8 @@ export namespace Sre { export const getWalker = WalkerFactory.walker; + export const parseDOM = parseInput; + /** * Loads locales that are already included in the imported MathMaps. Defaults * to standard loading if a locale is not yet preloaded. diff --git a/ts/output/svg.ts b/ts/output/svg.ts index d86194500..502ad0120 100644 --- a/ts/output/svg.ts +++ b/ts/output/svg.ts @@ -97,6 +97,10 @@ CommonOutputJax< }, 'mjx-container[jax="SVG"] > svg a': { fill: 'blue', stroke: 'blue' + }, + 'rect[sre-highlighter-added]': { + stroke: 'black', + 'stroke-width': '40px' } }; From 30ebd5c9329bf95890e08388cb21bdf9c927f82b Mon Sep 17 00:00:00 2001 From: zorkow Date: Wed, 6 Dec 2023 19:39:35 +0100 Subject: [PATCH 47/47] fixes the incorrect braille components --- components/mjs/a11y/util-pack.js | 4 ++-- components/mjs/a11y/util.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/mjs/a11y/util-pack.js b/components/mjs/a11y/util-pack.js index 288e09bbe..c226708c5 100644 --- a/components/mjs/a11y/util-pack.js +++ b/components/mjs/a11y/util-pack.js @@ -6,7 +6,7 @@ import './explorer/explorer.js'; import {MathMaps} from '#js/a11y/mathmaps.js'; import base from 'speech-rule-engine/lib/mathmaps/base.json' assert { type: 'json' }; import en from 'speech-rule-engine/lib/mathmaps/en.json' assert {type: 'json' }; -import euro from 'speech-rule-engine/lib/mathmaps/euro.json' assert {type: 'json' }; +import nemeth from 'speech-rule-engine/lib/mathmaps/nemeth.json' assert {type: 'json' }; Loader.preLoad( 'a11y/sre', @@ -16,7 +16,7 @@ Loader.preLoad( MathMaps.set('base', base); MathMaps.set('en', en); -MathMaps.set('euro', euro); +MathMaps.set('nemeth', nemeth); export function checkSre(startup) { return () => startup(() => Sre.sreReady()); diff --git a/components/mjs/a11y/util.js b/components/mjs/a11y/util.js index 830af5e73..c9f1de1de 100644 --- a/components/mjs/a11y/util.js +++ b/components/mjs/a11y/util.js @@ -7,7 +7,7 @@ import {MathMaps} from '#js/a11y/mathmaps.js'; const base = require('speech-rule-engine/lib/mathmaps/base.json'); const en = require('speech-rule-engine/lib/mathmaps/en.json'); -const euro = require('speech-rule-engine/lib/mathmaps/euro.json'); +const nemeth = require('speech-rule-engine/lib/mathmaps/nemeth.json'); Loader.preLoad( 'a11y/sre', @@ -17,7 +17,7 @@ Loader.preLoad( MathMaps.set('base', base); MathMaps.set('en', en); -MathMaps.set('euro', euro); +MathMaps.set('nemeth', nemeth); export function checkSre(startup) { return () => startup(() => Sre.sreReady());