assistive-mml.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2019-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Mixin that adds hidden MathML to the output
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {Handler} from '../core/Handler.js';
  23. import {MathDocument, AbstractMathDocument, MathDocumentConstructor} from '../core/MathDocument.js';
  24. import {MathItem, AbstractMathItem, STATE, newState} from '../core/MathItem.js';
  25. import {MmlNode} from '../core/MmlTree/MmlNode.js';
  26. import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js';
  27. import {OptionList, expandable} from '../util/Options.js';
  28. import {StyleList} from '../util/StyleList.js';
  29. /*==========================================================================*/
  30. export class LimitedMmlVisitor extends SerializedMmlVisitor {
  31. /**
  32. * @override
  33. */
  34. protected getAttributes(node: MmlNode): string {
  35. /**
  36. * Remove id from attribute output
  37. */
  38. return super.getAttributes(node).replace(/ ?id=".*?"/, '');
  39. }
  40. }
  41. /**
  42. * Generic constructor for Mixins
  43. */
  44. export type Constructor<T> = new(...args: any[]) => T;
  45. /*==========================================================================*/
  46. /**
  47. * Add STATE value for having assistive MathML (after TYPESETTING)
  48. */
  49. newState('ASSISTIVEMML', 153);
  50. /**
  51. * The functions added to MathItem for assistive MathML
  52. *
  53. * @template N The HTMLElement node class
  54. * @template T The Text node class
  55. * @template D The Document class
  56. */
  57. export interface AssistiveMmlMathItem<N, T, D> extends MathItem<N, T, D> {
  58. /**
  59. * @param {MathDocument} document The document where assistive MathML is being added
  60. * @param {boolean} force True to force assistive MathML even if enableAssistiveMml is false
  61. */
  62. assistiveMml(document: MathDocument<N, T, D>, force?: boolean): void;
  63. }
  64. /**
  65. * The mixin for adding assistive MathML to MathItems
  66. *
  67. * @param {B} BaseMathItem The MathItem class to be extended
  68. * @return {AssistiveMathItem} The augmented MathItem class
  69. *
  70. * @template N The HTMLElement node class
  71. * @template T The Text node class
  72. * @template D The Document class
  73. * @template B The MathItem class to extend
  74. */
  75. export function AssistiveMmlMathItemMixin<N, T, D, B extends Constructor<AbstractMathItem<N, T, D>>>(
  76. BaseMathItem: B
  77. ): Constructor<AssistiveMmlMathItem<N, T, D>> & B {
  78. return class extends BaseMathItem {
  79. /**
  80. * @param {MathDocument} document The MathDocument for the MathItem
  81. * @param {boolean} force True to force assistive MathML evenif enableAssistiveMml is false
  82. */
  83. public assistiveMml(document: AssistiveMmlMathDocument<N, T, D>, force: boolean = false) {
  84. if (this.state() >= STATE.ASSISTIVEMML) return;
  85. if (!this.isEscaped && (document.options.enableAssistiveMml || force)) {
  86. const adaptor = document.adaptor;
  87. //
  88. // Get the serialized MathML
  89. //
  90. const mml = document.toMML(this.root).replace(/\n */g, '').replace(/<!--.*?-->/g, '');
  91. //
  92. // Parse is as HTML and retrieve the <math> element
  93. //
  94. const mmlNodes = adaptor.firstChild(adaptor.body(adaptor.parse(mml, 'text/html')));
  95. //
  96. // Create a container for the hidden MathML
  97. //
  98. const node = adaptor.node('mjx-assistive-mml', {
  99. unselectable: 'on', display: (this.display ? 'block' : 'inline')
  100. }, [mmlNodes]);
  101. //
  102. // Hide the typeset math from assistive technology and append the MathML that is visually
  103. // hidden from other users
  104. //
  105. adaptor.setAttribute(adaptor.firstChild(this.typesetRoot) as N, 'aria-hidden', 'true');
  106. adaptor.setStyle(this.typesetRoot, 'position', 'relative');
  107. adaptor.append(this.typesetRoot, node);
  108. }
  109. this.state(STATE.ASSISTIVEMML);
  110. }
  111. };
  112. }
  113. /*==========================================================================*/
  114. /**
  115. * The functions added to MathDocument for assistive MathML
  116. *
  117. * @template N The HTMLElement node class
  118. * @template T The Text node class
  119. * @template D The Document class
  120. */
  121. export interface AssistiveMmlMathDocument<N, T, D> extends AbstractMathDocument<N, T, D> {
  122. /**
  123. * @param {MmlNode} node The node to be serializes
  124. * @return {string} The serialization of the node
  125. */
  126. toMML: (node: MmlNode) => string;
  127. /**
  128. * Add assistive MathML to the MathItems in the MathDocument
  129. *
  130. * @return {AssistiveMmlMathDocument} The MathDocument (so calls can be chained)
  131. */
  132. assistiveMml(): AssistiveMmlMathDocument<N, T, D>;
  133. }
  134. /**
  135. * The mixin for adding assistive MathML to MathDocuments
  136. *
  137. * @param {B} BaseDocument The MathDocument class to be extended
  138. * @return {AssistiveMmlMathDocument} The Assistive MathML MathDocument class
  139. *
  140. * @template N The HTMLElement node class
  141. * @template T The Text node class
  142. * @template D The Document class
  143. * @template B The MathDocument class to extend
  144. */
  145. export function AssistiveMmlMathDocumentMixin<N, T, D,
  146. B extends MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
  147. BaseDocument: B
  148. ): MathDocumentConstructor<AssistiveMmlMathDocument<N, T, D>> & B {
  149. return class BaseClass extends BaseDocument {
  150. /**
  151. * @override
  152. */
  153. public static OPTIONS: OptionList = {
  154. ...BaseDocument.OPTIONS,
  155. enableAssistiveMml: true,
  156. renderActions: expandable({
  157. ...BaseDocument.OPTIONS.renderActions,
  158. assistiveMml: [STATE.ASSISTIVEMML]
  159. })
  160. };
  161. /**
  162. * styles needed for the hidden MathML
  163. */
  164. public static assistiveStyles: StyleList = {
  165. 'mjx-assistive-mml': {
  166. position: 'absolute !important',
  167. top: '0px', left: '0px',
  168. clip: 'rect(1px, 1px, 1px, 1px)',
  169. padding: '1px 0px 0px 0px !important',
  170. border: '0px !important',
  171. display: 'block !important',
  172. width: 'auto !important',
  173. overflow: 'hidden !important',
  174. /*
  175. * Don't allow the assistive MathML to become part of the selection
  176. */
  177. '-webkit-touch-callout': 'none',
  178. '-webkit-user-select': 'none',
  179. '-khtml-user-select': 'none',
  180. '-moz-user-select': 'none',
  181. '-ms-user-select': 'none',
  182. 'user-select': 'none'
  183. },
  184. 'mjx-assistive-mml[display="block"]': {
  185. width: '100% !important'
  186. }
  187. };
  188. /**
  189. * Visitor used for serializing internal MathML nodes
  190. */
  191. protected visitor: LimitedMmlVisitor;
  192. /**
  193. * Augment the MathItem class used for this MathDocument, and create the serialization visitor.
  194. *
  195. * @override
  196. * @constructor
  197. */
  198. constructor(...args: any[]) {
  199. super(...args);
  200. const CLASS = (this.constructor as typeof BaseClass);
  201. const ProcessBits = CLASS.ProcessBits;
  202. if (!ProcessBits.has('assistive-mml')) {
  203. ProcessBits.allocate('assistive-mml');
  204. }
  205. this.visitor = new LimitedMmlVisitor(this.mmlFactory);
  206. this.options.MathItem =
  207. AssistiveMmlMathItemMixin<N, T, D, Constructor<AbstractMathItem<N, T, D>>>(
  208. this.options.MathItem
  209. );
  210. if ('addStyles' in this) {
  211. (this as any).addStyles(CLASS.assistiveStyles);
  212. }
  213. }
  214. /**
  215. * @param {MmlNode} node The node to be serializes
  216. * @return {string} The serialization of the node
  217. */
  218. public toMML(node: MmlNode): string {
  219. return this.visitor.visitTree(node);
  220. }
  221. /**
  222. * Add assistive MathML to the MathItems in this MathDocument
  223. */
  224. public assistiveMml() {
  225. if (!this.processed.isSet('assistive-mml')) {
  226. for (const math of this.math) {
  227. (math as AssistiveMmlMathItem<N, T, D>).assistiveMml(this);
  228. }
  229. this.processed.set('assistive-mml');
  230. }
  231. return this;
  232. }
  233. /**
  234. * @override
  235. */
  236. public state(state: number, restore: boolean = false) {
  237. super.state(state, restore);
  238. if (state < STATE.ASSISTIVEMML) {
  239. this.processed.clear('assistive-mml');
  240. }
  241. return this;
  242. }
  243. };
  244. }
  245. /*==========================================================================*/
  246. /**
  247. * Add assitive MathML support a Handler instance
  248. *
  249. * @param {Handler} handler The Handler instance to enhance
  250. * @return {Handler} The handler that was modified (for purposes of chainging extensions)
  251. *
  252. * @template N The HTMLElement node class
  253. * @template T The Text node class
  254. * @template D The Document class
  255. */
  256. export function AssistiveMmlHandler<N, T, D>(handler: Handler<N, T, D>): Handler<N, T, D> {
  257. handler.documentClass =
  258. AssistiveMmlMathDocumentMixin<N, T, D, MathDocumentConstructor<AbstractMathDocument<N, T, D>>>(
  259. handler.documentClass
  260. );
  261. return handler;
  262. }