diff --git a/components/bin/build b/components/bin/build
index dfb0c86c1..be0504011 100755
--- a/components/bin/build
+++ b/components/bin/build
@@ -39,7 +39,7 @@ const EXPORTPATTERN =
/(^export(?:\s+default)?(?:\s+abstract)?\s+(?:[^ {*}]+\s+(?:enum\s+)?[a-zA-Z0-9_.$]+|\{.* as .*\}))/m;
const EXPORT_IGNORE = ['type', 'interface'];
-const EXPORT_PROCESS = ['let', 'const', 'var', 'function', 'class', 'namespace', 'as'];
+const EXPORT_PROCESS = ['let', 'const', 'var', 'function', 'class', 'namespace', 'enum', 'as'];
/**
* The module type to use ('cjs' or 'mjs')
@@ -159,7 +159,7 @@ function processParts(parts) {
for (let i = 1; i < parts.length; i += 2) {
const words = parts[i].split(/\s+/);
const n = words.length;
- const type = (words[n - 2] === 'enum' ? words[n - 3] : words[n - 2]);
+ const type = (words[n - 2] === 'enum' && n > 3 ? words[n - 3] : words[n - 2]);
const name = words[n - 1].replace(/\}$/, '');
if (words[1] === 'default' || type === 'default') {
diff --git a/components/mjs/a11y/explorer/config.json b/components/mjs/a11y/explorer/config.json
index 4070ae12c..bca3e99e3 100644
--- a/components/mjs/a11y/explorer/config.json
+++ b/components/mjs/a11y/explorer/config.json
@@ -6,7 +6,6 @@
"webpack": {
"name": "a11y/explorer",
"libs": [
- "components/src/ui/menu/lib",
"components/src/a11y/semantic-enrich/lib",
"components/src/a11y/sre/lib",
"components/src/input/mml/lib",
diff --git a/components/mjs/a11y/explorer/explorer.js b/components/mjs/a11y/explorer/explorer.js
index 9f38cce67..646ea55f9 100644
--- a/components/mjs/a11y/explorer/explorer.js
+++ b/components/mjs/a11y/explorer/explorer.js
@@ -1,17 +1,7 @@
import './lib/explorer.js';
-import {combineDefaults} from '#js/components/global.js';
import {ExplorerHandler} from '#js/a11y/explorer.js';
if (MathJax.startup && typeof window !== 'undefined') {
- 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/mjs/a11y/semantic-enrich/config.json b/components/mjs/a11y/semantic-enrich/config.json
index dd4068cfe..bdbef7eb1 100644
--- a/components/mjs/a11y/semantic-enrich/config.json
+++ b/components/mjs/a11y/semantic-enrich/config.json
@@ -1,7 +1,11 @@
{
"build": {
"component": "a11y/semantic-enrich",
- "targets": ["a11y/semantic-enrich.ts"]
+ "targets": [
+ "a11y/semantic-enrich.ts",
+ "a11y/speech/SpeechUtil.ts",
+ "a11y/speech/GeneratorPool.ts"
+ ]
},
"webpack": {
"name": "a11y/semantic-enrich",
diff --git a/components/mjs/dependencies.js b/components/mjs/dependencies.js
index ed6df0094..74b2be456 100644
--- a/components/mjs/dependencies.js
+++ b/components/mjs/dependencies.js
@@ -18,7 +18,7 @@
export const dependencies = {
'a11y/semantic-enrich': ['input/mml', 'a11y/sre'],
'a11y/complexity': ['a11y/semantic-enrich'],
- 'a11y/explorer': ['a11y/semantic-enrich', 'ui/menu'],
+ 'a11y/explorer': ['a11y/semantic-enrich'],
'[mml]/mml3': ['input/mml'],
'[tex]/all-packages': ['input/tex-base'],
'[tex]/action': ['input/tex-base', '[tex]/newcommand'],
diff --git a/components/mjs/ui/menu/config.json b/components/mjs/ui/menu/config.json
index 645cfe8e5..4fffd9231 100644
--- a/components/mjs/ui/menu/config.json
+++ b/components/mjs/ui/menu/config.json
@@ -1,7 +1,7 @@
{
"build": {
"component": "ui/menu",
- "targets": ["ui/menu"],
+ "targets": ["ui/menu", "a11y/speech/SpeechMenu.ts"],
"excludeSubdirs": true
},
"webpack": {
diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts
index a20e77938..484385aea 100644
--- a/ts/a11y/explorer.ts
+++ b/ts/a11y/explorer.ts
@@ -203,7 +203,7 @@ export function ExplorerMathDocumentMixin this.document.options.a11y[exKey])) {
+ explorer.Attach();
+ this.attached.push(key);
+ } else {
+ explorer.Detach();
+ }
+ continue;
}
- if (this.document.options.a11y[key]) {
+ if (a11y[key] || (key === 'speech' && (a11y.braille || a11y.keyMagnifier))) {
explorer.Attach();
this.attached.push(key);
} else {
diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts
index 096d0f53e..7946557dc 100644
--- a/ts/a11y/explorer/KeyExplorer.ts
+++ b/ts/a11y/explorer/KeyExplorer.ts
@@ -178,6 +178,7 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo
const prev = this.node.querySelector(prevNav);
if (prev) {
prev.removeAttribute('tabindex');
+ this.FocusOut(null);
}
this.current = clicked;
if (!this.triggerLinkMouse()) {
@@ -204,6 +205,9 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo
* @override
*/
public FocusOut(_event: FocusEvent) {
+ // This guard is to FF and Safari, where focus in fires only once on
+ // keyboard.
+ if (!this.active) return;
this.generators.CleanUp(this.current);
if (!this.move) {
this.Stop();
@@ -474,8 +478,9 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo
// the root node by default.
this.current = this.node.childNodes[0] as HTMLElement;
}
+ const options = this.document.options;
let promise = Sre.sreReady();
- if (this.generators.update(this.document.options)) {
+ if (this.generators.update(options)) {
promise = promise.then(
() => this.Speech()
);
@@ -483,15 +488,15 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo
this.current.setAttribute('tabindex', '0');
this.current.focus();
super.Start();
- if (this.document.options.a11y.subtitles) {
+ if (options.a11y.subtitles && options.a11y.speech && options.enableSpeech) {
promise.then(
() => this.region.Show(this.node, this.highlighter));
}
- if (this.document.options.a11y.viewBraille) {
+ if (options.a11y.viewBraille && options.a11y.braille && options.enableBraille) {
promise.then(
() => this.brailleRegion.Show(this.node, this.highlighter));
}
- if (this.document.options.a11y.keyMagnifier) {
+ if (options.a11y.keyMagnifier) {
this.magnifyRegion.Show(this.node, this.highlighter);
}
this.Update();
@@ -566,8 +571,8 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo
}
}
if (this.active) {
- this.stopEvent(event);
if (this.Move(event)) {
+ this.stopEvent(event);
this.Update();
return;
}
@@ -642,7 +647,8 @@ export class SpeechExplorer extends AbstractExplorer implements KeyExplo
public semanticFocus() {
const node = this.current || this.node;
const id = node.getAttribute('data-semantic-id');
- const stree = this.generators.speechGenerator.getRebuilt().stree;
+ const stree = this.generators.speechGenerator.getRebuilt()?.stree;
+ if (!stree) return null;
const snode = stree.root.querySelectorAll((x: any) => x.id.toString() === id)[0];
return snode || stree.root;
}
diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts
index 3be434267..bb0425527 100644
--- a/ts/a11y/explorer/Region.ts
+++ b/ts/a11y/explorer/Region.ts
@@ -417,6 +417,12 @@ export class SpeechRegion extends LiveRegion {
setTimeout(() => {
if (this.voiceRequest) {
resolve(true);
+ } else {
+ // This case is to make FF and Safari work.
+ setTimeout(() => {
+ this.voiceRequest = true;
+ resolve(true);
+ }, 100);
}
}, 100);
});
diff --git a/ts/a11y/semantic-enrich.ts b/ts/a11y/semantic-enrich.ts
index b18546ee5..fcbf7fad8 100644
--- a/ts/a11y/semantic-enrich.ts
+++ b/ts/a11y/semantic-enrich.ts
@@ -118,6 +118,11 @@ export interface EnrichedMathItem extends MathItem {
* @param {MathDocument} document The document where enrichment is occurring
*/
attachSpeech(document: MathDocument): void;
+
+ /**
+ * @param {MathDocument} document The MathDocument for the MathItem
+ */
+ unEnrich(document: MathDocument): void;
}
/**
@@ -201,6 +206,19 @@ export function EnrichedMathItemMixin) {
+ const mml = this.inputData.originalMml;
+ if (!mml) return;
+ const math = new document.options.MathItem('', MmlJax);
+ math.math = mml;
+ math.display = this.display;
+ math.compile(document);
+ this.root = math.root;
+ }
+
/**
* Correct the selection values for the maction items from the original MathML
*/
@@ -248,25 +266,28 @@ export function EnrichedMathItemMixin extends AbstractMathDocument, math: EnrichedMathItem, err: Error): void;
+
+ /**
+ * @param {EnrichedMathDocument} doc The MathDocument for the error
+ * @paarm {EnrichedMathItem} math The MathItem causing the error
+ * @param {Error} err The error being processed
+ */
+ speechError(doc: EnrichedMathDocument, math: EnrichedMathItem, err: Error): void;
}
/**
@@ -343,6 +371,9 @@ export function EnrichedMathDocumentMixin,
math: EnrichedMathItem,
err: Error) => doc.enrichError(doc, math, err),
+ speechError: (doc: EnrichedMathDocument,
+ math: EnrichedMathItem,
+ err: Error) => doc.speechError(doc, math, err),
renderActions: expandable({
...BaseDocument.OPTIONS.renderActions,
enrich: [STATE.ENRICHED],
@@ -416,6 +447,12 @@ export function EnrichedMathDocumentMixin, _math: EnrichedMathItem, err: Error) {
+ console.warn('Speech generation error:', err);
+ }
+
/**
* @override
*/
@@ -423,6 +460,11 @@ export function EnrichedMathDocumentMixin= STATE.COMPILED) {
+ for (const item of this.math) {
+ (item as EnrichedMathItem).unEnrich(this);
+ }
+ }
}
if (state < STATE.ATTACHSPEECH) {
this.processed.clear('attach-speech');
diff --git a/ts/a11y/speech/GeneratorPool.ts b/ts/a11y/speech/GeneratorPool.ts
index 63ff8e0fa..2cd76d3d8 100644
--- a/ts/a11y/speech/GeneratorPool.ts
+++ b/ts/a11y/speech/GeneratorPool.ts
@@ -370,13 +370,17 @@ export class GeneratorPool {
this.dummyList.forEach(attr => this.copyAttributes(xml, node, attr));
}
}
- const speech = this.getLabel(node);
- if (speech) {
- this.adaptor.setAttribute(node, 'aria-label', buildSpeech(speech, locale)[0]);
+ if (this.options.a11y.speech) {
+ const speech = this.getLabel(node);
+ if (speech) {
+ this.adaptor.setAttribute(node, 'aria-label', buildSpeech(speech, locale)[0]);
+ }
}
- const braille = this.adaptor.getAttribute(node, 'data-semantic-braille');
- if (braille) {
- this.adaptor.setAttribute(node, 'aria-braillelabel', braille);
+ if (this.options.a11y.braille) {
+ const braille = this.adaptor.getAttribute(node, 'data-semantic-braille');
+ if (braille) {
+ this.adaptor.setAttribute(node, 'aria-braillelabel', braille);
+ }
}
const xmlChildren = Array.from(xml.childNodes);
Array.from(this.adaptor.childNodes(node)).forEach(
diff --git a/ts/a11y/speech/SpeechMenu.ts b/ts/a11y/speech/SpeechMenu.ts
index 0e6b8dbd2..7bf741358 100644
--- a/ts/a11y/speech/SpeechMenu.ts
+++ b/ts/a11y/speech/SpeechMenu.ts
@@ -169,23 +169,25 @@ export function clearspeakMenu(menu: MJContextMenu, sub: Submenu) {
let locale = menu.pool.lookup('locale').getValue() as string;
const box = csSelectionBox(menu, locale);
let items: Object[] = [];
- const explorer = (menu.mathItem as ExplorerMathItem)?.explorers?.speech;
- const semantic = explorer?.semanticFocus();
- const previous = Sre.clearspeakPreferences.currentPreference();
- items = items.concat(basePreferences(previous));
- if (semantic) {
- const smart = Sre.clearspeakPreferences.relevantPreferences(semantic);
- items = items.concat(smartPreferences(previous, smart, locale));
- }
- if (box) {
- items.splice(2, 0, box);
+ if (menu.settings.speech) {
+ const explorer = (menu.mathItem as ExplorerMathItem)?.explorers?.speech;
+ const semantic = explorer?.semanticFocus();
+ const previous = Sre.clearspeakPreferences.currentPreference();
+ items = items.concat(basePreferences(previous));
+ if (semantic) {
+ const smart = Sre.clearspeakPreferences.relevantPreferences(semantic);
+ items = items.concat(smartPreferences(previous, smart, locale));
+ }
+ if (box) {
+ items.splice(2, 0, box);
+ }
}
return menu.factory.get('subMenu')(menu.factory, {
items: items,
id: 'Clearspeak'
}, sub);
}
-MJContextMenu.DynamicSubmenus.set('Clearspeak', clearspeakMenu);
+MJContextMenu.DynamicSubmenus.set('Clearspeak', [clearspeakMenu, 'speech']);
let LOCALE_MENU: SubMenu = null;
/**
@@ -209,4 +211,4 @@ export function localeMenu(menu: MJContextMenu, sub: Submenu) {
items: radios, id: 'Language'}, sub);
return LOCALE_MENU;
}
-MJContextMenu.DynamicSubmenus.set('A11yLanguage', localeMenu);
+MJContextMenu.DynamicSubmenus.set('A11yLanguage', [localeMenu, 'speech']);
diff --git a/ts/ui/menu/MJContextMenu.ts b/ts/ui/menu/MJContextMenu.ts
index e22eff1b8..ccc6dfbf8 100644
--- a/ts/ui/menu/MJContextMenu.ts
+++ b/ts/ui/menu/MJContextMenu.ts
@@ -22,6 +22,7 @@
*/
import {MathItem} from '../../core/MathItem.js';
+import {OptionList} from '../../util/Options.js';
import {JaxList} from './Menu.js';
import {ContextMenu, SubMenu, Submenu, Menu, Item} from './mj-context-menu.js';
@@ -38,13 +39,18 @@ export class MJContextMenu extends ContextMenu {
* Static map to hold methods for re-computing dynamic submenus.
* @type {Map SubMenu> = new Map();
+ public static DynamicSubmenus: Map SubMenu, string]> = new Map();
/**
* The MathItem that has posted the menu
*/
public mathItem: MathItem = null;
+ /**
+ * The document options
+ */
+ public settings: OptionList;
+
/**
* The error message for the current MathItem
*/
@@ -69,6 +75,7 @@ export class MJContextMenu extends ContextMenu {
this.getOriginalMenu();
this.getSemanticsMenu();
this.getSpeechMenu();
+ this.getBrailleMenu();
this.getSvgMenu();
this.getErrorMessage();
this.dynamicSubmenus();
@@ -84,6 +91,7 @@ export class MJContextMenu extends ContextMenu {
*/
public unpost() {
super.unpost();
+ this.mathItem.typesetRoot.blur();
this.mathItem = null;
}
@@ -99,11 +107,13 @@ export class MJContextMenu extends ContextMenu {
let menu = this as Menu;
let item = null as Item;
for (const name of names) {
- if (menu) {
- item = menu.find(name);
- menu = (item instanceof Submenu ? item.submenu : null);
- } else {
- item = null;
+ if (!menu) return null;
+ for (item of menu.items) {
+ if (item.id === name) {
+ menu = (item instanceof Submenu ? item.submenu : null);
+ break;
+ }
+ menu = item = null;
}
}
return item;
@@ -135,7 +145,7 @@ export class MJContextMenu extends ContextMenu {
* Enable/disable the semantics settings item
*/
protected getSemanticsMenu() {
- const semantics = this.findID('Settings', 'semantics');
+ const semantics = this.findID('Settings', 'MathmlIncludes', 'semantics');
this.mathItem.inputJax.name === 'MathML' ? semantics.disable() : semantics.enable();
}
@@ -148,6 +158,15 @@ export class MJContextMenu extends ContextMenu {
this.findID('Copy', 'Speech')[speech ? 'enable' : 'disable']();
}
+ /**
+ * Enable/disable the Braille menus
+ */
+ protected getBrailleMenu() {
+ const braille = this.mathItem.outputData.braille;
+ this.findID('Show', 'Braille')[braille ? 'enable' : 'disable']();
+ this.findID('Copy', 'Braille')[braille ? 'enable' : 'disable']();
+ }
+
/**
* Enable/disable the svg menus
*/
@@ -179,12 +198,12 @@ export class MJContextMenu extends ContextMenu {
* Renews the dynamic submenus.
*/
public dynamicSubmenus() {
- for (const [id, method] of MJContextMenu.DynamicSubmenus) {
+ for (const [id, [method, option]] of MJContextMenu.DynamicSubmenus) {
const menu = this.find(id) as Submenu;
if (!menu) continue;
const sub = method(this, menu);
menu.submenu = sub;
- if (sub.items.length) {
+ if (sub.items.length && (!option || this.settings[option])) {
menu.enable();
} else {
menu.disable();
diff --git a/ts/ui/menu/Menu.ts b/ts/ui/menu/Menu.ts
index b1945185a..784371b80 100644
--- a/ts/ui/menu/Menu.ts
+++ b/ts/ui/menu/Menu.ts
@@ -43,7 +43,6 @@ import * as MenuUtil from './MenuUtil.js';
import {Info, Parser, Rule, CssStyles, Submenu} from './mj-context-menu.js';
-
/*==========================================================================*/
/**
@@ -78,20 +77,21 @@ export interface MenuSettings {
breakInline: boolean;
autocollapse: boolean;
collapsible: boolean;
+ enrich: boolean;
inTabOrder: boolean;
assistiveMml: boolean;
// A11y settings
backgroundColor: string;
backgroundOpacity: string;
braille: boolean;
- explorer: boolean;
+ brailleCode: string;
foregroundColor: string;
foregroundOpacity: string;
highlight: string;
- locale: string;
infoPrefix: boolean;
infoRole: boolean;
infoType: boolean;
+ locale: string;
magnification: string;
magnify: string;
speech: boolean;
@@ -140,9 +140,13 @@ export class Menu {
breakInline: true,
autocollapse: false,
collapsible: false,
+ enrich: true,
inTabOrder: true,
assistiveMml: false,
- explorer: false
+ speech: true,
+ braille: true,
+ brailleCode: 'nemeth',
+ speechRules: 'mathspeek-default'
},
jax: {
CHTML: null,
@@ -298,14 +302,15 @@ export class Menu {
' as MathML or in its original format, to the clipboard',
' (in browsers that support that).
',
'Math Settings: These give you control over features of MathJax,',
- ' such the size of the mathematics, and the mechanism used',
- ' to display equations.
',
+ ' such the size of the mathematics, the mechanism used to display equations,',
+ ' how to handle equations that are too wide, and the language to use for',
+ ' MathJax\'s menus and error messages (not yet implemented in v4).',
+ '',
'Accessibility: MathJax can work with screen',
' readers to make mathematics accessible to the visually impaired.',
- ' Turn on the explorer to enable generation of speech strings',
- ' and the ability to investigate expressions interactively.
',
- 'Language: This menu lets you select the language used by MathJax',
- ' for its menus and warning messages. (Not yet implemented in version 3.)
',
+ ' Turn on speech or braille generation to enable creation of speech strings',
+ ' and the ability to investigate expressions interactively. You can control',
+ ' the style of the explorer in its menu.',
'',
'Math Zoom: If you are having difficulty reading an',
' equation, MathJax can enlarge it to help you see it better, or',
@@ -387,6 +392,20 @@ export class Menu {
''
);
+ /**
+ * The "Show As Speech Text" info box
+ */
+ protected brailleText = new SelectableInfo(
+ 'MathJax Braille Code',
+ () => {
+ if (!this.menu.mathItem) return '';
+ return '
'
+ + this.formatSource(this.menu.mathItem.outputData.braille)
+ + '
';
+ },
+ ''
+ );
+
/**
* The "Show As Error Message" info box
*/
@@ -442,13 +461,11 @@ export class Menu {
const jax = this.document.outputJax;
this.jax[jax.name] = jax;
this.settings.renderer = jax.name;
- if (MathJax._.a11y && MathJax._.a11y.explorer) {
- Object.assign(this.settings, this.document.options.a11y);
- }
this.settings.scale = jax.options.scale;
this.defaultSettings = Object.assign({}, this.settings);
this.settings.overflow =
- jax.options.displayOverflow.substring(0, 1).toUpperCase() + jax.options.displayOverflow.substring(1).toLowerCase();
+ jax.options.displayOverflow.substring(0, 1).toUpperCase() +
+ jax.options.displayOverflow.substring(1).toLowerCase();
this.settings.breakInline = jax.options.linebreaks.inline;
}
@@ -478,20 +495,18 @@ export class Menu {
this.variable('ctrl'),
this.variable('shift'),
this.variable ('scale', scale => this.setScale(scale)),
- this.variable('explorer', explore => this.setExplorer(explore)),
+ this.a11yVar('speech', speech => this.setSpeech(speech)),
+ this.a11yVar('braille', braille => this.setBraille(braille)),
+ this.variable('brailleCode', code => this.setBrailleCode(code)),
this.a11yVar ('highlight'),
this.a11yVar ('backgroundColor'),
this.a11yVar ('backgroundOpacity'),
this.a11yVar ('foregroundColor'),
this.a11yVar ('foregroundOpacity'),
- this.a11yVar('speech'),
this.a11yVar('subtitles'),
- this.a11yVar('braille'),
this.a11yVar('viewBraille'),
this.a11yVar('voicing'),
- this.a11yVar('locale', value => {
- MathJax._.a11y.sre.Sre.setupEngine({locale: value as string});
- }),
+ this.a11yVar('locale', locale => this.setLocale(locale)),
this.a11yVar('speechRules', value => {
const [domain, style] = value.split('-');
this.document.options.sre.domain = domain;
@@ -505,6 +520,7 @@ export class Menu {
this.a11yVar('infoPrefix'),
this.variable('autocollapse'),
this.variable('collapsible', collapse => this.setCollapsible(collapse)),
+ this.variable('enrich', enrich => this.setEnrichment(enrich)),
this.variable('inTabOrder', tab => this.setTabOrder(tab)),
this.variable('assistiveMml', mml => this.setAssistiveMml(mml))
],
@@ -514,6 +530,7 @@ export class Menu {
this.command('Original', 'Original Form', () => this.originalText.post()),
this.rule(),
this.command('Speech', 'Speech Text', () => this.speechText.post(), {disabled: true}),
+ this.command('Braille', 'Braille Code', () => this.brailleText.post(), {disabled: true}),
this.command('SVG', 'SVG Image', () => this.postSvgImage(), {disabled: true}),
this.submenu('ShowAnnotation', 'Annotation'),
this.rule(),
@@ -524,6 +541,7 @@ export class Menu {
this.command('Original', 'Original Form', () => this.copyOriginal()),
this.rule(),
this.command('Speech', 'Speech Text', () => this.copySpeechText(), {disabled: true}),
+ this.command('Braille', 'Braille Code', () => this.copyBrailleText(), {disabled: true}),
this.command('SVG', 'SVG Image', () => this.copySvgImage(), {disabled: true}),
this.submenu('CopyAnnotation', 'Annotation'),
this.rule(),
@@ -546,6 +564,7 @@ export class Menu {
this.checkbox('texHints', 'TeX hints', 'texHints'),
this.checkbox('semantics', 'Original as annotation', 'semantics')
]),
+ this.submenu('Language', 'Language'),
this.rule(),
this.submenu('ZoomTrigger', 'Zoom Trigger', [
this.command('ZoomNow', 'Zoom Once Now', () => this.zoom(null, '', this.menu.mathItem)),
@@ -568,30 +587,37 @@ export class Menu {
this.rule(),
this.command('Reset', 'Reset to defaults', () => this.resetDefaults())
]),
- this.submenu('Accessibility', 'Accessibility', [
- this.checkbox('Activate', 'Activate', 'explorer'),
- this.submenu('Speech', 'Speech', [
- this.checkbox('Speech', 'Speech Output', 'speech'),
- this.checkbox('Subtitles', 'Speech Subtitles', 'subtitles'),
- this.checkbox('Auto Voicing', 'Auto Voicing', 'voicing'),
- this.checkbox('Braille', 'Braille Output', 'braille'),
- this.checkbox('View Braille', 'Braille Subtitles', 'viewBraille'),
- this.rule(),
- this.submenu('A11yLanguage', 'Language'),
- this.rule(),
- this.submenu('Mathspeak', 'Mathspeak Rules', this.radioGroup('speechRules', [
- ['mathspeak-default', 'Verbose'],
- ['mathspeak-brief', 'Brief'],
- ['mathspeak-sbrief', 'Superbrief']
- ])),
- this.submenu('Clearspeak', 'Clearspeak Rules', this.radioGroup('speechRules', [
- ['clearspeak-default', 'Auto']
- ])),
- this.submenu('ChromeVox', 'ChromeVox Rules', this.radioGroup('speechRules', [
- ['chromevox-default', 'Standard'],
- ['chromevox-alternative', 'Alternative']
- ]))
- ]),
+ this.rule(),
+ this.label('Accessibility', '\xA0\xA0 Accessibility:'),
+ this.submenu('Speech', '\xA0 \xA0 Speech', [
+ this.checkbox('Generate', 'Generate', 'speech'),
+ this.checkbox('Subtitles', 'Show Subtitles', 'subtitles'),
+ this.checkbox('Auto Voicing', 'Auto Voicing', 'voicing'),
+ this.rule(),
+ this.label('Rules', 'Rules:'),
+ this.submenu('Mathspeak', 'Mathspeak', this.radioGroup('speechRules', [
+ ['mathspeak-default', 'Verbose'],
+ ['mathspeak-brief', 'Brief'],
+ ['mathspeak-sbrief', 'Superbrief']
+ ])),
+ this.submenu('Clearspeak', 'Clearspeak', this.radioGroup('speechRules', [
+ ['clearspeak-default', 'Auto']
+ ])),
+ this.submenu('ChromeVox', 'ChromeVox', this.radioGroup('speechRules', [
+ ['chromevox-default', 'Standard'],
+ ['chromevox-alternative', 'Alternative']
+ ])),
+ this.rule(),
+ this.submenu('A11yLanguage', 'Language')
+ ]),
+ this.submenu('Braille', '\xA0 \xA0 Braille', [
+ this.checkbox('Generate', 'Generate', 'braille'),
+ this.checkbox('Subtitles', 'Show Subtitles', 'viewBraille'),
+ this.rule(),
+ this.label('Code', 'Code Format:'),
+ this.radioGroup('brailleCode', [['nemeth', 'Nemeth'], ['ueb', 'UEB'], ['euro', 'Euro']])
+ ]),
+ this.submenu('Explorer', '\xA0 \xA0 Explorer', [
this.submenu('Highlight', 'Highlight', [
this.submenu('Background', 'Background', this.radioGroup('backgroundColor', [
['Blue'], ['Red'], ['Green'], ['Yellow'], ['Cyan'], ['Magenta'], ['White'], ['Black']
@@ -627,23 +653,47 @@ export class Menu {
this.checkbox('Type', 'Type', 'infoType'),
this.checkbox('Role', 'Role', 'infoRole'),
this.checkbox('Prefix', 'Prefix', 'infoPrefix')
- ], true),
- this.rule(),
+ ], true)
+ ]),
+ this.submenu('Options', '\xA0 \xA0 Options', [
+ this.checkbox('Enrich', 'Semantic Enrichment', 'enrich'),
this.checkbox('Collapsible', 'Collapsible Math', 'collapsible'),
this.checkbox('AutoCollapse', 'Auto Collapse', 'autocollapse', {disabled: true}),
this.rule(),
this.checkbox('InTabOrder', 'Include in Tab Order', 'inTabOrder'),
this.checkbox('AssistiveMml', 'Include Hidden MathML', 'assistiveMml')
]),
- this.submenu('Language', 'Language'),
this.rule(),
this.command('About', 'About MathJax', () => this.about.post()),
this.command('Help', 'MathJax Help', () => this.help.post())
]
}) as MJContextMenu;
const menu = this.menu;
+ menu.settings = this.settings;
menu.findID('Settings', 'Overflow', 'Elide').disable();
+ menu.findID('Braille', 'ueb').hide();
menu.setJax(this.jax);
+ this.attachDialogMenus(menu);
+ this.checkLoadableItems();
+ this.enableAccessibilityItems('Speech', this.settings.speech);
+ this.enableAccessibilityItems('Braille', this.settings.braille);
+ this.setAccessibilityMenus();
+ const cache: [string, string][] = [];
+ MJContextMenu.DynamicSubmenus.set(
+ 'ShowAnnotation',
+ [AnnotationMenu.showAnnotations(
+ this.annotationBox, this.options.annotationTypes, cache), '']);
+ MJContextMenu.DynamicSubmenus.set(
+ 'CopyAnnotation',
+ [AnnotationMenu.copyAnnotations(cache), '']);
+ CssStyles.addInfoStyles(this.document.document as any);
+ CssStyles.addMenuStyles(this.document.document as any);
+ }
+
+ /**
+ * @param {MJContextMenu} menu The menu to attach
+ */
+ protected attachDialogMenus(menu: MJContextMenu) {
this.about.attachMenu(menu);
this.help.attachMenu(menu);
this.originalText.attachMenu(menu);
@@ -651,20 +701,9 @@ export class Menu {
this.originalText.attachMenu(menu);
this.svgImage.attachMenu(menu);
this.speechText.attachMenu(menu);
+ this.brailleText.attachMenu(menu);
this.errorMessage.attachMenu(menu);
this.zoomBox.attachMenu(menu);
- this.checkLoadableItems();
- this.enableExplorerItems(this.settings.explorer);
- const cache: [string, string][] = [];
- MJContextMenu.DynamicSubmenus.set(
- 'ShowAnnotation',
- AnnotationMenu.showAnnotations(
- this.annotationBox, this.options.annotationTypes, cache));
- MJContextMenu.DynamicSubmenus.set(
- 'CopyAnnotation',
- AnnotationMenu.copyAnnotations(cache));
- CssStyles.addInfoStyles(this.document.document as any);
- CssStyles.addMenuStyles(this.document.document as any);
}
/**
@@ -675,13 +714,11 @@ export class Menu {
*/
protected checkLoadableItems() {
if (MathJax && MathJax._ && MathJax.loader && MathJax.startup) {
- if (this.settings.collapsible && (!MathJax._.a11y || !MathJax._.a11y.complexity)) {
- this.loadA11y('complexity');
- }
- if (this.settings.explorer && (!MathJax._.a11y || !MathJax._.a11y.explorer)) {
+ if ((this.settings.enrich || this.settings.collapsible || this.settings.speech || this.settings.braille) &&
+ (!MathJax._?.a11y?.['semantic-enrich'])) {
this.loadA11y('explorer');
}
- if (this.settings.assistiveMml && (!MathJax._.a11y || !MathJax._.a11y['assistive-mml'])) {
+ if (this.settings.assistiveMml && !MathJax._?.a11y?.['assistive-mml']) {
this.loadA11y('assistive-mml');
}
} else {
@@ -691,22 +728,26 @@ export class Menu {
menu.findID('Settings', 'Renderer', name).disable();
}
}
- menu.findID('Accessibility', 'Activate').disable();
- menu.findID('Accessibility', 'AutoCollapse').disable();
- menu.findID('Accessibility', 'Collapsible').disable();
+ menu.findID('Speech').disable();
+ menu.findID('Braille').disable();
+ menu.findID('Explorer').disable();
+ menu.findID('Options', 'AutoCollapse').disable();
+ menu.findID('Options', 'Collapsible').disable();
+ menu.findID('Options', 'Enrich').disable();
+ menu.findID('Options', 'AssistiveMml').disable();
}
}
/**
- * Enable/disable the Explorer submenu items
+ * Enable/disable an assistive submenu's items
*
* @param {boolean} enable True to enable, false to disable
*/
- protected enableExplorerItems(enable: boolean) {
- const menu = (this.menu.findID('Accessibility', 'Activate') as Submenu).menu;
+ protected enableAccessibilityItems(name: string, enable: boolean) {
+ const menu = (this.menu.findID(name) as Submenu).submenu;
for (const item of menu.items.slice(1)) {
- if (item instanceof Rule) break;
- enable ? item.enable() : item.disable();
+ if (item instanceof Rule) continue;
+ enable && (!(item instanceof Submenu) || item.submenu.items.length) ? item.enable() : item.disable();
}
}
@@ -753,7 +794,7 @@ export class Menu {
* @param {{[key: string]: any}} options The options.
*/
protected setA11y(options: {[key: string]: any}) {
- if (MathJax._.a11y && MathJax._.a11y.explorer) {
+ if (MathJax._?.a11y?.explorer) {
MathJax._.a11y.explorer_ts.setA11yOptions(this.document, options);
}
}
@@ -764,7 +805,7 @@ export class Menu {
* @return {any} The value of the option
*/
protected getA11y(option: string): any {
- if (MathJax._.a11y && MathJax._.a11y.explorer) {
+ if (MathJax._?.a11y?.explorer) {
if (this.document.options.a11y[option] !== undefined) {
return this.document.options.a11y[option];
}
@@ -780,16 +821,27 @@ export class Menu {
*/
protected applySettings() {
this.setTabOrder(this.settings.inTabOrder);
- this.document.options.enableAssistiveMml = this.settings.assistiveMml;
+ const options = this.document.options;
+ options.enableAssistiveMml = this.settings.assistiveMml;
+ options.enableSpeech = this.settings.speech;
+ options.enableBraille = this.settings.braille;
+ options.enableExplorer = this.settings.enrich;
const renderer = this.settings.renderer.replace(/[^a-zA-Z0-9]/g, '') || 'CHTML';
- const promise = (renderer !== this.defaultSettings.renderer ?
- this.setRenderer(renderer, false) :
- Promise.resolve());
+ const promise = (Menu._loadingPromise || Promise.resolve()).then(
+ () => (renderer !== this.defaultSettings.renderer ?
+ this.setRenderer(renderer, false) :
+ Promise.resolve())
+ );
promise.then(() => {
- this.document.options.enableExplorer = this.settings.explorer;
- this.document.outputJax.options.scale = parseFloat(this.settings.scale);
- this.document.outputJax.options.displayOverflow = this.settings.overflow.toLowerCase();
- this.document.outputJax.options.linebreaks.inline = this.settings.breakInline;
+ const settings = this.settings;
+ const options = this.document.outputJax.options;
+ options.scale = parseFloat(settings.scale);
+ options.displayOverflow = settings.overflow.toLowerCase();
+ options.linebreaks.inline = settings.breakInline;
+ if (!settings.speechRules) {
+ const sre = this.document.options.sre;
+ settings.speechRules = `${sre.domain || 'mathspeak'}-${sre.style || 'default'}`;
+ }
});
}
@@ -874,7 +926,7 @@ export class Menu {
*/
protected setAssistiveMml(mml: boolean) {
this.document.options.enableAssistiveMml = mml;
- if (!mml || (MathJax._.a11y && MathJax._.a11y['assistive-mml'])) {
+ if (!mml || MathJax._?.a11y?.['assistive-mml']) {
this.rerender();
} else {
this.loadA11y('assistive-mml');
@@ -882,13 +934,68 @@ export class Menu {
}
/**
- * @param {boolean} explore True to enable the explorer, false to not
+ * Enable/disable assistive menus based on enrichment setting
*/
- protected setExplorer(explore: boolean) {
- this.enableExplorerItems(explore);
- this.document.options.enableExplorer = explore;
- if (!explore || (MathJax._.a11y && MathJax._.a11y.explorer)) {
- this.rerender(this.settings.collapsible ? STATE.RERENDER : STATE.COMPILED);
+ protected setAccessibilityMenus() {
+ const enable = this.settings.enrich;
+ const method = (enable ? 'enable' : 'disable');
+ ['Speech', 'Braille', 'Explorer'].forEach(id => this.menu.findID(id)[method]());
+ if (!enable) {
+ this.settings.collapsible = false;
+ this.document.options.enableCollapsible = false;
+ }
+ }
+
+ /**
+ * @param {boolean} speech True to enable speech, false to not
+ */
+ protected setSpeech(speech: boolean) {
+ this.enableAccessibilityItems('Speech', speech);
+ this.document.options.enableSpeech = speech;
+ if (!speech || MathJax._?.a11y?.['semantic-enrich']) {
+ this.rerender(STATE.COMPILED);
+ } else {
+ this.loadA11y('explorer');
+ }
+ }
+
+ /**
+ * @param {boolean} braille True to enable braille, false to not
+ */
+ protected setBraille(braille: boolean) {
+ this.enableAccessibilityItems('Braille', braille);
+ this.document.options.enableBraille = braille;
+ if (!braille || MathJax._?.a11y?.['semantic-enrich']) {
+ this.rerender(STATE.COMPILED);
+ } else {
+ this.loadA11y('explorer');
+ }
+ }
+
+ /**
+ * @param {string} code The Braille code format (nemeth or euro)
+ */
+ protected setBrailleCode(code: string) {
+ this.document.options.sre.braille = code;
+ this.rerender(STATE.COMPILED);
+ }
+
+ /**
+ * @param {string} locale The speech locale
+ */
+ protected setLocale(locale: string) {
+ this.document.options.sre.locale = locale;
+ this.rerender(STATE.COMPILED);
+ }
+
+ /**
+ * @param {boolean} enrich True to enable enriched math, false to not
+ */
+ protected setEnrichment(enrich: boolean) {
+ this.document.options.enableEnrichment = this.document.options.enableExplorer = enrich;
+ this.setAccessibilityMenus();
+ if (!enrich || MathJax._?.a11y?.['semantic-enrich']) {
+ this.rerender(STATE.COMPILED);
} else {
this.loadA11y('explorer');
}
@@ -899,10 +1006,17 @@ export class Menu {
*/
protected setCollapsible(collapse: boolean) {
this.document.options.enableComplexity = collapse;
- if (!collapse || (MathJax._.a11y && MathJax._.a11y.complexity)) {
+ if (collapse && !this.settings.enrich) {
+ this.settings.enrich = true;
+ this.setEnrichment(true);
+ }
+ if (!collapse || MathJax._?.a11y?.complexity) {
this.rerender(STATE.COMPILED);
} else {
this.loadA11y('complexity');
+ if (!MathJax._?.a11y?.explorer) {
+ this.loadA11y('explorer');
+ }
}
}
@@ -1015,6 +1129,7 @@ export class Menu {
const document = this.document;
this.document = startup.document = startup.getDocument();
this.document.menu = this;
+ this.setA11y(this.settings);
this.document.outputJax.reset();
this.transferMathList(document);
this.document.processed = document.processed;
@@ -1027,7 +1142,6 @@ export class Menu {
});
}
-
/**
* @param {MenuMathDocument} document The original document whose list is to be transferred
*/
@@ -1088,7 +1202,7 @@ export class Menu {
* @param {boolean} breaks True if there are inline breaks
* @returns {Promise} A promise returning the serialized SVG
*/
- protected typesetSVG(math: HTMLMATHITEM, cache: string, breaks: boolean): Promise {
+ protected async typesetSVG(math: HTMLMATHITEM, cache: string, breaks: boolean): Promise {
const jax = this.jax.SVG as SVG;
const div = jax.html('div');
if (cache === 'global') {
@@ -1111,7 +1225,7 @@ export class Menu {
math.root = root;
jax.options.fontCache = cache;
return this.formatSvg(jax.adaptor.innerHTML(div));
- })
+ });
}
/**
@@ -1220,6 +1334,13 @@ export class Menu {
MenuUtil.copyToClipboard(this.menu.mathItem.outputData.speech);
}
+ /**
+ * Copy the speech text to the clipboard
+ */
+ protected copyBrailleText() {
+ MenuUtil.copyToClipboard(this.menu.mathItem.outputData.braille);
+ }
+
/**
* Copy the error message to the clipboard
*/
@@ -1285,9 +1406,7 @@ export class Menu {
getter: () => this.getA11y(name),
setter: (value: T) => {
(this.settings as any)[name] = value;
- let options: {[key: string]: any} = {};
- options[name] = value;
- this.setA11y(options);
+ this.setA11y({[name]: value});
action && action(value);
this.saveUserSettings();
}
diff --git a/ts/ui/menu/MenuHandler.ts b/ts/ui/menu/MenuHandler.ts
index d811a2f23..384702c94 100644
--- a/ts/ui/menu/MenuHandler.ts
+++ b/ts/ui/menu/MenuHandler.ts
@@ -172,6 +172,8 @@ export function MenuMathDocumentMixin(
//
enableEnrichment: true,
enableComplexity: true,
+ enableSpeech: true,
+ enableBraille: true,
enableExplorer: true,
enrichSpeech: 'none',
enrichError: (_doc: MenuMathDocument, _math: MenuMathItem, err: Error) =>
@@ -203,11 +205,20 @@ export function MenuMathDocumentMixin(
constructor(...args: any[]) {
super(...args);
this.menu = new this.options.MenuClass(this, this.options.menuOptions);
+
const ProcessBits = (this.constructor as typeof BaseDocument).ProcessBits;
if (!ProcessBits.has('context-menu')) {
ProcessBits.allocate('context-menu');
}
this.options.MathItem = MenuMathItemMixin(this.options.MathItem);
+
+ const settings = this.menu.settings;
+ const options = this.options;
+ const enrich = options.enableEnrichment = settings.enrich;
+ options.enableSpeech = settings.speech && enrich;
+ options.enableBraille = settings.braille && enrich;
+ options.enableComplexity = settings.collapsible && enrich;
+ options.enableExplorer = enrich;
}
/**
@@ -234,14 +245,10 @@ export function MenuMathDocumentMixin(
if (this.menu.isLoading) {
mathjax.retryAfter(this.menu.loadingPromise.catch((err) => console.log(err)));
}
- const settings = this.menu.settings;
- if (settings.collapsible) {
- this.options.enableComplexity = true;
+ if (this.options.enableComplexity) {
this.menu.checkComponent('a11y/complexity');
}
- if (settings.explorer) {
- this.options.enableEnrichment = true;
- this.options.enableExplorer = true;
+ if (this.options.enableExplorer) {
this.menu.checkComponent('a11y/explorer');
}
return this;