1
0

add initial marp implementation with sample content and build configuration

This commit is contained in:
2025-09-13 18:13:22 +02:00
parent dcacc9b409
commit e5f219507f
10319 changed files with 1402023 additions and 0 deletions

295
node_modules/mathjax-full/ts/a11y/assistive-mml.ts generated vendored Normal file
View File

@@ -0,0 +1,295 @@
/*************************************************************
*
* Copyright (c) 2019-2022 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 Mixin that adds hidden MathML to the output
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
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 {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
import {OptionList, expandable} from '../util/Options.js';
import {StyleList} from '../util/StyleList.js';
/*==========================================================================*/
export class LimitedMmlVisitor extends SerializedMmlVisitor {
/**
* @override
*/
protected getAttributes(node: MmlNode): string {
/**
* Remove id from attribute output
*/
return super.getAttributes(node).replace(/ ?id=".*?"/, '');
}
}
/**
* Generic constructor for Mixins
*/
export type Constructor<T> = new(...args: any[]) => T;
/*==========================================================================*/
/**
* Add STATE value for having assistive MathML (after TYPESETTING)
*/
newState('ASSISTIVEMML', 153);
/**
* The functions added to MathItem for assistive MathML
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export interface AssistiveMmlMathItem<N, T, D> extends MathItem<N, T, D> {
/**
* @param {MathDocument} document The document where assistive MathML is being added
* @param {boolean} force True to force assistive MathML even if enableAssistiveMml is false
*/
assistiveMml(document: MathDocument<N, T, D>, force?: boolean): void;
}
/**
* The mixin for adding assistive MathML to MathItems
*
* @param {B} BaseMathItem The MathItem class to be extended
* @return {AssistiveMathItem} The augmented MathItem class
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
* @template B The MathItem class to extend
*/
export function AssistiveMmlMathItemMixin<N, T, D, B extends Constructor<AbstractMathItem<N, T, D>>>(
BaseMathItem: B
): Constructor<AssistiveMmlMathItem<N, T, D>> & B {
return class extends BaseMathItem {
/**
* @param {MathDocument} document The MathDocument for the MathItem
* @param {boolean} force True to force assistive MathML evenif enableAssistiveMml is false
*/
public assistiveMml(document: AssistiveMmlMathDocument<N, T, D>, force: boolean = false) {
if (this.state() >= STATE.ASSISTIVEMML) return;
if (!this.isEscaped && (document.options.enableAssistiveMml || force)) {
const adaptor = document.adaptor;
//
// Get the serialized MathML
//
const mml = document.toMML(this.root).replace(/\n */g, '').replace(/<!--.*?-->/g, '');
//
// Parse is as HTML and retrieve the <math> element
//
const mmlNodes = adaptor.firstChild(adaptor.body(adaptor.parse(mml, 'text/html')));
//
// Create a container for the hidden MathML
//
const node = adaptor.node('mjx-assistive-mml', {
unselectable: 'on', display: (this.display ? 'block' : 'inline')
}, [mmlNodes]);
//
// Hide the typeset math from assistive technology and append the MathML that is visually
// hidden from other users
//
adaptor.setAttribute(adaptor.firstChild(this.typesetRoot) as N, 'aria-hidden', 'true');
adaptor.setStyle(this.typesetRoot, 'position', 'relative');
adaptor.append(this.typesetRoot, node);
}
this.state(STATE.ASSISTIVEMML);
}
};
}
/*==========================================================================*/
/**
* The functions added to MathDocument for assistive MathML
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export interface AssistiveMmlMathDocument<N, T, D> extends AbstractMathDocument<N, T, D> {
/**
* @param {MmlNode} node The node to be serializes
* @return {string} The serialization of the node
*/
toMML: (node: MmlNode) => string;
/**
* Add assistive MathML to the MathItems in the MathDocument
*
* @return {AssistiveMmlMathDocument} The MathDocument (so calls can be chained)
*/
assistiveMml(): AssistiveMmlMathDocument<N, T, D>;
}
/**
* The mixin for adding assistive MathML to MathDocuments
*
* @param {B} BaseDocument The MathDocument class to be extended
* @return {AssistiveMmlMathDocument} The Assistive MathML MathDocument class
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
* @template B The MathDocument class to extend
*/
export function AssistiveMmlMathDocumentMixin<N, T, D,
B extends MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
BaseDocument: B
): MathDocumentConstructor<AssistiveMmlMathDocument<N, T, D>> & B {
return class BaseClass extends BaseDocument {
/**
* @override
*/
public static OPTIONS: OptionList = {
...BaseDocument.OPTIONS,
enableAssistiveMml: true,
renderActions: expandable({
...BaseDocument.OPTIONS.renderActions,
assistiveMml: [STATE.ASSISTIVEMML]
})
};
/**
* styles needed for the hidden MathML
*/
public static assistiveStyles: StyleList = {
'mjx-assistive-mml': {
position: 'absolute !important',
top: '0px', left: '0px',
clip: 'rect(1px, 1px, 1px, 1px)',
padding: '1px 0px 0px 0px !important',
border: '0px !important',
display: 'block !important',
width: 'auto !important',
overflow: 'hidden !important',
/*
* Don't allow the assistive MathML to become part of the selection
*/
'-webkit-touch-callout': 'none',
'-webkit-user-select': 'none',
'-khtml-user-select': 'none',
'-moz-user-select': 'none',
'-ms-user-select': 'none',
'user-select': 'none'
},
'mjx-assistive-mml[display="block"]': {
width: '100% !important'
}
};
/**
* Visitor used for serializing internal MathML nodes
*/
protected visitor: LimitedMmlVisitor;
/**
* Augment the MathItem class used for this MathDocument, and create the serialization visitor.
*
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
const CLASS = (this.constructor as typeof BaseClass);
const ProcessBits = CLASS.ProcessBits;
if (!ProcessBits.has('assistive-mml')) {
ProcessBits.allocate('assistive-mml');
}
this.visitor = new LimitedMmlVisitor(this.mmlFactory);
this.options.MathItem =
AssistiveMmlMathItemMixin<N, T, D, Constructor<AbstractMathItem<N, T, D>>>(
this.options.MathItem
);
if ('addStyles' in this) {
(this as any).addStyles(CLASS.assistiveStyles);
}
}
/**
* @param {MmlNode} node The node to be serializes
* @return {string} The serialization of the node
*/
public toMML(node: MmlNode): string {
return this.visitor.visitTree(node);
}
/**
* Add assistive MathML to the MathItems in this MathDocument
*/
public assistiveMml() {
if (!this.processed.isSet('assistive-mml')) {
for (const math of this.math) {
(math as AssistiveMmlMathItem<N, T, D>).assistiveMml(this);
}
this.processed.set('assistive-mml');
}
return this;
}
/**
* @override
*/
public state(state: number, restore: boolean = false) {
super.state(state, restore);
if (state < STATE.ASSISTIVEMML) {
this.processed.clear('assistive-mml');
}
return this;
}
};
}
/*==========================================================================*/
/**
* Add assitive MathML support a Handler instance
*
* @param {Handler} handler The Handler instance to enhance
* @return {Handler} The handler that was modified (for purposes of chainging extensions)
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export function AssistiveMmlHandler<N, T, D>(handler: Handler<N, T, D>): Handler<N, T, D> {
handler.documentClass =
AssistiveMmlMathDocumentMixin<N, T, D, MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
handler.documentClass
);
return handler;
}

231
node_modules/mathjax-full/ts/a11y/complexity.ts generated vendored Normal file
View File

@@ -0,0 +1,231 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 Mixin that computes complexity of the internal MathML
* and optionally marks collapsible items
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {Handler} from '../core/Handler.js';
import {MathDocumentConstructor} from '../core/MathDocument.js';
import {STATE, newState} from '../core/MathItem.js';
import {MathML} from '../input/mathml.js';
import {MmlNode} from '../core/MmlTree/MmlNode.js';
import {EnrichHandler, EnrichedMathItem, EnrichedMathDocument} from './semantic-enrich.js';
import {ComplexityVisitor} from './complexity/visitor.js';
import {OptionList, selectOptionsFromKeys, expandable} from '../util/Options.js';
/**
* Generic constructor for Mixins
*/
export type Constructor<T> = new(...args: any[]) => T;
/**
* Shorthands for constructors
*/
export type EMItemC<N, T, D> = Constructor<EnrichedMathItem<N, T, D>>;
export type CMItemC<N, T, D> = Constructor<ComplexityMathItem<N, T, D>>;
export type EMDocC<N, T, D> = MathDocumentConstructor<EnrichedMathDocument<N, T, D>>;
export type CMDocC<N, T, D> = Constructor<ComplexityMathDocument<N, T, D>>;
/*==========================================================================*/
/**
* Add STATE value for having complexity added (after ENRICHED and before TYPESET)
*/
newState('COMPLEXITY', 40);
/**
* The functions added to MathItem for complexity
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export interface ComplexityMathItem<N, T, D> extends EnrichedMathItem<N, T, D> {
/**
* @param {ComplexityMathDocument} document The MathDocument for the MathItem
* @param {boolean} force True to force the computation even if enableComplexity is false
*/
complexity(document: ComplexityMathDocument<N, T, D>, force?: boolean): void;
}
/**
* The mixin for adding complexity to MathItems
*
* @param {B} BaseMathItem The MathItem class to be extended
* @param {function(MmlNode): void} computeComplexity Method of complexity computation.
* @return {ComplexityMathItem} The complexity MathItem class
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
* @template B The MathItem class to extend
*/
export function ComplexityMathItemMixin<N, T, D, B extends
EMItemC<N, T, D>>(BaseMathItem: B, computeComplexity: (node: MmlNode) => void): CMItemC<N, T, D> & B {
return class extends BaseMathItem {
/**
* @param {ComplexityMathDocument} document The MathDocument for the MathItem
* @param {boolean} force True to force the computation even if enableComplexity is false
*/
public complexity(document: ComplexityMathDocument<N, T, D>, force: boolean = false) {
if (this.state() >= STATE.COMPLEXITY) return;
if (!this.isEscaped && (document.options.enableComplexity || force)) {
this.enrich(document, true);
computeComplexity(this.root);
}
this.state(STATE.COMPLEXITY);
}
};
}
/*==========================================================================*/
/**
* The functions added to MathDocument for complexity
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export interface ComplexityMathDocument<N, T, D> extends EnrichedMathDocument<N, T, D> {
/**
* Perform complexity computations on the MathItems in the MathDocument
*
* @return {ComplexityMathDocument} The MathDocument (so calls can be chained)
*/
complexity(): ComplexityMathDocument<N, T, D>;
}
/**
* The mixin for adding complexity to MathDocuments
*
* @param {B} BaseDocument The MathDocument class to be extended
* @return {EnrichedMathDocument} The enriched MathDocument class
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
* @template B The MathDocument class to extend
*/
export function ComplexityMathDocumentMixin<N, T, D, B extends
EMDocC<N, T, D>>(BaseDocument: B): CMDocC<N, T, D> & B {
return class extends BaseDocument {
/**
* The options for this type of document
*/
public static OPTIONS: OptionList = {
...BaseDocument.OPTIONS,
...ComplexityVisitor.OPTIONS,
enableComplexity: true,
ComplexityVisitor: ComplexityVisitor,
renderActions: expandable({
...BaseDocument.OPTIONS.renderActions,
complexity: [STATE.COMPLEXITY]
})
};
/**
* The visitor that computes complexities
*/
protected complexityVisitor: ComplexityVisitor;
/**
* Extend the MathItem class used for this MathDocument
*
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
const ProcessBits = (this.constructor as typeof BaseDocument).ProcessBits;
if (!ProcessBits.has('complexity')) {
ProcessBits.allocate('complexity');
}
const visitorOptions = selectOptionsFromKeys(this.options, this.options.ComplexityVisitor.OPTIONS);
this.complexityVisitor = new this.options.ComplexityVisitor(this.mmlFactory, visitorOptions);
const computeComplexity = ((node: MmlNode) => this.complexityVisitor.visitTree(node));
this.options.MathItem =
ComplexityMathItemMixin<N, T, D, EMItemC<N, T, D>>(
this.options.MathItem, computeComplexity
);
}
/**
* Compute the complexity the MathItems in this MathDocument
*/
public complexity() {
if (!this.processed.isSet('complexity')) {
if (this.options.enableComplexity) {
for (const math of this.math) {
(math as ComplexityMathItem<N, T, D>).complexity(this);
}
}
this.processed.set('complexity');
}
return this;
}
/**
* @override
*/
public state(state: number, restore: boolean = false) {
super.state(state, restore);
if (state < STATE.COMPLEXITY) {
this.processed.clear('complexity');
}
return this;
}
};
}
/*==========================================================================*/
/**
* Add complexity computations a Handler instance
*
* @param {Handler} handler The Handler instance to enhance
* @param {MathML} MmlJax The MathML input jax to use for reading the enriched MathML
* @return {Handler} The handler that was modified (for purposes of chainging extensions)
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export function ComplexityHandler<N, T, D>(
handler: Handler<N, T, D>,
MmlJax: MathML<N, T, D> = null
): Handler<N, T, D> {
if (!handler.documentClass.prototype.enrich && MmlJax) {
handler = EnrichHandler(handler, MmlJax);
}
handler.documentClass = ComplexityMathDocumentMixin<N, T, D, EMDocC<N, T, D>>(handler.documentClass as any);
return handler;
}

View File

@@ -0,0 +1,519 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 Implements a class that marks complex items for collapsing
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {MmlNode, AbstractMmlTokenNode, TextNode} from '../../core/MmlTree/MmlNode.js';
import {ComplexityVisitor} from './visitor.js';
/*==========================================================================*/
/**
* Function for checking if a node should be collapsible
*/
export type CollapseFunction = (node: MmlNode, complexity: number) => number;
/**
* Map of types to collase functions
*/
export type CollapseFunctionMap = Map<string, CollapseFunction>;
/**
* A list of values indexed by semantic-type, possibly sub-indexed by semantic-role
*
* @template T The type of the indexed item
*/
export type TypeRole<T> = {[type: string]: T | {[role: string]: T}};
/**
* The class for determining of a subtree can be collapsed
*/
export class Collapse {
/**
* A constant to use to indicate no collapsing
*/
public static NOCOLLAPSE: number = 10000000; // really big complexity
/**
* The complexity object containing this one
*/
public complexity: ComplexityVisitor;
/**
* The cutt-off complexity values for when a structure
* of the given type should collapse
*/
public cutoff: TypeRole<number> = {
identifier: 3,
number: 3,
text: 10,
infixop: 15,
relseq: 15,
multirel: 15,
fenced: 18,
bigop: 20,
integral: 20,
fraction: 12,
sqrt: 9,
root: 12,
vector: 15,
matrix: 15,
cases: 15,
superscript: 9,
subscript: 9,
subsup: 9,
punctuated: {
endpunct: Collapse.NOCOLLAPSE,
startpunct: Collapse.NOCOLLAPSE,
value: 12
}
};
/**
* These are the characters to use for the various collapsed elements
* (if an object, then semantic-role is used to get the character
* from the object)
*/
public marker: TypeRole<string> = {
identifier: 'x',
number: '#',
text: '...',
appl: {
'limit function': 'lim',
value: 'f()'
},
fraction: '/',
sqrt: '\u221A',
root: '\u221A',
superscript: '\u25FD\u02D9',
subscript: '\u25FD.',
subsup: '\u25FD:',
vector: {
binomial: '(:)',
determinant: '|:|',
value: '\u27E8:\u27E9'
},
matrix: {
squarematrix: '[::]',
rowvector: '\u27E8\u22EF\u27E9',
columnvector: '\u27E8\u22EE\u27E9',
determinant: '|::|',
value: '(::)'
},
cases: '{:',
infixop: {
addition: '+',
subtraction: '\u2212',
multiplication: '\u22C5',
implicit: '\u22C5',
value: '+'
},
punctuated: {
text: '...',
value: ','
}
};
/**
* The type-to-function mapping for semantic types
*/
public collapse: CollapseFunctionMap = new Map([
//
// For fenced elements, if the contents are collapsed,
// collapse the fence instead.
//
['fenced', (node, complexity) => {
complexity = this.uncollapseChild(complexity, node, 1);
if (complexity > this.cutoff.fenced && node.attributes.get('data-semantic-role') === 'leftright') {
complexity = this.recordCollapse(
node, complexity,
this.getText(node.childNodes[0] as MmlNode) +
this.getText(node.childNodes[node.childNodes.length - 1] as MmlNode)
);
}
return complexity;
}],
//
// Collapse function applications if the argument is collapsed
// (FIXME: Handle role="limit function" a bit better?)
//
['appl', (node, complexity) => {
if (this.canUncollapse(node, 2, 2)) {
complexity = this.complexity.visitNode(node, false);
const marker = this.marker.appl as {[name: string]: string};
const text = marker[node.attributes.get('data-semantic-role') as string] || marker.value;
complexity = this.recordCollapse(node, complexity, text);
}
return complexity;
}],
//
// For sqrt elements, if the contents are collapsed,
// collapse the sqrt instead.
//
['sqrt', (node, complexity) => {
complexity = this.uncollapseChild(complexity, node, 0);
if (complexity > this.cutoff.sqrt) {
complexity = this.recordCollapse(node, complexity, this.marker.sqrt as string);
}
return complexity;
}],
['root', (node, complexity) => {
complexity = this.uncollapseChild(complexity, node, 0, 2);
if (complexity > this.cutoff.sqrt) {
complexity = this.recordCollapse(node, complexity, this.marker.sqrt as string);
}
return complexity;
}],
//
// For enclose, include enclosure in collapse
//
['enclose', (node, complexity) => {
if (this.splitAttribute(node, 'children').length === 1) {
const child = this.canUncollapse(node, 1);
if (child) {
const marker = child.getProperty('collapse-marker') as string;
this.unrecordCollapse(child);
complexity = this.recordCollapse(node, this.complexity.visitNode(node, false), marker);
}
}
return complexity;
}],
//
// For bigops, get the character to use from the largeop at its core.
//
['bigop', (node, complexity) => {
if (complexity > this.cutoff.bigop || !node.isKind('mo')) {
const id = this.splitAttribute(node, 'content').pop();
const op = this.findChildText(node, id);
complexity = this.recordCollapse(node, complexity, op);
}
return complexity;
}],
['integral', (node, complexity) => {
if (complexity > this.cutoff.integral || !node.isKind('mo')) {
const id = this.splitAttribute(node, 'content').pop();
const op = this.findChildText(node, id);
complexity = this.recordCollapse(node, complexity, op);
}
return complexity;
}],
//
// For relseq and multirel, use proper symbol
//
['relseq', (node, complexity) => {
if (complexity > this.cutoff.relseq) {
const id = this.splitAttribute(node, 'content')[0];
const text = this.findChildText(node, id);
complexity = this.recordCollapse(node, complexity, text);
}
return complexity;
}],
['multirel', (node, complexity) => {
if (complexity > this.cutoff.relseq) {
const id = this.splitAttribute(node, 'content')[0];
const text = this.findChildText(node, id) + '\u22EF';
complexity = this.recordCollapse(node, complexity, text);
}
return complexity;
}],
//
// Include super- and subscripts into a collapsed base
//
['superscript', (node, complexity) => {
complexity = this.uncollapseChild(complexity, node, 0, 2);
if (complexity > this.cutoff.superscript) {
complexity = this.recordCollapse(node, complexity, this.marker.superscript as string);
}
return complexity;
}],
['subscript', (node, complexity) => {
complexity = this.uncollapseChild(complexity, node, 0, 2);
if (complexity > this.cutoff.subscript) {
complexity = this.recordCollapse(node, complexity, this.marker.subscript as string);
}
return complexity;
}],
['subsup', (node, complexity) => {
complexity = this.uncollapseChild(complexity, node, 0, 3);
if (complexity > this.cutoff.subsup) {
complexity = this.recordCollapse(node, complexity, this.marker.subsup as string);
}
return complexity;
}]
] as [string, CollapseFunction][]);
/**
* The highest id number used for mactions so far
*/
private idCount = 0;
/**
* @param {ComplexityVisitor} visitor The visitor for computing complexities
*/
constructor(visitor: ComplexityVisitor) {
this.complexity = visitor;
}
/**
* Check if a node should be collapsible and insert the
* maction node to handle that. Return the updated
* complexity.
*
* @param {MmlNode} node The node to check
* @param {number} complexity The current complexity of the node
* @return {number} The revised complexity
*/
public check(node: MmlNode, complexity: number): number {
const type = node.attributes.get('data-semantic-type') as string;
if (this.collapse.has(type)) {
return this.collapse.get(type).call(this, node, complexity);
}
if (this.cutoff.hasOwnProperty(type)) {
return this.defaultCheck(node, complexity, type);
}
return complexity;
}
/**
* Check if the complexity exceeds the cutoff value for the type
*
* @param {MmlNode} node The node to check
* @param {number} complexity The current complexity of the node
* @param {string} type The semantic type of the node
* @return {number} The revised complexity
*/
protected defaultCheck(node: MmlNode, complexity: number, type: string): number {
const role = node.attributes.get('data-semantic-role') as string;
const check = this.cutoff[type];
const cutoff = (typeof check === 'number' ? check : check[role] || check.value);
if (complexity > cutoff) {
const marker = this.marker[type] || '??';
const text = (typeof marker === 'string' ? marker : marker[role] || marker.value);
complexity = this.recordCollapse(node, complexity, text);
}
return complexity;
}
/**
* @param {MmlNode} node The node to check
* @param {number} complexity The current complexity of the node
* @param {string} text The text to use for the collapsed node
* @return {number} The revised complexity for the collapsed node
*/
protected recordCollapse(node: MmlNode, complexity: number, text: string): number {
text = '\u25C2' + text + '\u25B8';
node.setProperty('collapse-marker', text);
node.setProperty('collapse-complexity', complexity);
return text.length * this.complexity.complexity.text;
}
/**
* Remove collapse markers (to move them to a parent node)
*
* @param {MmlNode} node The node to uncollapse
*/
protected unrecordCollapse(node: MmlNode) {
const complexity = node.getProperty('collapse-complexity');
if (complexity != null) {
node.attributes.set('data-semantic-complexity', complexity);
node.removeProperty('collapse-complexity');
node.removeProperty('collapse-marker');
}
}
/**
* @param {MmlNode} node The node to check if its child is collapsible
* @param {number} n The position of the child node to check
* @param {number=} m The number of children node must have
* @return {MmlNode|null} The child node that was collapsed (or null)
*/
protected canUncollapse(node: MmlNode, n: number, m: number = 1): MmlNode | null {
if (this.splitAttribute(node, 'children').length === m) {
const mml = (node.childNodes.length === 1 &&
(node.childNodes[0] as MmlNode).isInferred ? node.childNodes[0] as MmlNode : node);
if (mml && mml.childNodes[n]) {
const child = mml.childNodes[n] as MmlNode;
if (child.getProperty('collapse-marker')) {
return child;
}
}
}
return null;
}
/**
* @param {number} complexity The current complexity
* @param {MmlNode} node The node to check
* @param {number} n The position of the child node to check
* @param {number=} m The number of children the node must have
* @return {number} The updated complexity
*/
protected uncollapseChild(complexity: number, node: MmlNode, n: number, m: number = 1): number {
const child = this.canUncollapse(node, n, m);
if (child) {
this.unrecordCollapse(child);
if (child.parent !== node) {
child.parent.attributes.set('data-semantic-complexity', undefined);
}
complexity = this.complexity.visitNode(node, false) as number;
}
return complexity;
}
/**
* @param {MmlNode} node The node whose attribute is to be split
* @param {string} id The name of the data-semantic attribute to split
* @return {string[]} Array of ids in the attribute split at commas
*/
protected splitAttribute(node: MmlNode, id: string): string[] {
return (node.attributes.get('data-semantic-' + id) as string || '').split(/,/);
}
/**
* @param {MmlNode} node The node whose text content is needed
* @return{string} The text of the node (and its children), combined
*/
protected getText(node: MmlNode): string {
if (node.isToken) return (node as AbstractMmlTokenNode).getText();
return node.childNodes.map((n: MmlNode) => this.getText(n)).join('');
}
/**
* @param {MmlNode} node The node whose child text is needed
* @param {string} id The (semantic) id of the child needed
* @return {string} The text of the specified child node
*/
protected findChildText(node: MmlNode, id: string): string {
const child = this.findChild(node, id);
return this.getText(child.coreMO() || child);
}
/**
* @param {MmlNode} node The node whose child is to be located
* @param {string} id The (semantic) id of the child to be found
* @return {MmlNode|null} The child node (or null if not found)
*/
protected findChild(node: MmlNode, id: string): MmlNode | null {
if (!node || node.attributes.get('data-semantic-id') === id) return node;
if (!node.isToken) {
for (const mml of node.childNodes) {
const child = this.findChild(mml as MmlNode, id);
if (child) return child;
}
}
return null;
}
/**
* Add maction nodes to the nodes in the tree that can collapse
*
* @paramn {MmlNode} node The root of the tree to check
*/
public makeCollapse(node: MmlNode) {
const nodes: MmlNode[] = [];
node.walkTree((child: MmlNode) => {
if (child.getProperty('collapse-marker')) {
nodes.push(child);
}
});
this.makeActions(nodes);
}
/**
* @param {MmlNode[]} nodes The list of nodes to replace by maction nodes
*/
public makeActions(nodes: MmlNode[]) {
for (const node of nodes) {
this.makeAction(node);
}
}
/**
* @return {string} A unique id string.
*/
private makeId(): string {
return 'mjx-collapse-' + this.idCount++;
}
/**
* @param {MmlNode} node The node to make collapsible by replacing with an maction
*/
public makeAction(node: MmlNode) {
if (node.isKind('math')) {
node = this.addMrow(node);
}
const factory = this.complexity.factory;
const marker = node.getProperty('collapse-marker') as string;
const parent = node.parent;
let maction = factory.create('maction', {
actiontype: 'toggle',
selection: 2,
'data-collapsible': true,
id: this.makeId(),
'data-semantic-complexity': node.attributes.get('data-semantic-complexity')
}, [
factory.create('mtext', {mathcolor: 'blue'}, [
(factory.create('text') as TextNode).setText(marker)
])
]);
maction.inheritAttributesFrom(node);
node.attributes.set('data-semantic-complexity', node.getProperty('collapse-complexity'));
node.removeProperty('collapse-marker');
node.removeProperty('collapse-complexity');
parent.replaceChild(maction, node);
maction.appendChild(node);
}
/**
* If the <math> node is to be collapsible, add an mrow to it instead so that we can wrap it
* in an maction (can't put one around the <math> node).
*
* @param {MmlNode} node The math node to create an mrow for
* @return {MmlNode} The newly created mrow
*/
public addMrow(node: MmlNode): MmlNode {
const mrow = this.complexity.factory.create('mrow', null, node.childNodes[0].childNodes as MmlNode[]);
node.childNodes[0].setChildren([mrow]);
const attributes = node.attributes.getAllAttributes();
for (const name of Object.keys(attributes)) {
if (name.substr(0, 14) === 'data-semantic-') {
mrow.attributes.set(name, attributes[name]);
delete attributes[name];
}
}
mrow.setProperty('collapse-marker', node.getProperty('collapse-marker'));
mrow.setProperty('collapse-complexity', node.getProperty('collapse-complexity'));
node.removeProperty('collapse-marker');
node.removeProperty('collapse-complexity');
return mrow;
}
}

381
node_modules/mathjax-full/ts/a11y/complexity/visitor.ts generated vendored Normal file
View File

@@ -0,0 +1,381 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 Implements a class that computes complexities for enriched math
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {MmlNode, AbstractMmlTokenNode} from '../../core/MmlTree/MmlNode.js';
import {MmlMroot} from '../../core/MmlTree/MmlNodes/mroot.js';
import {MmlMaction} from '../../core/MmlTree/MmlNodes/maction.js';
import {MmlMsubsup, MmlMsub, MmlMsup} from '../../core/MmlTree/MmlNodes/msubsup.js';
import {MmlMunderover, MmlMunder, MmlMover} from '../../core/MmlTree/MmlNodes/munderover.js';
import {MmlVisitor} from '../../core/MmlTree/MmlVisitor.js';
import {MmlFactory} from '../../core/MmlTree/MmlFactory.js';
import {Collapse} from './collapse.js';
import {OptionList, userOptions, defaultOptions} from '../../util/Options.js';
/*==========================================================================*/
/**
* A visitor pattern that computes complexities within the MathML tree
*/
export class ComplexityVisitor extends MmlVisitor {
/**
* The options for handling collapsing
*/
public static OPTIONS: OptionList = {
identifyCollapsible: true, // mark elements that should be collapsed
makeCollapsible: true, // insert maction to allow collapsing
Collapse: Collapse // the Collapse class to use
};
/**
* Values used to compute complexities
*/
public complexity: {[name: string]: number} = {
text: .5, // each character of a token element adds this to complexity
token: .5, // each token element gets this additional complexity
child: 1, // child nodes add this to their parent node's complexity
script: .8, // script elements reduce their complexity by this factor
sqrt: 2, // sqrt adds this extra complexity
subsup: 2, // sub-sup adds this extra complexity
underover: 2, // under-over adds this extra complexity
fraction: 2, // fractions add this extra complexity
enclose: 2, // menclose adds this extra complexity
action: 2, // maction adds this extra complexity
phantom: 0, // mphantom makes complexity 0?
xml: 2, // Can't really measure complexity of annotation-xml, so punt
glyph: 2 // Can't really measure complexity of mglyph, to punt
};
/**
* The object used to handle collapsable content
*/
public collapse: Collapse;
/**
* The MmlFactory for this visitor
*/
public factory: MmlFactory;
/**
* The options for this visitor
*/
public options: OptionList;
/**
* @override
*/
constructor(factory: MmlFactory, options: OptionList) {
super(factory);
let CLASS = this.constructor as typeof ComplexityVisitor;
this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
this.collapse = new this.options.Collapse(this);
this.factory = factory;
}
/*==========================================================================*/
/**
* @override
*/
public visitTree(node: MmlNode) {
super.visitTree(node, true);
if (this.options.makeCollapsible) {
this.collapse.makeCollapse(node);
}
}
/**
* @override
*/
public visitNode(node: MmlNode, save: boolean) {
if (node.attributes.get('data-semantic-complexity')) return;
return super.visitNode(node, save);
}
/**
* For token nodes, use the content length, otherwise, add up the child complexities
*
* @override
*/
public visitDefault(node: MmlNode, save: boolean) {
let complexity;
if (node.isToken) {
const text = (node as AbstractMmlTokenNode).getText();
complexity = this.complexity.text * text.length + this.complexity.token;
} else {
complexity = this.childrenComplexity(node);
}
return this.setComplexity(node, complexity, save);
}
/**
* For a fraction, add the complexities of the children and scale by script factor, then
* add the fraction amount
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMfracNode(node: MmlNode, save: boolean) {
const complexity = this.childrenComplexity(node) * this.complexity.script + this.complexity.fraction;
return this.setComplexity(node, complexity, save);
}
/**
* For square roots, use the child complexity plus the sqrt complexity
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMsqrtNode(node: MmlNode, save: boolean) {
const complexity = this.childrenComplexity(node) + this.complexity.sqrt;
return this.setComplexity(node, complexity, save);
}
/**
* For roots, do the sqrt root computation and remove a bit for the root
* (since it is counted in the children sum but is smaller)
*
* @param {MmlMroot} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMrootNode(node: MmlMroot, save: boolean) {
const complexity = this.childrenComplexity(node) + this.complexity.sqrt
- (1 - this.complexity.script) * this.getComplexity(node.childNodes[1]);
return this.setComplexity(node, complexity, save);
}
/**
* Phantom complexity is 0
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMphantomNode(node: MmlNode, save: boolean) {
return this.setComplexity(node, this.complexity.phantom, save);
}
/**
* For ms, add the complexity of the quotes to that of the content, and use the
* length of that times the text factor as the complexity
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMsNode(node: MmlNode, save: boolean) {
const text = node.attributes.get('lquote')
+ (node as AbstractMmlTokenNode).getText()
+ node.attributes.get('rquote');
const complexity = text.length * this.complexity.text;
return this.setComplexity(node, complexity, save);
}
/**
* For supscripts and superscripts use the maximum of the script complexities,
* multiply by the script factor, and add the base complexity. Add the child
* complexity for each child, and the subsup complexity.
*
* @param {MmlMsubsup} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMsubsupNode(node: MmlMsubsup, save: boolean) {
super.visitDefault(node, true);
const sub = node.childNodes[node.sub];
const sup = node.childNodes[node.sup];
const base = node.childNodes[node.base];
let complexity = Math.max(
sub ? this.getComplexity(sub) : 0,
sup ? this.getComplexity(sup) : 0
) * this.complexity.script;
complexity += this.complexity.child * ((sub ? 1 : 0) + (sup ? 1 : 0));
complexity += (base ? this.getComplexity(base) + this.complexity.child : 0);
complexity += this.complexity.subsup;
return this.setComplexity(node, complexity, save);
}
/**
* @param {MmlMsub} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMsubNode(node: MmlMsub, save: boolean) {
return this.visitMsubsupNode(node, save);
}
/**
* @param {MmlMsup} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMsupNode(node: MmlMsup, save: boolean) {
return this.visitMsubsupNode(node, save);
}
/**
* For under/over, get the maximum of the complexities of the under and over
* elements times the script factor, and that the maximum of that with the
* base complexity. Add child complexity for all children, and add the
* underover amount.
*
* @param {MmlMunderover} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMunderoverNode(node: MmlMunderover, save: boolean) {
super.visitDefault(node, true);
const under = node.childNodes[node.under];
const over = node.childNodes[node.over];
const base = node.childNodes[node.base];
let complexity = Math.max(
under ? this.getComplexity(under) : 0,
over ? this.getComplexity(over) : 0,
) * this.complexity.script;
if (base) {
complexity = Math.max(this.getComplexity(base), complexity);
}
complexity += this.complexity.child * ((under ? 1 : 0) + (over ? 1 : 0) + (base ? 1 : 0));
complexity += this.complexity.underover;
return this.setComplexity(node, complexity, save);
}
/**
* @param {MmlMunder} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMunderNode(node: MmlMunder, save: boolean) {
return this.visitMunderoverNode(node, save);
}
/**
* @param {MmlMover} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMoverNode(node: MmlMover, save: boolean) {
return this.visitMunderoverNode(node, save);
}
/**
* For enclose, use sum of child complexities plus some for the enclose
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMencloseNode(node: MmlNode, save: boolean) {
const complexity = this.childrenComplexity(node) + this.complexity.enclose;
return this.setComplexity(node, complexity, save);
}
/**
* For actions, use the complexity of the selected child
*
* @param {MmlMaction} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMactionNode(node: MmlMaction, save: boolean) {
this.childrenComplexity(node);
const complexity = this.getComplexity(node.selected);
return this.setComplexity(node, complexity, save);
}
/**
* For semantics, get the complexity from the first child
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMsemanticsNode(node: MmlNode, save: boolean) {
const child = node.childNodes[0] as MmlNode;
let complexity = 0;
if (child) {
this.visitNode(child, true);
complexity = this.getComplexity(child);
}
return this.setComplexity(node, complexity, save);
}
/**
* Can't really measure annotations, so just use a specific value
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitAnnotationNode(node: MmlNode, save: boolean) {
return this.setComplexity(node, this.complexity.xml, save);
}
/**
* Can't really measure annotations, so just use a specific value
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitAnnotation_xmlNode(node: MmlNode, save: boolean) {
return this.setComplexity(node, this.complexity.xml, save);
}
/**
* Can't really measure mglyph complexity, so just use a specific value
*
* @param {MmlNode} node The node whose complixity is being computed
* @param {boolean} save True if the complexity is to be saved or just returned
*/
protected visitMglyphNode(node: MmlNode, save: boolean) {
return this.setComplexity(node, this.complexity.glyph, save);
}
/*==========================================================================*/
/**
* @param {MmlNode} node The node whose complixity is needed
* @return {number} The complexity fof the node (if collapsable, then the collapsed complexity)
*/
public getComplexity(node: MmlNode): number {
const collapsed = node.getProperty('collapsedComplexity');
return (collapsed != null ? collapsed : node.attributes.get('data-semantic-complexity')) as number;
}
/**
* @param {MmlNode} node The node whose complixity is being set
* @param {complexity} number The complexity for the node
* @param {boolean} save True if complexity is to be set or just reported
*/
protected setComplexity(node: MmlNode, complexity: number, save: boolean) {
if (save) {
if (this.options.identifyCollapsible) {
complexity = this.collapse.check(node, complexity);
}
node.attributes.set('data-semantic-complexity', complexity);
}
return complexity;
}
/**
* @param {MmlNode} node The node whose children complexities are to be added
* @return {number} The sum of the complexities, plus child complexity for each one
*/
protected childrenComplexity(node: MmlNode): number {
super.visitDefault(node, true);
let complexity = 0;
for (const child of node.childNodes) {
complexity += this.getComplexity(child as MmlNode);
}
if (node.childNodes.length > 1) {
complexity += node.childNodes.length * this.complexity.child;
}
return complexity;
}
}

650
node_modules/mathjax-full/ts/a11y/explorer.ts generated vendored Normal file
View File

@@ -0,0 +1,650 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 Mixin that implements the Explorer
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {Handler} from '../core/Handler.js';
import {MmlNode} from '../core/MmlTree/MmlNode.js';
import {MathML} from '../input/mathml.js';
import {STATE, newState} from '../core/MathItem.js';
import {EnrichedMathItem, EnrichedMathDocument, EnrichHandler} from './semantic-enrich.js';
import {MathDocumentConstructor} from '../core/MathDocument.js';
import {OptionList, expandable} from '../util/Options.js';
import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
import {MJContextMenu} from '../ui/menu/MJContextMenu.js';
import {Explorer} from './explorer/Explorer.js';
import * as ke from './explorer/KeyExplorer.js';
import * as me from './explorer/MouseExplorer.js';
import {TreeColorer, FlameColorer} from './explorer/TreeExplorer.js';
import {LiveRegion, ToolTip, HoverRegion} from './explorer/Region.js';
import {Submenu} from 'mj-context-menu/js/item_submenu.js';
import Sre from './sre.js';
/**
* Generic constructor for Mixins
*/
export type Constructor<T> = new(...args: any[]) => T;
/**
* Shorthands for types with HTMLElement, Text, and Document instead of generics
*/
export type HANDLER = Handler<HTMLElement, Text, Document>;
export type HTMLDOCUMENT = EnrichedMathDocument<HTMLElement, Text, Document>;
export type HTMLMATHITEM = EnrichedMathItem<HTMLElement, Text, Document>;
export type MATHML = MathML<HTMLElement, Text, Document>;
/*==========================================================================*/
/**
* Add STATE value for having the Explorer added (after TYPESET and before INSERTED or CONTEXT_MENU)
*/
newState('EXPLORER', 160);
/**
* The properties added to MathItem for the Explorer
*/
export interface ExplorerMathItem extends HTMLMATHITEM {
/**
* @param {HTMLDocument} document The document where the Explorer is being added
* @param {boolean} force True to force the explorer even if enableExplorer is false
*/
explorable(document: HTMLDOCUMENT, force?: boolean): void;
/**
* @param {HTMLDocument} document The document where the Explorer is being added
*/
attachExplorers(document: HTMLDOCUMENT): void;
}
/**
* The mixin for adding the Explorer to MathItems
*
* @param {B} BaseMathItem The MathItem class to be extended
* @param {Function} toMathML The function to serialize the internal MathML
* @returns {ExplorerMathItem} The Explorer MathItem class
*
* @template B The MathItem class to extend
*/
export function ExplorerMathItemMixin<B extends Constructor<HTMLMATHITEM>>(
BaseMathItem: B,
toMathML: (node: MmlNode) => string
): Constructor<ExplorerMathItem> & B {
return class extends BaseMathItem {
/**
* The Explorer objects for this math item
*/
protected explorers: {[key: string]: Explorer} = {};
/**
* The currently attached explorers
*/
protected attached: string[] = [];
/**
* True when a rerendered element should restart these explorers
*/
protected restart: string[] = [];
/**
* True when a rerendered element should regain the focus
*/
protected refocus: boolean = false;
/**
* Save explorer id during rerendering.
*/
protected savedId: string = null;
/**
* Add the explorer to the output for this math item
*
* @param {HTMLDocument} document The MathDocument for the MathItem
* @param {boolean} force True to force the explorer even if enableExplorer is false
*/
public explorable(document: ExplorerMathDocument, force: boolean = false) {
if (this.state() >= STATE.EXPLORER) return;
if (!this.isEscaped && (document.options.enableExplorer || force)) {
const node = this.typesetRoot;
const mml = toMathML(this.root);
if (this.savedId) {
this.typesetRoot.setAttribute('sre-explorer-id', this.savedId);
this.savedId = null;
}
// Init explorers:
this.explorers = initExplorers(document, node, mml);
this.attachExplorers(document);
}
this.state(STATE.EXPLORER);
}
/**
* Attaches the explorers that are currently meant to be active given
* the document options. Detaches all others.
* @param {ExplorerMathDocument} document The current document.
*/
public attachExplorers(document: ExplorerMathDocument) {
this.attached = [];
let keyExplorers = [];
for (let key of Object.keys(this.explorers)) {
let explorer = this.explorers[key];
if (explorer instanceof ke.AbstractKeyExplorer) {
explorer.AddEvents();
explorer.stoppable = false;
keyExplorers.unshift(explorer);
}
if (document.options.a11y[key]) {
explorer.Attach();
this.attached.push(key);
} else {
explorer.Detach();
}
}
// Ensure that the last currently attached key explorer stops propagating
// key events.
for (let explorer of keyExplorers) {
if (explorer.attached) {
explorer.stoppable = true;
break;
}
}
}
/**
* @override
*/
public rerender(document: ExplorerMathDocument, start: number = STATE.RERENDER) {
this.savedId = this.typesetRoot.getAttribute('sre-explorer-id');
this.refocus = (window.document.activeElement === this.typesetRoot);
for (let key of this.attached) {
let explorer = this.explorers[key];
if (explorer.active) {
this.restart.push(key);
explorer.Stop();
}
}
super.rerender(document, start);
}
/**
* @override
*/
public updateDocument(document: ExplorerMathDocument) {
super.updateDocument(document);
this.refocus && this.typesetRoot.focus();
this.restart.forEach(x => this.explorers[x].Start());
this.restart = [];
this.refocus = false;
}
};
}
/**
* The functions added to MathDocument for the Explorer
*/
export interface ExplorerMathDocument extends HTMLDOCUMENT {
/**
* The objects needed for the explorer
*/
explorerRegions: ExplorerRegions;
/**
* Add the Explorer to the MathItems in the MathDocument
*
* @returns {MathDocument} The MathDocument (so calls can be chained)
*/
explorable(): HTMLDOCUMENT;
}
/**
* The mixin for adding the Explorer to MathDocuments
*
* @param {B} BaseDocument The MathDocument class to be extended
* @returns {ExplorerMathDocument} The extended MathDocument class
*/
export function ExplorerMathDocumentMixin<B extends MathDocumentConstructor<HTMLDOCUMENT>>(
BaseDocument: B
): MathDocumentConstructor<ExplorerMathDocument> & B {
return class extends BaseDocument {
/**
* @override
*/
public static OPTIONS: OptionList = {
...BaseDocument.OPTIONS,
enableExplorer: true,
renderActions: expandable({
...BaseDocument.OPTIONS.renderActions,
explorable: [STATE.EXPLORER]
}),
sre: expandable({
...BaseDocument.OPTIONS.sre,
speech: 'shallow', // overrides option in EnrichedMathDocument
}),
a11y: {
align: 'top', // placement of magnified expression
backgroundColor: 'Blue', // color for background of selected sub-expression
backgroundOpacity: 20, // opacity for background of selected sub-expression
braille: false, // switch on Braille output
flame: false, // color collapsible sub-expressions
foregroundColor: 'Black', // color to use for text of selected sub-expression
foregroundOpacity: 100, // opacity for text of selected sub-expression
highlight: 'None', // type of highlighting for collapsible sub-expressions
hover: false, // show collapsible sub-expression on mouse hovering
infoPrefix: false, // show speech prefixes on mouse hovering
infoRole: false, // show semantic role on mouse hovering
infoType: false, // show semantic type on mouse hovering
keyMagnifier: false, // switch on magnification via key exploration
magnification: 'None', // type of magnification
magnify: '400%', // percentage of magnification of zoomed expressions
mouseMagnifier: false, // switch on magnification via mouse hovering
speech: true, // switch on speech output
subtitles: true, // show speech as a subtitle
treeColoring: false, // tree color expression
viewBraille: false // display Braille output as subtitles
}
};
/**
* The objects needed for the explorer
*/
public explorerRegions: ExplorerRegions;
/**
* Extend the MathItem class used for this MathDocument
* and create the visitor and explorer objects needed for the explorer
*
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
const ProcessBits = (this.constructor as typeof BaseDocument).ProcessBits;
if (!ProcessBits.has('explorer')) {
ProcessBits.allocate('explorer');
}
const visitor = new SerializedMmlVisitor(this.mmlFactory);
const toMathML = ((node: MmlNode) => visitor.visitTree(node));
this.options.MathItem = ExplorerMathItemMixin(this.options.MathItem, toMathML);
// TODO: set backward compatibility options here.
this.explorerRegions = initExplorerRegions(this);
}
/**
* Add the Explorer to the MathItems in this MathDocument
*
* @return {ExplorerMathDocument} The MathDocument (so calls can be chained)
*/
public explorable(): ExplorerMathDocument {
if (!this.processed.isSet('explorer')) {
if (this.options.enableExplorer) {
for (const math of this.math) {
(math as ExplorerMathItem).explorable(this);
}
}
this.processed.set('explorer');
}
return this;
}
/**
* @override
*/
public state(state: number, restore: boolean = false) {
super.state(state, restore);
if (state < STATE.EXPLORER) {
this.processed.clear('explorer');
}
return this;
}
};
}
/*==========================================================================*/
/**
* Add Explorer functions to a Handler instance
*
* @param {Handler} handler The Handler instance to enhance
* @param {MathML} MmlJax A MathML input jax to be used for the semantic enrichment
* @returns {Handler} The handler that was modified (for purposes of chainging extensions)
*/
export function ExplorerHandler(handler: HANDLER, MmlJax: MATHML = null): HANDLER {
if (!handler.documentClass.prototype.enrich && MmlJax) {
handler = EnrichHandler(handler, MmlJax);
}
handler.documentClass = ExplorerMathDocumentMixin(handler.documentClass as any);
return handler;
}
/*==========================================================================*/
/**
* The regions objects needed for the explorers.
*/
export type ExplorerRegions = {
speechRegion?: LiveRegion,
brailleRegion?: LiveRegion,
magnifier?: HoverRegion,
tooltip1?: ToolTip,
tooltip2?: ToolTip,
tooltip3?: ToolTip
};
/**
* Initializes the regions needed for a document.
* @param {ExplorerMathDocument} document The current document.
*/
function initExplorerRegions(document: ExplorerMathDocument) {
return {
speechRegion: new LiveRegion(document),
brailleRegion: new LiveRegion(document),
magnifier: new HoverRegion(document),
tooltip1: new ToolTip(document),
tooltip2: new ToolTip(document),
tooltip3: new ToolTip(document)
};
}
/**
* Type of explorer initialization methods.
* @type {(ExplorerMathDocument, HTMLElement, any[]): Explorer}
*/
type ExplorerInit = (doc: ExplorerMathDocument,
node: HTMLElement, ...rest: any[]) => Explorer;
/**
* Generation methods for all MathJax explorers available via option settings.
*/
let allExplorers: {[options: string]: ExplorerInit} = {
speech: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => {
let explorer = ke.SpeechExplorer.create(
doc, doc.explorerRegions.speechRegion, node, ...rest) as ke.SpeechExplorer;
explorer.speechGenerator.setOptions({
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.showRegion = 'subtitles';
return explorer;
},
braille: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => {
let explorer = ke.SpeechExplorer.create(
doc, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer;
explorer.speechGenerator.setOptions({locale: 'nemeth', domain: 'default',
style: 'default', modality: 'braille'});
explorer.showRegion = 'viewBraille';
return explorer;
},
keyMagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) =>
ke.Magnifier.create(doc, doc.explorerRegions.magnifier, node, ...rest),
mouseMagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
me.ContentHoverer.create(doc, doc.explorerRegions.magnifier, node,
(x: HTMLElement) => x.hasAttribute('data-semantic-type'),
(x: HTMLElement) => x),
hover: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
me.FlameHoverer.create(doc, null, node),
infoType: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
me.ValueHoverer.create(doc, doc.explorerRegions.tooltip1, node,
(x: HTMLElement) => x.hasAttribute('data-semantic-type'),
(x: HTMLElement) => x.getAttribute('data-semantic-type')),
infoRole: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
me.ValueHoverer.create(doc, doc.explorerRegions.tooltip2, node,
(x: HTMLElement) => x.hasAttribute('data-semantic-role'),
(x: HTMLElement) => x.getAttribute('data-semantic-role')),
infoPrefix: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
me.ValueHoverer.create(doc, doc.explorerRegions.tooltip3, node,
(x: HTMLElement) => x.hasAttribute('data-semantic-prefix'),
(x: HTMLElement) => x.getAttribute('data-semantic-prefix')),
flame: (doc: ExplorerMathDocument, node: HTMLElement, ..._rest: any[]) =>
FlameColorer.create(doc, null, node),
treeColoring: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) =>
TreeColorer.create(doc, null, node, ...rest)
};
/**
* Initialises explorers for a document.
* @param {ExplorerMathDocument} document The target document.
* @param {HTMLElement} node The node explorers will be attached to.
* @param {string} mml The corresponding Mathml node as a string.
* @return {Explorer[]} A list of initialised explorers.
*/
function initExplorers(document: ExplorerMathDocument, node: HTMLElement, mml: string): {[key: string]: Explorer} {
let explorers: {[key: string]: Explorer} = {};
for (let key of Object.keys(allExplorers)) {
explorers[key] = allExplorers[key](document, node, mml);
}
return explorers;
}
/* Context Menu Interactions */
/**
* Sets a list of a11y options for a given document.
* @param {HTMLDOCUMENT} document The current document.
* @param {{[key: string]: any}} options Association list for a11y option value pairs.
*/
export function setA11yOptions(document: HTMLDOCUMENT, options: {[key: string]: any}) {
let sreOptions = Sre.engineSetup() as {[name: string]: string};
for (let key in options) {
if (document.options.a11y[key] !== undefined) {
setA11yOption(document, key, options[key]);
if (key === 'locale') {
document.options.sre[key] = options[key];
}
continue;
}
if (sreOptions[key] !== undefined) {
document.options.sre[key] = options[key];
}
}
// Reinit explorers
for (let item of document.math) {
(item as ExplorerMathItem).attachExplorers(document as ExplorerMathDocument);
}
}
/**
* Sets a single a11y option for a menu name.
* @param {HTMLDOCUMENT} document The current document.
* @param {string} option The option name in the menu.
* @param {string|boolean} value The new value.
*/
export function setA11yOption(document: HTMLDOCUMENT, option: string, value: string | boolean) {
switch (option) {
case 'magnification':
switch (value) {
case 'None':
document.options.a11y.magnification = value;
document.options.a11y.keyMagnifier = false;
document.options.a11y.mouseMagnifier = false;
break;
case 'Keyboard':
document.options.a11y.magnification = value;
document.options.a11y.keyMagnifier = true;
document.options.a11y.mouseMagnifier = false;
break;
case 'Mouse':
document.options.a11y.magnification = value;
document.options.a11y.keyMagnifier = false;
document.options.a11y.mouseMagnifier = true;
break;
}
break;
case 'highlight':
switch (value) {
case 'None':
document.options.a11y.highlight = value;
document.options.a11y.hover = false;
document.options.a11y.flame = false;
break;
case 'Hover':
document.options.a11y.highlight = value;
document.options.a11y.hover = true;
document.options.a11y.flame = false;
break;
case 'Flame':
document.options.a11y.highlight = value;
document.options.a11y.hover = false;
document.options.a11y.flame = true;
break;
}
break;
default:
document.options.a11y[option] = value;
}
}
/**
* Values for the ClearSpeak preference variables.
*/
let csPrefsSetting: {[pref: string]: string} = {};
/**
* Generator of all variables for the Clearspeak Preference settings.
* @param {MJContextMenu} menu The current context menu.
* @param {string[]} prefs The preferences.
*/
let csPrefsVariables = function(menu: MJContextMenu, prefs: string[]) {
let srVariable = menu.pool.lookup('speechRules');
for (let pref of prefs) {
if (csPrefsSetting[pref]) continue;
menu.factory.get('variable')(menu.factory, {
name: 'csprf_' + pref,
setter: (value: string) => {
csPrefsSetting[pref] = value;
srVariable.setValue(
'clearspeak-' +
Sre.clearspeakPreferences.addPreference(
Sre.clearspeakStyle(), pref, value)
);
},
getter: () => { return csPrefsSetting[pref] || 'Auto'; }
}, menu.pool);
}
};
/**
* Generate the selection box for the Clearspeak Preferences.
* @param {MJContextMenu} menu The current context menu.
* @param {string} locale The current locale.
*/
let csSelectionBox = function(menu: MJContextMenu, locale: string) {
let prefs = Sre.clearspeakPreferences.getLocalePreferences();
let props = prefs[locale];
if (!props) {
let csEntry = menu.findID('Accessibility', 'Speech', 'Clearspeak');
if (csEntry) {
csEntry.disable();
}
return null;
}
csPrefsVariables(menu, Object.keys(props));
let items = [];
for (const prop of Object.getOwnPropertyNames(props)) {
items.push({
'title': prop,
'values': props[prop].map(x => x.replace(RegExp('^' + prop + '_'), '')),
'variable': 'csprf_' + prop
});
}
let sb = menu.factory.get('selectionBox')(menu.factory, {
'title': 'Clearspeak Preferences',
'signature': '',
'order': 'alphabetic',
'grid': 'square',
'selections': items
}, menu);
return {'type': 'command',
'id': 'ClearspeakPreferences',
'content': 'Select Preferences',
'action': () => sb.post(0, 0)};
};
/**
* Creates dynamic clearspeak menu.
* @param {MJContextMenu} menu The context menu.
* @param {Submenu} sub The submenu to attach.
*/
let csMenu = function(menu: MJContextMenu, sub: Submenu) {
let locale = menu.pool.lookup('locale').getValue() as string;
const box = csSelectionBox(menu, locale);
let items: Object[] = [];
try {
items = Sre.clearspeakPreferences.smartPreferences(
menu.mathItem, locale);
} catch (e) {
console.log(e);
}
if (box) {
items.splice(2, 0, box);
}
return menu.factory.get('subMenu')(menu.factory, {
items: items,
id: 'Clearspeak'
}, sub);
};
MJContextMenu.DynamicSubmenus.set('Clearspeak', csMenu);
/**
* Creates dynamic locale menu.
* @param {MJContextMenu} menu The context menu.
* @param {Submenu} sub The submenu to attach.
*/
let language = function(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;
radios.push({type: 'radio', id: lang,
content: Sre.locales.get(lang) || lang, variable: 'locale'});
}
radios.sort((x, y) => x.content.localeCompare(y.content, 'en'));
return menu.factory.get('subMenu')(menu.factory, {
items: radios, id: 'Language'}, sub);
};
MJContextMenu.DynamicSubmenus.set('A11yLanguage', language);

282
node_modules/mathjax-full/ts/a11y/explorer/Explorer.ts generated vendored Normal file
View File

@@ -0,0 +1,282 @@
/*************************************************************
*
* Copyright (c) 2009-2022 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 Explorers for A11Y purposes.
*
* @author v.sorge@mathjax.org (Volker Sorge)
*/
import {A11yDocument, Region} from './Region.js';
import Sre from '../sre.js';
/**
* A11y explorers.
* @interface
*/
export interface Explorer {
/**
* Flag indicating if the explorer is active.
* @type {boolean}
*/
active: boolean;
/**
* Flag indicating if event bubbling is stopped.
* @type {boolean}
*/
stoppable: boolean;
/**
* Attaches navigator and its event handlers to a node.
*/
Attach(): void;
/**
* Detaches navigator and its event handlers to a node.
*/
Detach(): void;
/**
* Starts the explorer.
*/
Start(): void;
/**
* Stops the explorer.
*/
Stop(): void;
/**
* Adds the events of the explorer to the node's event listener.
*/
AddEvents(): void;
/**
* Removes the events of the explorer from the node's event listener.
*/
RemoveEvents(): void;
/**
* Update the explorer after state changes.
* @param {boolean=} force Forces the update in any case. (E.g., even if
* explorer is inactive.)
*/
Update(force?: boolean): void;
}
/**
* Abstract class implementing the very basic explorer functionality.
*
* Explorers use creator pattern to ensure they automatically attach themselves
* to their node. This class provides the create method and is consequently not
* declared abstract.
*
* @constructor
* @implements {Explorer}
*
* @template T The type that is consumed by the Region of this explorer.
*/
export class AbstractExplorer<T> implements Explorer {
/**
* @override
*/
public stoppable: boolean = true;
/**
* Named events and their functions.
* @type {[string, function(x: Event)][]}
*/
protected events: [string, (x: Event) => void][] = [];
/**
* The Sre highlighter associated with the walker.
* @type {Sre.highlighter}
*/
protected highlighter: Sre.highlighter = this.getHighlighter();
/**
* Flag if explorer is active.
* @type {boolean}
*/
private _active: boolean = false;
/**
* Stops event bubbling.
* @param {Event} event The event that is stopped.
*/
protected static stopEvent(event: Event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
} else if (event.stopPropagation) {
event.stopPropagation();
}
event.cancelBubble = true;
}
/**
* Creator pattern for explorers.
* @param {A11yDocument} document The current document.
* @param {Region<T>} region A region to display results.
* @param {HTMLElement} node The node on which the explorer works.
* @param {any[]} ...rest Remaining information.
* @return {Explorer} An object of the particular explorer class.
*
* @template T
*/
public static create<T>(
document: A11yDocument,
region: Region<T>,
node: HTMLElement, ...rest: any[]
): Explorer {
let explorer = new this(document, region, node, ...rest);
return explorer;
}
/**
* @constructor
* @param {A11yDocument} document The current document.
* @param {Region<T>} region A region to display results.
* @param {HTMLElement} node The node on which the explorer works.
* @param {any[]} ...rest Remaining information.
*/
protected constructor(
public document: A11yDocument,
protected region: Region<T>,
protected node: HTMLElement, ..._rest: any[]
) {
}
/**
* @return {[string, (x: Event) => void][]} The events associated with this
* explorer.
*/
protected Events(): [string, (x: Event) => void][] {
return this.events;
}
/**
* @override
*/
public get active(): boolean {
return this._active;
}
/**
* @override
*/
public set active(flag: boolean) {
this._active = flag;
}
/**
* @override
*/
public Attach() {
this.AddEvents();
}
/**
* @override
*/
public Detach() {
this.RemoveEvents();
}
/**
* @override
*/
public Start() {
this.highlighter = this.getHighlighter();
this.active = true;
}
/**
* @override
*/
public Stop() {
if (this.active) {
this.region.Clear();
this.region.Hide();
this.active = false;
}
}
/**
* @override
*/
public AddEvents() {
for (let [eventkind, eventfunc] of this.events) {
this.node.addEventListener(eventkind, eventfunc);
}
}
/**
* @override
*/
public RemoveEvents() {
for (let [eventkind, eventfunc] of this.events) {
this.node.removeEventListener(eventkind, eventfunc);
}
}
/**
* @override
*/
// @ts-ignore: unused variable
public Update(force: boolean = false): void {}
/**
* @return {Sre.Highlighter} A highlighter for the explorer.
*/
protected getHighlighter(): Sre.highlighter {
let opts = this.document.options.a11y;
let foreground = {color: opts.foregroundColor.toLowerCase(),
alpha: opts.foregroundOpacity / 100};
let background = {color: opts.backgroundColor.toLowerCase(),
alpha: opts.backgroundOpacity / 100};
return Sre.getHighlighter(
background, foreground,
{renderer: this.document.outputJax.name, browser: 'v3'});
}
/**
* Stops the events of this explorer from bubbling.
* @param {Event} event The event to stop.
*/
protected stopEvent(event: Event) {
if (this.stoppable) {
AbstractExplorer.stopEvent(event);
}
}
}

View File

@@ -0,0 +1,460 @@
/*************************************************************
*
* Copyright (c) 2009-2022 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 Explorers based on keyboard events.
*
* @author v.sorge@mathjax.org (Volker Sorge)
*/
import {A11yDocument, Region} from './Region.js';
import {Explorer, AbstractExplorer} from './Explorer.js';
import Sre from '../sre.js';
/**
* Interface for keyboard explorers. Adds the necessary keyboard events.
* @interface
* @extends {Explorer}
*/
export interface KeyExplorer extends Explorer {
/**
* Function to be executed on key down.
* @param {KeyboardEvent} event The keyboard event.
*/
KeyDown(event: KeyboardEvent): void;
/**
* Function to be executed on focus in.
* @param {KeyboardEvent} event The keyboard event.
*/
FocusIn(event: FocusEvent): void;
/**
* Function to be executed on focus out.
* @param {KeyboardEvent} event The keyboard event.
*/
FocusOut(event: FocusEvent): void;
}
/**
* @constructor
* @extends {AbstractExplorer}
*
* @template T The type that is consumed by the Region of this explorer.
*/
export abstract class AbstractKeyExplorer<T> extends AbstractExplorer<T> implements KeyExplorer {
/**
* Flag indicating if the explorer is attached to an object.
*/
public attached: boolean = false;
/**
* The attached Sre walker.
* @type {Walker}
*/
protected walker: Sre.walker;
private eventsAttached: boolean = false;
/**
* @override
*/
protected events: [string, (x: Event) => void][] =
super.Events().concat(
[['keydown', this.KeyDown.bind(this)],
['focusin', this.FocusIn.bind(this)],
['focusout', this.FocusOut.bind(this)]]);
/**
* The original tabindex value before explorer was attached.
* @type {boolean}
*/
private oldIndex: number = null;
/**
* @override
*/
public abstract KeyDown(event: KeyboardEvent): void;
/**
* @override
*/
public FocusIn(_event: FocusEvent) {
}
/**
* @override
*/
public FocusOut(_event: FocusEvent) {
this.Stop();
}
/**
* @override
*/
public Update(force: boolean = false) {
if (!this.active && !force) return;
this.highlighter.unhighlight();
let nodes = this.walker.getFocus(true).getNodes();
if (!nodes.length) {
this.walker.refocus();
nodes = this.walker.getFocus().getNodes();
}
this.highlighter.highlight(nodes as HTMLElement[]);
}
/**
* @override
*/
public Attach() {
super.Attach();
this.attached = true;
this.oldIndex = this.node.tabIndex;
this.node.tabIndex = 1;
this.node.setAttribute('role', 'application');
}
/**
* @override
*/
public AddEvents() {
if (!this.eventsAttached) {
super.AddEvents();
this.eventsAttached = true;
}
}
/**
* @override
*/
public Detach() {
if (this.active) {
this.node.tabIndex = this.oldIndex;
this.oldIndex = null;
this.node.removeAttribute('role');
}
this.attached = false;
}
/**
* @override
*/
public Stop() {
if (this.active) {
this.highlighter.unhighlight();
this.walker.deactivate();
}
super.Stop();
}
}
/**
* Explorer that pushes speech to live region.
* @constructor
* @extends {AbstractKeyExplorer}
*/
export class SpeechExplorer extends AbstractKeyExplorer<string> {
private static updatePromise = Promise.resolve();
/**
* The Sre speech generator associated with the walker.
* @type {SpeechGenerator}
*/
public speechGenerator: Sre.speechGenerator;
/**
* The name of the option used to control when this is being shown
* @type {string}
*/
public showRegion: string = 'subtitles';
private init: boolean = false;
/**
* Flag in case the start method is triggered before the walker is fully
* initialised. I.e., we have to wait for Sre. Then region is re-shown if
* necessary, as otherwise it leads to incorrect stacking.
* @type {boolean}
*/
private restarted: boolean = false;
/**
* @constructor
* @extends {AbstractKeyExplorer}
*/
constructor(public document: A11yDocument,
protected region: Region<string>,
protected node: HTMLElement,
private mml: string) {
super(document, region, node);
this.initWalker();
}
/**
* @override
*/
public Start() {
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);
this.Start();
});
})
.catch((error: Error) => console.log(error.message));
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;
}
/**
* @override
*/
public Update(force: boolean = false) {
super.Update(force);
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({modality: options.modality,
locale: options.locale}))
.then(() => this.region.Update(this.walker.speech()));
});
}
/**
* Computes the speech for the current expression once Sre is ready.
* @param {Walker} walker The sre walker.
*/
public Speech(walker: Sre.walker) {
SpeechExplorer.updatePromise.then(() => {
walker.speech();
this.node.setAttribute('hasspeech', 'true');
this.Update();
if (this.restarted && this.document.options.a11y[this.showRegion]) {
this.region.Show(this.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) {
this.Move(code);
if (this.triggerLink(code)) return;
this.stopEvent(event);
return;
}
if (code === 32 && event.shiftKey || code === 13) {
this.Start();
this.stopEvent(event);
}
}
/**
* 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) {
return false;
}
let node = this.walker.getFocus().getNodes()?.[0];
let focus = node?.
getAttribute('data-semantic-postfix')?.
match(/(^| )link($| )/);
if (focus) {
node.parentNode.dispatchEvent(new MouseEvent('click'));
return true;
}
return false;
}
/**
* @override
*/
public Move(key: number) {
this.walker.move(key);
this.Update();
}
/**
* 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
* generator.
*/
private getOptions(): {[key: string]: string} {
let options = this.speechGenerator.getOptions();
let sreOptions = this.document.options.sre;
if (options.modality === 'speech' &&
(options.locale !== sreOptions.locale ||
options.domain !== sreOptions.domain ||
options.style !== sreOptions.style)) {
options.domain = sreOptions.domain;
options.style = sreOptions.style;
options.locale = sreOptions.locale;
this.walker.update(options);
}
return options;
}
}
/**
* Explorer that magnifies what is currently explored. Uses a hover region.
* @constructor
* @extends {AbstractKeyExplorer}
*/
export class Magnifier extends AbstractKeyExplorer<HTMLElement> {
/**
* @constructor
* @extends {AbstractKeyExplorer}
*/
constructor(public document: A11yDocument,
protected region: Region<HTMLElement>,
protected node: HTMLElement,
private mml: string) {
super(document, 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 Move(key: number) {
let result = this.walker.move(key);
if (result) {
this.Update();
}
}
/**
* @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);
}
}
}

View File

@@ -0,0 +1,224 @@
/*************************************************************
*
* Copyright (c) 2009-2022 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 Explorers based on mouse events.
*
* @author v.sorge@mathjax.org (Volker Sorge)
*/
import {A11yDocument, DummyRegion, Region} from './Region.js';
import {Explorer, AbstractExplorer} from './Explorer.js';
import '../sre.js';
/**
* Interface for mouse explorers. Adds the necessary mouse events.
* @interface
* @extends {Explorer}
*/
export interface MouseExplorer extends Explorer {
/**
* Function to be executed on mouse over.
* @param {MouseEvent} event The mouse event.
*/
MouseOver(event: MouseEvent): void;
/**
* Function to be executed on mouse out.
* @param {MouseEvent} event The mouse event.
*/
MouseOut(event: MouseEvent): void;
}
/**
* @constructor
* @extends {AbstractExplorer}
*
* @template T The type that is consumed by the Region of this explorer.
*/
export abstract class AbstractMouseExplorer<T> extends AbstractExplorer<T> implements MouseExplorer {
/**
* @override
*/
protected events: [string, (x: Event) => void][] =
super.Events().concat([
['mouseover', this.MouseOver.bind(this)],
['mouseout', this.MouseOut.bind(this)]
]);
/**
* @override
*/
public MouseOver(_event: MouseEvent) {
this.Start();
}
/**
* @override
*/
public MouseOut(_event: MouseEvent) {
this.Stop();
}
}
/**
* Exploration via hovering.
* @constructor
* @extends {AbstractMouseExplorer}
*/
export abstract class Hoverer<T> extends AbstractMouseExplorer<T> {
/**
* Remember the last position to avoid flickering.
* @type {[number, number]}
*/
protected coord: [number, number];
/**
* @constructor
* @extends {AbstractMouseExplorer<T>}
*
* @param {A11yDocument} document The current document.
* @param {Region<T>} region A region to display results.
* @param {HTMLElement} node The node on which the explorer works.
* @param {(node: HTMLElement) => boolean} nodeQuery Predicate on nodes that
* will fire the hoverer.
* @param {(node: HTMLElement) => T} nodeAccess Accessor to extract node value
* that is passed to the region.
*
* @template T
*/
protected constructor(public document: A11yDocument,
protected region: Region<T>,
protected node: HTMLElement,
protected nodeQuery: (node: HTMLElement) => boolean,
protected nodeAccess: (node: HTMLElement) => T) {
super(document, region, node);
}
/**
* @override
*/
public MouseOut(event: MouseEvent) {
if (event.clientX === this.coord[0] &&
event.clientY === this.coord[1]) {
return;
}
this.highlighter.unhighlight();
this.region.Hide();
super.MouseOut(event);
}
/**
* @override
*/
public MouseOver(event: MouseEvent) {
super.MouseOver(event);
let target = event.target as HTMLElement;
this.coord = [event.clientX, event.clientY];
let [node, kind] = this.getNode(target);
if (!node) {
return;
}
this.highlighter.unhighlight();
this.highlighter.highlight([node]);
this.region.Update(kind);
this.region.Show(node, this.highlighter);
}
/**
* Retrieves the closest node on which the node query fires. Thereby closest
* is defined as:
* 1. The node or its ancestor on which the query is true.
* 2. In case 1 does not exist the left-most child on which query is true.
* 3. Otherwise fails.
*
* @param {HTMLElement} node The node on which the mouse event fired.
* @return {[HTMLElement, T]} Node and output pair if successful.
*/
public getNode(node: HTMLElement): [HTMLElement, T] {
let original = node;
while (node && node !== this.node) {
if (this.nodeQuery(node)) {
return [node, this.nodeAccess(node)];
}
node = node.parentNode as HTMLElement;
}
node = original;
while (node) {
if (this.nodeQuery(node)) {
return [node, this.nodeAccess(node)];
}
let child = node.childNodes[0] as HTMLElement;
node = (child && child.tagName === 'defs') ? // This is for SVG.
node.childNodes[1] as HTMLElement : child;
}
return [null, null];
}
}
/**
* Hoverer that displays information on nodes (e.g., as tooltips).
* @constructor
* @extends {Hoverer}
*/
export class ValueHoverer extends Hoverer<string> { }
/**
* Hoverer that displays node content (e.g., for magnification).
* @constructor
* @extends {Hoverer}
*/
export class ContentHoverer extends Hoverer<HTMLElement> { }
/**
* Highlights maction nodes on hovering.
* @constructor
* @extends {Hoverer}
*/
export class FlameHoverer extends Hoverer<void> {
/**
* @override
*/
protected constructor(
public document: A11yDocument,
_ignore: any,
protected node: HTMLElement) {
super(document, new DummyRegion(document), node,
x => this.highlighter.isMactionNode(x),
() => {});
}
}

519
node_modules/mathjax-full/ts/a11y/explorer/Region.ts generated vendored Normal file
View File

@@ -0,0 +1,519 @@
/*************************************************************
*
* Copyright (c) 2009-2022 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 Regions for A11y purposes.
*
* @author v.sorge@mathjax.org (Volker Sorge)
*/
import {MathDocument} from '../../core/MathDocument.js';
import {CssStyles} from '../../util/StyleList.js';
import Sre from '../sre.js';
export type A11yDocument = MathDocument<HTMLElement, Text, Document>;
export interface Region<T> {
/**
* Adds a style sheet for the live region to the document.
*/
AddStyles(): void;
/**
* Adds the region element to the document.
*/
AddElement(): void;
/**
* Shows the live region in the document.
* @param {HTMLElement} node
* @param {Sre.highlighter} highlighter
*/
Show(node: HTMLElement, highlighter: Sre.highlighter): void;
/**
* Takes the element out of the document flow.
*/
Hide(): void;
/**
* Clears the content of the region.
*/
Clear(): void;
/**
* Updates the content of the region.
* @template T
*/
Update(content: T): void;
}
export abstract class AbstractRegion<T> implements Region<T> {
/**
* CSS Classname of the element.
* @type {String}
*/
protected static className: string;
/**
* True if the style has already been added to the document.
* @type {boolean}
*/
protected static styleAdded: boolean = false;
/**
* The CSS style that needs to be added for this type of region.
* @type {CssStyles}
*/
protected static style: CssStyles;
/**
* The outer div node.
* @type {HTMLElement}
*/
protected div: HTMLElement;
/**
* The inner node.
* @type {HTMLElement}
*/
protected inner: HTMLElement;
/**
* The actual class name to refer to static elements of a class.
* @type {typeof AbstractRegion}
*/
protected CLASS: typeof AbstractRegion;
/**
* @constructor
* @param {A11yDocument} document The document the live region is added to.
*/
constructor(public document: A11yDocument) {
this.CLASS = this.constructor as typeof AbstractRegion;
this.AddStyles();
this.AddElement();
}
/**
* @override
*/
public AddStyles() {
if (this.CLASS.styleAdded) {
return;
}
// TODO: should that be added to document.documentStyleSheet()?
let node = this.document.adaptor.node('style');
node.innerHTML = this.CLASS.style.cssText;
this.document.adaptor.head(this.document.adaptor.document).
appendChild(node);
this.CLASS.styleAdded = true;
}
/**
* @override
*/
public AddElement() {
let element = this.document.adaptor.node('div');
element.classList.add(this.CLASS.className);
element.style.backgroundColor = 'white';
this.div = element;
this.inner = this.document.adaptor.node('div');
this.div.appendChild(this.inner);
this.document.adaptor.
body(this.document.adaptor.document).
appendChild(this.div);
}
/**
* @override
*/
public Show(node: HTMLElement, highlighter: Sre.highlighter) {
this.position(node);
this.highlight(highlighter);
this.div.classList.add(this.CLASS.className + '_Show');
}
/**
* Computes the position where to place the element wrt. to the given node.
* @param {HTMLElement} node The reference node.
*/
protected abstract position(node: HTMLElement): void;
/**
* Highlights the region.
* @param {Sre.highlighter} highlighter The Sre highlighter.
*/
protected abstract highlight(highlighter: Sre.highlighter): void;
/**
* @override
*/
public Hide() {
this.div.classList.remove(this.CLASS.className + '_Show');
}
/**
* @override
*/
public abstract Clear(): void;
/**
* @override
*/
public abstract Update(content: T): void;
/**
* Auxiliary position method that stacks shown regions of the same type.
* @param {HTMLElement} node The reference node.
*/
protected stackRegions(node: HTMLElement) {
// TODO: This could be made more efficient by caching regions of a class.
const rect = node.getBoundingClientRect();
let baseBottom = 0;
let baseLeft = Number.POSITIVE_INFINITY;
let regions = this.document.adaptor.document.getElementsByClassName(
this.CLASS.className + '_Show');
// Get all the shown regions (one is this element!) and append at bottom.
for (let i = 0, region; region = regions[i]; i++) {
if (region !== this.div) {
baseBottom = Math.max(region.getBoundingClientRect().bottom, baseBottom);
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;
this.div.style.top = bot + 'px';
this.div.style.left = left + 'px';
}
}
export class DummyRegion extends AbstractRegion<void> {
/**
* @override
*/
public Clear() {}
/**
* @override
*/
public Update() {}
/**
* @override
*/
public Hide() {}
/**
* @override
*/
public Show() {}
/**
* @override
*/
public AddElement() {}
/**
* @override
*/
public AddStyles() {}
/**
* @override
*/
public position() {}
/**
* @override
*/
public highlight(_highlighter: Sre.highlighter) {}
}
export class StringRegion extends AbstractRegion<string> {
/**
* @override
*/
public Clear(): void {
this.Update('');
this.inner.style.top = '';
this.inner.style.backgroundColor = '';
}
/**
* @override
*/
public Update(speech: string) {
this.inner.textContent = '';
this.inner.textContent = speech;
}
/**
* @override
*/
protected position(node: HTMLElement) {
this.stackRegions(node);
}
/**
* @override
*/
protected highlight(highlighter: Sre.highlighter) {
const color = highlighter.colorString();
this.inner.style.backgroundColor = color.background;
this.inner.style.color = color.foreground;
}
}
export class ToolTip extends StringRegion {
/**
* @override
*/
protected static className = 'MJX_ToolTip';
/**
* @override
*/
protected static style: CssStyles =
new CssStyles({
['.' + ToolTip.className]: {
position: 'absolute', display: 'inline-block',
height: '1px', width: '1px'
},
['.' + ToolTip.className + '_Show']: {
width: 'auto', height: 'auto', opacity: 1, 'text-align': 'center',
'border-radius': '6px', padding: '0px 0px',
'border-bottom': '1px dotted black', position: 'absolute',
'z-index': 202
}
});
}
export class LiveRegion extends StringRegion {
/**
* @override
*/
protected static className = 'MJX_LiveRegion';
/**
* @override
*/
protected static style: CssStyles =
new CssStyles({
['.' + LiveRegion.className]: {
position: 'absolute', top: '0', height: '1px', width: '1px',
padding: '1px', overflow: 'hidden'
},
['.' + LiveRegion.className + '_Show']: {
top: '0', position: 'absolute', width: 'auto', height: 'auto',
padding: '0px 0px', opacity: 1, 'z-index': '202',
left: 0, right: 0, 'margin': '0 auto',
'background-color': 'rgba(0, 0, 255, 0.2)', 'box-shadow': '0px 10px 20px #888',
border: '2px solid #CCCCCC'
}
});
/**
* @constructor
* @param {A11yDocument} document The document the live region is added to.
*/
constructor(public document: A11yDocument) {
super(document);
this.div.setAttribute('aria-live', 'assertive');
}
}
// Region that overlays the current element.
export class HoverRegion extends AbstractRegion<HTMLElement> {
/**
* @override
*/
protected static className = 'MJX_HoverRegion';
/**
* @override
*/
protected static style: CssStyles =
new CssStyles({
['.' + HoverRegion.className]: {
position: 'absolute', height: '1px', width: '1px',
padding: '1px', overflow: 'hidden'
},
['.' + HoverRegion.className + '_Show']: {
position: 'absolute', width: 'max-content', height: 'auto',
padding: '0px 0px', opacity: 1, 'z-index': '202', 'margin': '0 auto',
'background-color': 'rgba(0, 0, 255, 0.2)',
'box-shadow': '0px 10px 20px #888', border: '2px solid #CCCCCC'
}
});
/**
* @constructor
* @param {A11yDocument} document The document the live region is added to.
*/
constructor(public document: A11yDocument) {
super(document);
this.inner.style.lineHeight = '0';
}
/**
* Sets the position of the region with respect to align parameter. There are
* three options: top, bottom and center. Center is the default.
*
* @param {HTMLElement} node The node that is displayed.
*/
protected position(node: HTMLElement) {
const nodeRect = node.getBoundingClientRect();
const divRect = this.div.getBoundingClientRect();
const xCenter = nodeRect.left + (nodeRect.width / 2);
let left = xCenter - (divRect.width / 2);
left = (left < 0) ? 0 : left;
left = left + window.pageXOffset;
let top;
switch (this.document.options.a11y.align) {
case 'top':
top = nodeRect.top - divRect.height - 10 ;
break;
case 'bottom':
top = nodeRect.bottom + 10;
break;
case 'center':
default:
const yCenter = nodeRect.top + (nodeRect.height / 2);
top = yCenter - (divRect.height / 2);
}
top = top + window.pageYOffset;
top = (top < 0) ? 0 : top;
this.div.style.top = top + 'px';
this.div.style.left = left + 'px';
}
/**
* @override
*/
protected highlight(highlighter: Sre.highlighter) {
// TODO Do this with styles to avoid the interaction of SVG/CHTML.
if (this.inner.firstChild &&
!(this.inner.firstChild as HTMLElement).hasAttribute('sre-highlight')) {
return;
}
const color = highlighter.colorString();
this.inner.style.backgroundColor = color.background;
this.inner.style.color = color.foreground;
}
/**
* @override
*/
public Show(node: HTMLElement, highlighter: Sre.highlighter) {
this.div.style.fontSize = this.document.options.a11y.magnify;
this.Update(node);
super.Show(node, highlighter);
}
/**
* @override
*/
public Clear() {
this.inner.textContent = '';
this.inner.style.top = '';
this.inner.style.backgroundColor = '';
}
/**
* @override
*/
public Update(node: HTMLElement) {
this.Clear();
let mjx = this.cloneNode(node);
this.inner.appendChild(mjx);
}
/**
* Clones the node to put into the hover region.
* @param {HTMLElement} node The original node.
* @return {HTMLElement} The cloned node.
*/
private cloneNode(node: HTMLElement): HTMLElement {
let mjx = node.cloneNode(true) as HTMLElement;
if (mjx.nodeName !== 'MJX-CONTAINER') {
// remove element spacing (could be done in CSS)
if (mjx.nodeName !== 'g') {
mjx.style.marginLeft = mjx.style.marginRight = '0';
}
let container = node;
while (container && container.nodeName !== 'MJX-CONTAINER') {
container = container.parentNode as HTMLElement;
}
if (mjx.nodeName !== 'MJX-MATH' && mjx.nodeName !== 'svg') {
const child = container.firstChild;
mjx = child.cloneNode(false).appendChild(mjx).parentNode as HTMLElement;
//
// SVG specific
//
if (mjx.nodeName === 'svg') {
(mjx.firstChild as HTMLElement).setAttribute('transform', 'matrix(1 0 0 -1 0 0)');
const W = parseFloat(mjx.getAttribute('viewBox').split(/ /)[2]);
const w = parseFloat(mjx.getAttribute('width'));
const {x, y, width, height} = (node as any).getBBox();
mjx.setAttribute('viewBox', [x, -(y + height), width, height].join(' '));
mjx.removeAttribute('style');
mjx.setAttribute('width', (w / W * width) + 'ex');
mjx.setAttribute('height', (w / W * height) + 'ex');
container.setAttribute('sre-highlight', 'false');
}
}
mjx = container.cloneNode(false).appendChild(mjx).parentNode as HTMLElement;
// remove displayed math margins (could be done in CSS)
mjx.style.margin = '0';
}
return mjx;
}
}

View File

@@ -0,0 +1,124 @@
/*************************************************************
*
* Copyright (c) 2009-2022 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 Tree Explorers allow to switch on effects on the entire
* expression tree.
*
* @author v.sorge@mathjax.org (Volker Sorge)
*/
import {A11yDocument, Region} from './Region.js';
import {Explorer, AbstractExplorer} from './Explorer.js';
import Sre from '../sre.js';
export interface TreeExplorer extends Explorer {
}
export class AbstractTreeExplorer extends AbstractExplorer<void> {
/**
* @override
*/
protected constructor(public document: A11yDocument,
protected region: Region<void>,
protected node: HTMLElement,
protected mml: HTMLElement) {
super(document, null, node);
}
/**
* @override
*/
public readonly stoppable = false;
/**
* @override
*/
public Attach() {
super.Attach();
this.Start();
}
/**
* @override
*/
public Detach() {
this.Stop();
super.Detach();
}
}
export class FlameColorer extends AbstractTreeExplorer {
/**
* @override
*/
public Start() {
if (this.active) return;
this.active = true;
this.highlighter.highlightAll(this.node);
}
/**
* @override
*/
public Stop() {
if (this.active) {
this.highlighter.unhighlightAll();
}
this.active = false;
}
}
export class TreeColorer extends AbstractTreeExplorer {
/**
* @override
*/
public Start() {
if (this.active) return;
this.active = true;
let generator = Sre.getSpeechGenerator('Color');
if (!this.node.hasAttribute('hasforegroundcolor')) {
generator.generateSpeech(this.node, this.mml);
this.node.setAttribute('hasforegroundcolor', 'true');
}
// TODO: Make this cleaner in Sre.
(this.highlighter as any).colorizeAll(this.node);
}
/**
* @override
*/
public Stop() {
if (this.active) {
(this.highlighter as any).uncolorizeAll(this.node);
}
this.active = false;
}
}

27
node_modules/mathjax-full/ts/a11y/mathmaps.ts generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 Base imports of sre locales.
*
* @author dpvc@mathjax.org (Davide Cervone)
* @author v.sorge@mathjax.org (Volker Sorge)
*/
const MathMaps: Map<string, {[path: string]: any}> = new Map();
export default MathMaps;

361
node_modules/mathjax-full/ts/a11y/semantic-enrich.ts generated vendored Normal file
View File

@@ -0,0 +1,361 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 Mixin that adds semantic enrichment to internal MathML
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {mathjax} from '../mathjax.js';
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 {MathML} from '../input/mathml.js';
import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
import {OptionList, expandable} from '../util/Options.js';
import Sre from './sre.js';
/*==========================================================================*/
/**
* The current speech setting for Sre
*/
let currentSpeech = 'none';
/**
* Generic constructor for Mixins
*/
export type Constructor<T> = new(...args: any[]) => T;
/*==========================================================================*/
/**
* Add STATE value for being enriched (after COMPILED and before TYPESET)
*/
newState('ENRICHED', 30);
/**
* Add STATE value for adding speech (after TYPESET)
*/
newState('ATTACHSPEECH', 155);
/**
* The functions added to MathItem for enrichment
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export interface EnrichedMathItem<N, T, D> extends MathItem<N, T, D> {
/**
* @param {MathDocument} document The document where enrichment is occurring
* @param {boolean} force True to force the enrichment even if not enabled
*/
enrich(document: MathDocument<N, T, D>, force?: boolean): void;
/**
* @param {MathDocument} document The document where enrichment is occurring
*/
attachSpeech(document: MathDocument<N, T, D>): void;
}
/**
* The mixin for adding enrichment to MathItems
*
* @param {B} BaseMathItem The MathItem class to be extended
* @param {MathML} MmlJax The MathML input jax used to convert the enriched MathML
* @param {Function} toMathML The function to serialize the internal MathML
* @return {EnrichedMathItem} The enriched MathItem class
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
* @template B The MathItem class to extend
*/
export function EnrichedMathItemMixin<N, T, D, B extends Constructor<AbstractMathItem<N, T, D>>>(
BaseMathItem: B,
MmlJax: MathML<N, T, D>,
toMathML: (node: MmlNode) => string
): Constructor<EnrichedMathItem<N, T, D>> & B {
return class extends BaseMathItem {
/**
* @param {any} node The node to be serialized
* @return {string} The serialized version of node
*/
protected serializeMml(node: any): string {
if ('outerHTML' in node) {
return node.outerHTML;
}
//
// For IE11
//
if (typeof Element !== 'undefined' && typeof window !== 'undefined' && node instanceof Element) {
const div = window.document.createElement('div');
div.appendChild(node);
return div.innerHTML;
}
//
// For NodeJS version of Sre
//
return node.toString();
}
/**
* @param {MathDocument} document The MathDocument for the MathItem
* @param {boolean} force True to force the enrichment even if not enabled
*/
public enrich(document: MathDocument<N, T, D>, 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;
mathjax.retryAfter(
Sre.setupEngine(document.options.sre).then(
() => Sre.sreReady()));
}
const math = new document.options.MathItem('', MmlJax);
try {
const mml = this.inputData.originalMml = toMathML(this.root);
math.math = this.serializeMml(Sre.toEnriched(mml));
math.display = this.display;
math.compile(document);
this.root = math.root;
this.inputData.enrichedMml = math.math;
} catch (err) {
document.options.enrichError(document, this, err);
}
}
this.state(STATE.ENRICHED);
}
/**
* @param {MathDocument} document The MathDocument for the MathItem
*/
public attachSpeech(document: MathDocument<N, T, D>) {
if (this.state() >= STATE.ATTACHSPEECH) return;
const attributes = this.root.attributes;
const speech = (attributes.get('aria-label') ||
this.getSpeech(this.root)) as string;
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.state(STATE.ATTACHSPEECH);
}
/**
* Retrieves the actual speech element that should be used as aria label.
* @param {MmlNode} node The root node to search from.
* @return {string} The speech content.
*/
private getSpeech(node: MmlNode): string {
const attributes = node.attributes;
if (!attributes) return '';
const speech = attributes.getExplicit('data-semantic-speech') as string;
if (!attributes.getExplicit('data-semantic-parent') && speech) {
return speech;
}
for (let child of node.childNodes) {
let value = this.getSpeech(child as MmlNode);
if (value != null) {
return value;
}
}
return '';
}
};
}
/*==========================================================================*/
/**
* The functions added to MathDocument for enrichment
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export interface EnrichedMathDocument<N, T, D> extends AbstractMathDocument<N, T, D> {
/**
* Perform enrichment on the MathItems in the MathDocument
*
* @return {EnrichedMathDocument} The MathDocument (so calls can be chained)
*/
enrich(): EnrichedMathDocument<N, T, D>;
/**
* Attach speech to the MathItems in the MathDocument
*
* @return {EnrichedMathDocument} The MathDocument (so calls can be chained)
*/
attachSpeech(): EnrichedMathDocument<N, T, D>;
/**
* @param {EnrichedMathDocument} doc The MathDocument for the error
* @paarm {EnrichedMathItem} math The MathItem causing the error
* @param {Error} err The error being processed
*/
enrichError(doc: EnrichedMathDocument<N, T, D>, math: EnrichedMathItem<N, T, D>, err: Error): void;
}
/**
* The mixin for adding enrichment to MathDocuments
*
* @param {B} BaseDocument The MathDocument class to be extended
* @param {MathML} MmlJax The MathML input jax used to convert the enriched MathML
* @return {EnrichedMathDocument} The enriched MathDocument class
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
* @template B The MathDocument class to extend
*/
export function EnrichedMathDocumentMixin<N, T, D, B extends MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
BaseDocument: B,
MmlJax: MathML<N, T, D>,
): MathDocumentConstructor<EnrichedMathDocument<N, T, D>> & B {
return class extends BaseDocument {
/**
* @override
*/
public static OPTIONS: OptionList = {
...BaseDocument.OPTIONS,
enableEnrichment: true,
enrichError: (doc: EnrichedMathDocument<N, T, D>,
math: EnrichedMathItem<N, T, D>,
err: Error) => doc.enrichError(doc, math, err),
renderActions: expandable({
...BaseDocument.OPTIONS.renderActions,
enrich: [STATE.ENRICHED],
attachSpeech: [STATE.ATTACHSPEECH]
}),
sre: expandable({
speech: 'none', // by default no speech is included
domain: 'mathspeak', // speech rules domain
style: 'default', // speech rules style
locale: 'en' // switch the locale
}),
};
/**
* Enrich the MathItem class used for this MathDocument, and create the
* temporary MathItem used for enrchment
*
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
MmlJax.setMmlFactory(this.mmlFactory);
const ProcessBits = (this.constructor as typeof AbstractMathDocument).ProcessBits;
if (!ProcessBits.has('enriched')) {
ProcessBits.allocate('enriched');
ProcessBits.allocate('attach-speech');
}
const visitor = new SerializedMmlVisitor(this.mmlFactory);
const toMathML = ((node: MmlNode) => visitor.visitTree(node));
this.options.MathItem =
EnrichedMathItemMixin<N, T, D, Constructor<AbstractMathItem<N, T, D>>>(
this.options.MathItem, MmlJax, toMathML
);
}
/**
* Attach speech from a MathItem to a node
*/
public attachSpeech() {
if (!this.processed.isSet('attach-speech')) {
for (const math of this.math) {
(math as EnrichedMathItem<N, T, D>).attachSpeech(this);
}
this.processed.set('attach-speech');
}
return this;
}
/**
* Enrich the MathItems in this MathDocument
*/
public enrich() {
if (!this.processed.isSet('enriched')) {
if (this.options.enableEnrichment) {
for (const math of this.math) {
(math as EnrichedMathItem<N, T, D>).enrich(this);
}
}
this.processed.set('enriched');
}
return this;
}
/**
*/
public enrichError(_doc: EnrichedMathDocument<N, T, D>, _math: EnrichedMathItem<N, T, D>, err: Error) {
console.warn('Enrichment error:', err);
}
/**
* @override
*/
public state(state: number, restore: boolean = false) {
super.state(state, restore);
if (state < STATE.ENRICHED) {
this.processed.clear('enriched');
}
return this;
}
};
}
/*==========================================================================*/
/**
* Add enrichment a Handler instance
*
* @param {Handler} handler The Handler instance to enhance
* @param {MathML} MmlJax The MathML input jax to use for reading the enriched MathML
* @return {Handler} The handler that was modified (for purposes of chainging extensions)
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export function EnrichHandler<N, T, D>(handler: Handler<N, T, D>, MmlJax: MathML<N, T, D>): Handler<N, T, D> {
MmlJax.setAdaptor(handler.adaptor);
handler.documentClass =
EnrichedMathDocumentMixin<N, T, D, MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
handler.documentClass, MmlJax
);
return handler;
}

96
node_modules/mathjax-full/ts/a11y/sre.ts generated vendored Normal file
View File

@@ -0,0 +1,96 @@
/*************************************************************
*
* Copyright (c) 2018-2022 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 the interface functionality to SRE.
*
* @author dpvc@mathjax.org (Davide Cervone)
* @author v.sorge@mathjax.org (Volker Sorge)
*/
import * as Api from 'speech-rule-engine/js/common/system.js';
import {Walker} from 'speech-rule-engine/js/walker/walker.js';
import * as WalkerFactory from 'speech-rule-engine/js/walker/walker_factory.js';
import * as SpeechGeneratorFactory from 'speech-rule-engine/js/speech_generator/speech_generator_factory.js';
import * as EngineConst from 'speech-rule-engine/js/common/engine_const.js';
import Engine from 'speech-rule-engine/js/common/engine.js';
import {ClearspeakPreferences} from 'speech-rule-engine/js/speech_rules/clearspeak_preferences.js';
import {Highlighter} from 'speech-rule-engine/js/highlighter/highlighter.js';
import * as HighlighterFactory from 'speech-rule-engine/js/highlighter/highlighter_factory.js';
import {SpeechGenerator} from 'speech-rule-engine/js/speech_generator/speech_generator.js';
import {Variables} from 'speech-rule-engine/js/common/variables.js';
import MathMaps from './mathmaps.js';
export namespace Sre {
export type highlighter = Highlighter;
export type speechGenerator = SpeechGenerator;
export type walker = Walker;
export const locales = Variables.LOCALES;
export const sreReady = Api.engineReady;
export const setupEngine = Api.setupEngine;
export const engineSetup = Api.engineSetup;
export const toEnriched = Api.toEnriched;
export const toSpeech = Api.toSpeech;
export const clearspeakPreferences = ClearspeakPreferences;
export const getHighlighter = HighlighterFactory.highlighter;
export const getSpeechGenerator = SpeechGeneratorFactory.generator;
export const getWalker = WalkerFactory.walker;
export const clearspeakStyle = () => {
return EngineConst.DOMAIN_TO_STYLES['clearspeak'];
};
/**
* Loads locales that are already included in the imported MathMaps. Defaults
* to standard loading if a locale is not yet preloaded.
*/
export const preloadLocales = async function(locale: string) {
const json = MathMaps.get(locale);
return json ? new Promise((res, _rej) => res(JSON.stringify(json))) :
Api.localeLoader()(locale);
};
}
/**
* A promise that resolves when SRE is loaded and ready, and rejects if
* SRE can't be loaded, or does not become ready within the timout period.
*
* @deprecated
*/
export const sreReady = Sre.sreReady;
// Setting delay stops SRE from setting itself up (and loading locales) when it
// is not actually being used. As we are not yet sure in which environment we
// are (browser, node) we can not use a configuration vector.
Engine.getInstance().delay = true;
export default Sre;