MathDocument.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2017-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 Implements the interface and abstract class for MathDocument objects
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {userOptions, defaultOptions, OptionList, expandable} from '../util/Options.js';
  23. import {InputJax, AbstractInputJax} from './InputJax.js';
  24. import {OutputJax, AbstractOutputJax} from './OutputJax.js';
  25. import {MathList, AbstractMathList} from './MathList.js';
  26. import {MathItem, AbstractMathItem, STATE} from './MathItem.js';
  27. import {MmlNode, TextNode} from './MmlTree/MmlNode.js';
  28. import {MmlFactory} from '../core/MmlTree/MmlFactory.js';
  29. import {DOMAdaptor} from '../core/DOMAdaptor.js';
  30. import {BitField, BitFieldClass} from '../util/BitField.js';
  31. import {PrioritizedList} from '../util/PrioritizedList.js';
  32. /*****************************************************************/
  33. /**
  34. * A function to call while rendering a document (usually calls a MathDocument method)
  35. *
  36. * @template N The HTMLElement node class
  37. * @template T The Text node class
  38. * @template D The Document class
  39. */
  40. export type RenderDoc<N, T, D> = (document: MathDocument<N, T, D>) => boolean;
  41. /**
  42. * A function to call while rendering a MathItem (usually calls one of its methods)
  43. *
  44. * @template N The HTMLElement node class
  45. * @template T The Text node class
  46. * @template D The Document class
  47. */
  48. export type RenderMath<N, T, D> = (math: MathItem<N, T, D>, document: MathDocument<N, T, D>) => boolean;
  49. /**
  50. * The data for an action to perform during rendering or conversion
  51. *
  52. * @template N The HTMLElement node class
  53. * @template T The Text node class
  54. * @template D The Document class
  55. */
  56. export type RenderData<N, T, D> = {
  57. id: string, // The name for the action
  58. renderDoc: RenderDoc<N, T, D>, // The action to take during a render() call
  59. renderMath: RenderMath<N, T, D>, // The action to take during a rerender() or convert() call
  60. convert: boolean // Whether the action is to be used during convert()
  61. };
  62. /**
  63. * The data used to define a render action in configurations and options objects
  64. * (the key is used as the id, the number in the data below is the priority, and
  65. * the remainind data is as described below; if no boolean is given, convert = true
  66. * by default)
  67. *
  68. * @template N The HTMLElement node class
  69. * @template T The Text node class
  70. * @template D The Document class
  71. */
  72. export type RenderAction<N, T, D> =
  73. [number] | // id (i.e., key) is method name to use
  74. [number, string] | // string is method to call
  75. [number, string, string] | // the strings are methods names for doc and math
  76. [number, RenderDoc<N, T, D>, RenderMath<N, T, D>] | // explicit functions for doc and math
  77. [number, boolean] | // same as first above, with boolean for convert
  78. [number, string, boolean] | // same as second above, with boolean for convert
  79. [number, string, string, boolean] | // same as third above, with boolean for convert
  80. [number, RenderDoc<N, T, D>, RenderMath<N, T, D>, boolean]; // same as forth above, with boolean for convert
  81. /**
  82. * An object representing a collection of rendering actions (id's tied to priority-and-method data)
  83. *
  84. * @template N The HTMLElement node class
  85. * @template T The Text node class
  86. * @template D The Document class
  87. */
  88. export type RenderActions<N, T, D> = {[id: string]: RenderAction<N, T, D>};
  89. /**
  90. * Implements a prioritized list of render actions. Extensions can add actions to the list
  91. * to make it easy to extend the normal typesetting and conversion operations.
  92. *
  93. * @template N The HTMLElement node class
  94. * @template T The Text node class
  95. * @template D The Document class
  96. */
  97. export class RenderList<N, T, D> extends PrioritizedList<RenderData<N, T, D>> {
  98. /**
  99. * Creates a new RenderList from an initial list of rendering actions
  100. *
  101. * @param {RenderActions} actions The list of actions to take during render(), rerender(), and convert() calls
  102. * @returns {RenderList} The newly created prioritied list
  103. */
  104. public static create<N, T, D>(actions: RenderActions<N, T, D>): RenderList<N, T, D> {
  105. const list = new this<N, T, D>();
  106. for (const id of Object.keys(actions)) {
  107. const [action, priority] = this.action<N, T, D>(id, actions[id]);
  108. if (priority) {
  109. list.add(action, priority);
  110. }
  111. }
  112. return list;
  113. }
  114. /**
  115. * Parses a RenderAction to produce the correspinding RenderData item
  116. * (e.g., turn method names into actual functions that call the method)
  117. *
  118. * @param {string} id The id of the action
  119. * @param {RenderAction} action The RenderAction defining the action
  120. * @returns {[RenderData,number]} The corresponding RenderData definition for the action and its priority
  121. */
  122. public static action<N, T, D>(id: string, action: RenderAction<N, T, D>): [RenderData<N, T, D>, number] {
  123. let renderDoc, renderMath;
  124. let convert = true;
  125. let priority = action[0];
  126. if (action.length === 1 || typeof action[1] === 'boolean') {
  127. action.length === 2 && (convert = action[1] as boolean);
  128. [renderDoc, renderMath] = this.methodActions(id);
  129. } else if (typeof action[1] === 'string') {
  130. if (typeof action[2] === 'string') {
  131. action.length === 4 && (convert = action[3] as boolean);
  132. const [method1, method2] = action.slice(1) as [string, string];
  133. [renderDoc, renderMath] = this.methodActions(method1, method2);
  134. } else {
  135. action.length === 3 && (convert = action[2] as boolean);
  136. [renderDoc, renderMath] = this.methodActions(action[1] as string);
  137. }
  138. } else {
  139. action.length === 4 && (convert = action[3] as boolean);
  140. [renderDoc, renderMath] = action.slice(1) as [RenderDoc<N, T, D>, RenderMath<N, T, D>];
  141. }
  142. return [{id, renderDoc, renderMath, convert} as RenderData<N, T, D>, priority];
  143. }
  144. /**
  145. * Produces the doc and math actions for the given method name(s)
  146. * (a blank name is a no-op)
  147. *
  148. * @param {string} method1 The method to use for the render() call
  149. * @param {string} method1 The method to use for the rerender() and convert() calls
  150. */
  151. protected static methodActions(method1: string, method2: string = method1) {
  152. return [
  153. (document: any) => {method1 && document[method1](); return false; },
  154. (math: any, document: any) => {method2 && math[method2](document); return false; }
  155. ];
  156. }
  157. /**
  158. * Perform the document-level rendering functions
  159. *
  160. * @param {MathDocument} document The MathDocument whose methods are to be called
  161. * @param {number=} start The state at which to start rendering (default is UNPROCESSED)
  162. */
  163. public renderDoc(document: MathDocument<N, T, D>, start: number = STATE.UNPROCESSED) {
  164. for (const item of this.items) {
  165. if (item.priority >= start) {
  166. if (item.item.renderDoc(document)) return;
  167. }
  168. }
  169. }
  170. /**
  171. * Perform the MathItem-level rendering functions
  172. *
  173. * @param {MathItem} math The MathItem whose methods are to be called
  174. * @param {MathDocument} document The MathDocument to pass to the MathItem methods
  175. * @param {number=} start The state at which to start rendering (default is UNPROCESSED)
  176. */
  177. public renderMath(math: MathItem<N, T, D>, document: MathDocument<N, T, D>, start: number = STATE.UNPROCESSED) {
  178. for (const item of this.items) {
  179. if (item.priority >= start) {
  180. if (item.item.renderMath(math, document)) return;
  181. }
  182. }
  183. }
  184. /**
  185. * Perform the MathItem-level conversion functions
  186. *
  187. * @param {MathItem} math The MathItem whose methods are to be called
  188. * @param {MathDocument} document The MathDocument to pass to the MathItem methods
  189. * @param {number=} end The state at which to end rendering (default is LAST)
  190. */
  191. public renderConvert(math: MathItem<N, T, D>, document: MathDocument<N, T, D>, end: number = STATE.LAST) {
  192. for (const item of this.items) {
  193. if (item.priority > end) return;
  194. if (item.item.convert) {
  195. if (item.item.renderMath(math, document)) return;
  196. }
  197. }
  198. }
  199. /**
  200. * Find an entry in the list with a given ID
  201. *
  202. * @param {string} id The id to search for
  203. * @returns {RenderData|null} The data for the given id, if found, or null
  204. */
  205. public findID(id: string): RenderData<N, T, D> | null {
  206. for (const item of this.items) {
  207. if (item.item.id === id) {
  208. return item.item;
  209. }
  210. }
  211. return null;
  212. }
  213. }
  214. /*****************************************************************/
  215. /**
  216. * The ways of specifying a container (a selector string, an actual node,
  217. * or an array of those (e.g., the result of document.getElementsByTagName())
  218. *
  219. * @template N The HTMLElement node class
  220. */
  221. export type ContainerList<N> = string | N | (string | N | N[])[];
  222. /**
  223. * The options allowed for the reset() method
  224. */
  225. export type ResetList = {
  226. all?: boolean,
  227. processed?: boolean,
  228. inputJax?: any[],
  229. outputJax?: any[]
  230. };
  231. /**
  232. * The default option list for the reset() method
  233. */
  234. export const resetOptions: ResetList = {
  235. all: false,
  236. processed: false,
  237. inputJax: null,
  238. outputJax: null
  239. };
  240. /**
  241. * The option list for when all options are to be reset
  242. */
  243. export const resetAllOptions: ResetList = {
  244. all: true,
  245. processed: true,
  246. inputJax: [],
  247. outputJax: []
  248. };
  249. /*****************************************************************/
  250. /**
  251. * The MathDocument interface
  252. *
  253. * The MathDocument is created by MathJax.Document() and holds the
  254. * document, the math found in it, and so on. The methods of the
  255. * MathDocument all return the MathDocument itself, so you can
  256. * chain the method calls. E.g.,
  257. *
  258. * const html = MathJax.Document('<html>...</html>');
  259. * html.findMath()
  260. * .compile()
  261. * .getMetrics()
  262. * .typeset()
  263. * .updateDocument();
  264. *
  265. * The MathDocument is the main interface for page authors to
  266. * interact with MathJax.
  267. *
  268. * @template N The HTMLElement node class
  269. * @template T The Text node class
  270. * @template D The Document class
  271. */
  272. export interface MathDocument<N, T, D> {
  273. /**
  274. * The document being processed (e.g., DOM document, or Markdown string)
  275. */
  276. document: D;
  277. /**
  278. * The kind of MathDocument (e.g., "HTML")
  279. */
  280. kind: string;
  281. /**
  282. * The options for the document
  283. */
  284. options: OptionList;
  285. /**
  286. * The list of MathItems found in this page
  287. */
  288. math: MathList<N, T, D>;
  289. /**
  290. * The list of actions to take during a render() or convert() call
  291. */
  292. renderActions: RenderList<N, T, D>;
  293. /**
  294. * This object tracks what operations have been performed, so that (when
  295. * asynchronous operations are used), the ones that have already been
  296. * completed won't be performed again.
  297. */
  298. processed: BitField;
  299. /**
  300. * An array of input jax to run on the document
  301. */
  302. inputJax: InputJax<N, T, D>[];
  303. /**
  304. * The output jax to use for the document
  305. */
  306. outputJax: OutputJax<N, T, D>;
  307. /**
  308. * The DOM adaptor to use for input and output
  309. */
  310. adaptor: DOMAdaptor<N, T, D>;
  311. /**
  312. * The MmlFactory to be used for input jax and error processing
  313. */
  314. mmlFactory: MmlFactory;
  315. /**
  316. * @param {string} id The id of the action to add
  317. * @param {any[]} action The RenderAction to take
  318. */
  319. addRenderAction(id: string, ...action: any[]): void;
  320. /**
  321. * @param {string} id The id of the action to remove
  322. */
  323. removeRenderAction(id: string): void;
  324. /**
  325. * Perform the renderActions on the document
  326. */
  327. render(): MathDocument<N, T, D>;
  328. /**
  329. * Rerender the MathItems on the page
  330. *
  331. * @param {number=} start The state to start rerendering at
  332. * @return {MathDocument} The math document instance
  333. */
  334. rerender(start?: number): MathDocument<N, T, D>;
  335. /**
  336. * Convert a math string to the document's output format
  337. *
  338. * @param {string} math The math string to convert
  339. * @params {OptionList} options The options for the conversion (e.g., format, ex, em, etc.)
  340. * @return {MmlNode|N} The MmlNode or N node for the converted content
  341. */
  342. convert(math: string, options?: OptionList): MmlNode | N;
  343. /**
  344. * Locates the math in the document and constructs the MathList
  345. * for the document.
  346. *
  347. * @param {OptionList} options The options for locating the math
  348. * @return {MathDocument} The math document instance
  349. */
  350. findMath(options?: OptionList): MathDocument<N, T, D>;
  351. /**
  352. * Calls the input jax to process the MathItems in the MathList
  353. *
  354. * @return {MathDocument} The math document instance
  355. */
  356. compile(): MathDocument<N, T, D>;
  357. /**
  358. * Gets the metric information for the MathItems
  359. *
  360. * @return {MathDocument} The math document instance
  361. */
  362. getMetrics(): MathDocument<N, T, D>;
  363. /**
  364. * Calls the output jax to process the compiled math in the MathList
  365. *
  366. * @return {MathDocument} The math document instance
  367. */
  368. typeset(): MathDocument<N, T, D>;
  369. /**
  370. * Updates the document to include the typeset math
  371. *
  372. * @return {MathDocument} The math document instance
  373. */
  374. updateDocument(): MathDocument<N, T, D>;
  375. /**
  376. * Removes the typeset math from the document
  377. *
  378. * @param {boolean} restore True if the original math should be put
  379. * back into the document as well
  380. * @return {MathDocument} The math document instance
  381. */
  382. removeFromDocument(restore?: boolean): MathDocument<N, T, D>;
  383. /**
  384. * Set the state of the document (allowing you to roll back
  385. * the state to a previous one, if needed).
  386. *
  387. * @param {number} state The new state of the document
  388. * @param {boolean} restore True if the original math should be put
  389. * back into the document during the rollback
  390. * @return {MathDocument} The math document instance
  391. */
  392. state(state: number, restore?: boolean): MathDocument<N, T, D>;
  393. /**
  394. * Clear the processed values so that the document can be reprocessed
  395. *
  396. * @param {ResetList} options The things to be reset
  397. * @return {MathDocument} The math document instance
  398. */
  399. reset(options?: ResetList): MathDocument<N, T, D>;
  400. /**
  401. * Reset the processed values and clear the MathList (so that new math
  402. * can be processed in the document).
  403. *
  404. * @return {MathDocument} The math document instance
  405. */
  406. clear(): MathDocument<N, T, D>;
  407. /**
  408. * Merges a MathList into the list for this document.
  409. *
  410. * @param {MathList} list The MathList to be merged into this document's list
  411. * @return {MathDocument} The math document instance
  412. */
  413. concat(list: MathList<N, T, D>): MathDocument<N, T, D>;
  414. /**
  415. * Clear the typeset MathItems that are within the given container
  416. * from the document's MathList. (E.g., when the content of the
  417. * container has been updated and you want to remove the
  418. * associated MathItems)
  419. *
  420. * @param {ContainerList<N>} elements The container DOM elements whose math items are to be removed
  421. * @return {MathItem<N,T,D>[]} The removed MathItems
  422. */
  423. clearMathItemsWithin(containers: ContainerList<N>): MathItem<N, T, D>[];
  424. /**
  425. * Get the typeset MathItems that are within a given container.
  426. *
  427. * @param {ContainerList<N>} elements The container DOM elements whose math items are to be found
  428. * @return {MathItem<N,T,D>[]} The list of MathItems within that container
  429. */
  430. getMathItemsWithin(elements: ContainerList<N>): MathItem<N, T, D>[];
  431. }
  432. /*****************************************************************/
  433. /**
  434. * Defaults used when input jax isn't specified
  435. *
  436. * @template N The HTMLElement node class
  437. * @template T The Text node class
  438. * @template D The Document class
  439. */
  440. class DefaultInputJax<N, T, D> extends AbstractInputJax<N, T, D> {
  441. /**
  442. * @override
  443. */
  444. public compile(_math: MathItem<N, T, D>) {
  445. return null as MmlNode;
  446. }
  447. }
  448. /**
  449. * Defaults used when ouput jax isn't specified
  450. *
  451. * @template N The HTMLElement node class
  452. * @template T The Text node class
  453. * @template D The Document class
  454. */
  455. class DefaultOutputJax<N, T, D> extends AbstractOutputJax<N, T, D> {
  456. /**
  457. * @override
  458. */
  459. public typeset(_math: MathItem<N, T, D>, _document: MathDocument<N, T, D> = null) {
  460. return null as N;
  461. }
  462. /**
  463. * @override
  464. */
  465. public escaped(_math: MathItem<N, T, D>, _document?: MathDocument<N, T, D>) {
  466. return null as N;
  467. }
  468. }
  469. /**
  470. * Default for the MathList when one isn't specified
  471. *
  472. * @template N The HTMLElement node class
  473. * @template T The Text node class
  474. * @template D The Document class
  475. */
  476. class DefaultMathList<N, T, D> extends AbstractMathList<N, T, D> {}
  477. /**
  478. * Default for the Mathitem when one isn't specified
  479. *
  480. * @template N The HTMLElement node class
  481. * @template T The Text node class
  482. * @template D The Document class
  483. */
  484. class DefaultMathItem<N, T, D> extends AbstractMathItem<N, T, D> {}
  485. /*****************************************************************/
  486. /**
  487. * Implements the abstract MathDocument class
  488. *
  489. * @template N The HTMLElement node class
  490. * @template T The Text node class
  491. * @template D The Document class
  492. */
  493. export abstract class AbstractMathDocument<N, T, D> implements MathDocument<N, T, D> {
  494. /**
  495. * The type of MathDocument
  496. */
  497. public static KIND: string = 'MathDocument';
  498. /**
  499. * The default options for the document
  500. */
  501. public static OPTIONS: OptionList = {
  502. OutputJax: null, // instance of an OutputJax for the document
  503. InputJax: null, // instance of an InputJax or an array of them
  504. MmlFactory: null, // instance of a MmlFactory for this document
  505. MathList: DefaultMathList, // constructor for a MathList to use for the document
  506. MathItem: DefaultMathItem, // constructor for a MathItem to use for the MathList
  507. compileError: (doc: AbstractMathDocument<any, any, any>, math: MathItem<any, any, any>, err: Error) => {
  508. doc.compileError(math, err);
  509. },
  510. typesetError: (doc: AbstractMathDocument<any, any, any>, math: MathItem<any, any, any>, err: Error) => {
  511. doc.typesetError(math, err);
  512. },
  513. renderActions: expandable({
  514. find: [STATE.FINDMATH, 'findMath', '', false],
  515. compile: [STATE.COMPILED],
  516. metrics: [STATE.METRICS, 'getMetrics', '', false],
  517. typeset: [STATE.TYPESET],
  518. update: [STATE.INSERTED, 'updateDocument', false]
  519. }) as RenderActions<any, any, any>
  520. };
  521. /**
  522. * A bit-field for the actions that have been processed
  523. */
  524. public static ProcessBits = BitFieldClass('findMath', 'compile', 'getMetrics', 'typeset', 'updateDocument');
  525. /**
  526. * The document managed by this MathDocument
  527. */
  528. public document: D;
  529. /**
  530. * The actual options for this document (with user-supplied ones merged in)
  531. */
  532. public options: OptionList;
  533. /**
  534. * The list of MathItems for this document
  535. */
  536. public math: MathList<N, T, D>;
  537. /**
  538. * The list of render actions
  539. */
  540. public renderActions: RenderList<N, T, D>;
  541. /**
  542. * The bit-field used to tell what steps have been taken on the document (for retries)
  543. */
  544. public processed: BitField;
  545. /**
  546. * The list of input jax for the document
  547. */
  548. public inputJax: InputJax<N, T, D>[];
  549. /**
  550. * The output jax for the document
  551. */
  552. public outputJax: OutputJax<N, T, D>;
  553. /**
  554. * The DOM adaptor for the document
  555. */
  556. public adaptor: DOMAdaptor<N, T, D>;
  557. /**
  558. * The MathML node factory for the internal MathML representation
  559. */
  560. public mmlFactory: MmlFactory;
  561. /**
  562. * @param {any} document The document (HTML string, parsed DOM, etc.) to be processed
  563. * @param {DOMAdaptor} adaptor The DOM adaptor for this document
  564. * @param {OptionList} options The options for this document
  565. * @constructor
  566. */
  567. constructor (document: D, adaptor: DOMAdaptor<N, T, D>, options: OptionList) {
  568. let CLASS = this.constructor as typeof AbstractMathDocument;
  569. this.document = document;
  570. this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options);
  571. this.math = new (this.options['MathList'] || DefaultMathList)();
  572. this.renderActions = RenderList.create<N, T, D>(this.options['renderActions']);
  573. this.processed = new AbstractMathDocument.ProcessBits();
  574. this.outputJax = this.options['OutputJax'] || new DefaultOutputJax<N, T, D>();
  575. let inputJax = this.options['InputJax'] || [new DefaultInputJax<N, T, D>()];
  576. if (!Array.isArray(inputJax)) {
  577. inputJax = [inputJax];
  578. }
  579. this.inputJax = inputJax;
  580. //
  581. // Pass the DOM adaptor to the jax
  582. //
  583. this.adaptor = adaptor;
  584. this.outputJax.setAdaptor(adaptor);
  585. this.inputJax.map(jax => jax.setAdaptor(adaptor));
  586. //
  587. // Pass the MmlFactory to the jax
  588. //
  589. this.mmlFactory = this.options['MmlFactory'] || new MmlFactory();
  590. this.inputJax.map(jax => jax.setMmlFactory(this.mmlFactory));
  591. //
  592. // Do any initialization that requires adaptors or factories
  593. //
  594. this.outputJax.initialize();
  595. this.inputJax.map(jax => jax.initialize());
  596. }
  597. /**
  598. * @return {string} The kind of document
  599. */
  600. public get kind(): string {
  601. return (this.constructor as typeof AbstractMathDocument).KIND;
  602. }
  603. /**
  604. * @override
  605. */
  606. public addRenderAction(id: string, ...action: any[]) {
  607. const [fn, p] = RenderList.action<N, T, D>(id, action as RenderAction<N, T, D>);
  608. this.renderActions.add(fn, p);
  609. }
  610. /**
  611. * @override
  612. */
  613. public removeRenderAction(id: string) {
  614. const action = this.renderActions.findID(id);
  615. if (action) {
  616. this.renderActions.remove(action);
  617. }
  618. }
  619. /**
  620. * @override
  621. */
  622. public render() {
  623. this.renderActions.renderDoc(this);
  624. return this;
  625. }
  626. /**
  627. * @override
  628. */
  629. public rerender(start: number = STATE.RERENDER) {
  630. this.state(start - 1);
  631. this.render();
  632. return this;
  633. }
  634. /**
  635. * @override
  636. */
  637. public convert(math: string, options: OptionList = {}) {
  638. let {format, display, end, ex, em, containerWidth, lineWidth, scale, family} = userOptions({
  639. format: this.inputJax[0].name, display: true, end: STATE.LAST,
  640. em: 16, ex: 8, containerWidth: null, lineWidth: 1000000, scale: 1, family: ''
  641. }, options);
  642. if (containerWidth === null) {
  643. containerWidth = 80 * ex;
  644. }
  645. const jax = this.inputJax.reduce((jax, ijax) => (ijax.name === format ? ijax : jax), null);
  646. const mitem = new this.options.MathItem(math, jax, display);
  647. mitem.start.node = this.adaptor.body(this.document);
  648. mitem.setMetrics(em, ex, containerWidth, lineWidth, scale);
  649. if (this.outputJax.options.mtextInheritFont) {
  650. mitem.outputData.mtextFamily = family;
  651. }
  652. if (this.outputJax.options.merrorInheritFont) {
  653. mitem.outputData.merrorFamily = family;
  654. }
  655. mitem.convert(this, end);
  656. return (mitem.typesetRoot || mitem.root);
  657. }
  658. /**
  659. * @override
  660. */
  661. public findMath(_options: OptionList = null) {
  662. this.processed.set('findMath');
  663. return this;
  664. }
  665. /**
  666. * @override
  667. */
  668. public compile() {
  669. if (!this.processed.isSet('compile')) {
  670. //
  671. // Compile all the math in the list
  672. //
  673. const recompile = [];
  674. for (const math of this.math) {
  675. this.compileMath(math);
  676. if (math.inputData.recompile !== undefined) {
  677. recompile.push(math);
  678. }
  679. }
  680. //
  681. // If any were added to the recompile list,
  682. // compile them again
  683. //
  684. for (const math of recompile) {
  685. const data = math.inputData.recompile;
  686. math.state(data.state);
  687. math.inputData.recompile = data;
  688. this.compileMath(math);
  689. }
  690. this.processed.set('compile');
  691. }
  692. return this;
  693. }
  694. /**
  695. * @param {MathItem} math The item to compile
  696. */
  697. protected compileMath(math: MathItem<N, T, D>) {
  698. try {
  699. math.compile(this);
  700. } catch (err) {
  701. if (err.retry || err.restart) {
  702. throw err;
  703. }
  704. this.options['compileError'](this, math, err);
  705. math.inputData['error'] = err;
  706. }
  707. }
  708. /**
  709. * Produce an error using MmlNodes
  710. *
  711. * @param {MathItem} math The MathItem producing the error
  712. * @param {Error} err The Error object for the error
  713. */
  714. public compileError(math: MathItem<N, T, D>, err: Error) {
  715. math.root = this.mmlFactory.create('math', null, [
  716. this.mmlFactory.create('merror', {'data-mjx-error': err.message, title: err.message}, [
  717. this.mmlFactory.create('mtext', null, [
  718. (this.mmlFactory.create('text') as TextNode).setText('Math input error')
  719. ])
  720. ])
  721. ]);
  722. if (math.display) {
  723. math.root.attributes.set('display', 'block');
  724. }
  725. math.inputData.error = err.message;
  726. }
  727. /**
  728. * @override
  729. */
  730. public typeset() {
  731. if (!this.processed.isSet('typeset')) {
  732. for (const math of this.math) {
  733. try {
  734. math.typeset(this);
  735. } catch (err) {
  736. if (err.retry || err.restart) {
  737. throw err;
  738. }
  739. this.options['typesetError'](this, math, err);
  740. math.outputData['error'] = err;
  741. }
  742. }
  743. this.processed.set('typeset');
  744. }
  745. return this;
  746. }
  747. /**
  748. * Produce an error using HTML
  749. *
  750. * @param {MathItem} math The MathItem producing the error
  751. * @param {Error} err The Error object for the error
  752. */
  753. public typesetError(math: MathItem<N, T, D>, err: Error) {
  754. math.typesetRoot = this.adaptor.node('mjx-container', {
  755. class: 'MathJax mjx-output-error',
  756. jax: this.outputJax.name,
  757. }, [
  758. this.adaptor.node('span', {
  759. 'data-mjx-error': err.message,
  760. title: err.message,
  761. style: {
  762. color: 'red',
  763. 'background-color': 'yellow',
  764. 'line-height': 'normal'
  765. }
  766. }, [
  767. this.adaptor.text('Math output error')
  768. ])
  769. ]);
  770. if (math.display) {
  771. this.adaptor.setAttributes(math.typesetRoot, {
  772. style: {
  773. display: 'block',
  774. margin: '1em 0',
  775. 'text-align': 'center'
  776. }
  777. });
  778. }
  779. math.outputData.error = err.message;
  780. }
  781. /**
  782. * @override
  783. */
  784. public getMetrics() {
  785. if (!this.processed.isSet('getMetrics')) {
  786. this.outputJax.getMetrics(this);
  787. this.processed.set('getMetrics');
  788. }
  789. return this;
  790. }
  791. /**
  792. * @override
  793. */
  794. public updateDocument() {
  795. if (!this.processed.isSet('updateDocument')) {
  796. for (const math of this.math.reversed()) {
  797. math.updateDocument(this);
  798. }
  799. this.processed.set('updateDocument');
  800. }
  801. return this;
  802. }
  803. /**
  804. * @override
  805. */
  806. public removeFromDocument(_restore: boolean = false) {
  807. return this;
  808. }
  809. /**
  810. * @override
  811. */
  812. public state(state: number, restore: boolean = false) {
  813. for (const math of this.math) {
  814. math.state(state, restore);
  815. }
  816. if (state < STATE.INSERTED) {
  817. this.processed.clear('updateDocument');
  818. }
  819. if (state < STATE.TYPESET) {
  820. this.processed.clear('typeset');
  821. this.processed.clear('getMetrics');
  822. }
  823. if (state < STATE.COMPILED) {
  824. this.processed.clear('compile');
  825. }
  826. return this;
  827. }
  828. /**
  829. * @override
  830. */
  831. public reset(options: ResetList = {processed: true}) {
  832. options = userOptions(Object.assign({}, resetOptions), options);
  833. options.all && Object.assign(options, resetAllOptions);
  834. options.processed && this.processed.reset();
  835. options.inputJax && this.inputJax.forEach(jax => jax.reset(...options.inputJax));
  836. options.outputJax && this.outputJax.reset(...options.outputJax);
  837. return this;
  838. }
  839. /**
  840. * @override
  841. */
  842. public clear() {
  843. this.reset();
  844. this.math.clear();
  845. return this;
  846. }
  847. /**
  848. * @override
  849. */
  850. public concat(list: MathList<N, T, D>) {
  851. this.math.merge(list);
  852. return this;
  853. }
  854. /**
  855. * @override
  856. */
  857. public clearMathItemsWithin(containers: ContainerList<N>) {
  858. const items = this.getMathItemsWithin(containers);
  859. this.math.remove(...items);
  860. return items;
  861. }
  862. /**
  863. * @override
  864. */
  865. public getMathItemsWithin(elements: ContainerList<N>) {
  866. if (!Array.isArray(elements)) {
  867. elements = [elements];
  868. }
  869. const adaptor = this.adaptor;
  870. const items = [] as MathItem<N, T, D>[];
  871. const containers = adaptor.getElements(elements, this.document);
  872. ITEMS:
  873. for (const item of this.math) {
  874. for (const container of containers) {
  875. if (item.start.node && adaptor.contains(container, item.start.node)) {
  876. items.push(item);
  877. continue ITEMS;
  878. }
  879. }
  880. }
  881. return items;
  882. }
  883. }
  884. /**
  885. * The constructor type for a MathDocument
  886. *
  887. * @template D The MathDocument type this constructor is for
  888. */
  889. export interface MathDocumentConstructor<D extends MathDocument<any, any, any>> {
  890. KIND: string;
  891. OPTIONS: OptionList;
  892. ProcessBits: typeof BitField;
  893. new (...args: any[]): D;
  894. }