add initial marp implementation with sample content and build configuration
This commit is contained in:
295
node_modules/mathjax-full/ts/a11y/assistive-mml.ts
generated
vendored
Normal file
295
node_modules/mathjax-full/ts/a11y/assistive-mml.ts
generated
vendored
Normal 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
231
node_modules/mathjax-full/ts/a11y/complexity.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
519
node_modules/mathjax-full/ts/a11y/complexity/collapse.ts
generated
vendored
Normal file
519
node_modules/mathjax-full/ts/a11y/complexity/collapse.ts
generated
vendored
Normal 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
381
node_modules/mathjax-full/ts/a11y/complexity/visitor.ts
generated
vendored
Normal 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
650
node_modules/mathjax-full/ts/a11y/explorer.ts
generated
vendored
Normal 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
282
node_modules/mathjax-full/ts/a11y/explorer/Explorer.ts
generated
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
460
node_modules/mathjax-full/ts/a11y/explorer/KeyExplorer.ts
generated
vendored
Normal file
460
node_modules/mathjax-full/ts/a11y/explorer/KeyExplorer.ts
generated
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
224
node_modules/mathjax-full/ts/a11y/explorer/MouseExplorer.ts
generated
vendored
Normal file
224
node_modules/mathjax-full/ts/a11y/explorer/MouseExplorer.ts
generated
vendored
Normal 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
519
node_modules/mathjax-full/ts/a11y/explorer/Region.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
124
node_modules/mathjax-full/ts/a11y/explorer/TreeExplorer.ts
generated
vendored
Normal file
124
node_modules/mathjax-full/ts/a11y/explorer/TreeExplorer.ts
generated
vendored
Normal 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
27
node_modules/mathjax-full/ts/a11y/mathmaps.ts
generated
vendored
Normal 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
361
node_modules/mathjax-full/ts/a11y/semantic-enrich.ts
generated
vendored
Normal 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
96
node_modules/mathjax-full/ts/a11y/sre.ts
generated
vendored
Normal 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;
|
||||
Reference in New Issue
Block a user