MathItem.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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 MathItem objects
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {MathDocument} from './MathDocument.js';
  23. import {InputJax} from './InputJax.js';
  24. import {OptionList} from '../util/Options.js';
  25. import {MmlNode} from './MmlTree/MmlNode.js';
  26. /*****************************************************************/
  27. /**
  28. * The Location gives a location of a position in a document
  29. * (either a node and character position within it, or
  30. * an index into a string array, the character position within
  31. * the string, and the delimiter at that location).
  32. *
  33. * @template N The HTMLElement node class
  34. * @template T The Text node class
  35. */
  36. export type Location<N, T> = {
  37. i?: number;
  38. n?: number;
  39. delim?: string;
  40. node?: N | T;
  41. };
  42. /*****************************************************************/
  43. /**
  44. * The Metrics object includes the data needed to typeset
  45. * a MathItem.
  46. */
  47. export type Metrics = {
  48. em: number;
  49. ex: number;
  50. containerWidth: number;
  51. lineWidth: number;
  52. scale: number;
  53. };
  54. /*****************************************************************/
  55. /**
  56. * The MathItem interface
  57. *
  58. * The MathItem is the object that holds the information about a
  59. * particular expression on the page, including pointers to
  60. * where it is in the document, its compiled version (in the
  61. * internal format), its typeset version, its bounding box,
  62. * and so on.
  63. *
  64. * @template N The HTMLElement node class
  65. * @template T The Text node class
  66. * @template D The Document class
  67. */
  68. export interface MathItem<N, T, D> {
  69. /**
  70. * The string representing the expression to be processed
  71. */
  72. math: string;
  73. /**
  74. * The input jax used to process the math
  75. */
  76. inputJax: InputJax<N, T, D>;
  77. /**
  78. * Whether the math is in display mode or inline mode
  79. */
  80. display: boolean;
  81. /**
  82. * Whether this item is an escaped character or not
  83. */
  84. isEscaped: boolean;
  85. /**
  86. * The start and ending locations in the document of
  87. * this expression
  88. */
  89. start: Location<N, T>;
  90. end: Location<N, T>;
  91. /**
  92. * The internal format for this expression (once compiled)
  93. */
  94. root: MmlNode;
  95. /**
  96. * The typeset version of the expression (once typeset)
  97. */
  98. typesetRoot: N;
  99. /**
  100. * The metric information at the location of the math
  101. * (the em-size, scaling factor, etc.)
  102. */
  103. metrics: Metrics;
  104. /**
  105. * Extra data needed by the input or output jax, as needed
  106. */
  107. inputData: OptionList;
  108. outputData: OptionList;
  109. /**
  110. * Perform the renderActions on the document
  111. *
  112. * @param {MathDocument} document The MathDocument in which the math resides
  113. */
  114. render(document: MathDocument<N, T, D>): void;
  115. /**
  116. * Rerenders an already rendered item and inserts it into the document
  117. *
  118. * @param {MathDocument} document The MathDocument in which the math resides
  119. * @param {number=} start The state to start rerendering at (default = RERENDER)
  120. */
  121. rerender(document: MathDocument<N, T, D>, start?: number): void;
  122. /**
  123. * Converts the expression by calling the render actions until the state matches the end state
  124. *
  125. * @param {MathDocument} document The MathDocument in which the math resides
  126. * @param {number=} end The state to end rerendering at (default = LAST)
  127. */
  128. convert(document: MathDocument<N, T, D>, end?: number): void;
  129. /**
  130. * Converts the expression into the internal format by calling the input jax
  131. *
  132. * @param {MathDocument} document The MathDocument in which the math resides
  133. */
  134. compile(document: MathDocument<N, T, D>): void;
  135. /**
  136. * Converts the internal format to the typeset version by calling the output jax
  137. *
  138. * @param {MathDocument} document The MathDocument in which the math resides
  139. */
  140. typeset(document: MathDocument<N, T, D>): void;
  141. /**
  142. * Inserts the typeset version in place of the original form in the document
  143. *
  144. * @param {MathDocument} document The MathDocument in which the math resides
  145. */
  146. updateDocument(document: MathDocument<N, T, D>): void;
  147. /**
  148. * Removes the typeset version from the document, optionally replacing the original
  149. * form of the expression and its delimiters.
  150. *
  151. * @param {boolean} restore True if the original version is to be restored
  152. */
  153. removeFromDocument(restore: boolean): void;
  154. /**
  155. * Sets the metric information for this expression
  156. *
  157. * @param {number} em The size of 1 em in pixels
  158. * @param {number} ex The size of 1 ex in pixels
  159. * @param {number} cwidth The container width in pixels
  160. * @param {number} lwidth The line breaking width in pixels
  161. * @param {number} scale The scaling factor (unitless)
  162. */
  163. setMetrics(em: number, ex: number, cwidth: number, lwidth: number, scale: number): void;
  164. /**
  165. * Set or return the current processing state of this expression,
  166. * optionally restoring the document if rolling back an expression
  167. * that has been added to the document.
  168. *
  169. * @param {number} state The state to set for the expression
  170. * @param {number} restore True if the original form should be restored
  171. * to the document when rolling back a typeset version
  172. * @returns {number} The current state
  173. */
  174. state(state?: number, restore?: boolean): number;
  175. /**
  176. * Reset the item to its unprocessed state
  177. *
  178. * @param {number} restore True if the original form should be restored
  179. * to the document when rolling back a typeset version
  180. */
  181. reset(restore?: boolean): void;
  182. }
  183. /*****************************************************************/
  184. /**
  185. * The ProtoItem interface
  186. *
  187. * This is what is returned by the FindMath class, giving the location
  188. * of math within the document, and is used to produce the full
  189. * MathItem later (e.g., when the position within a string array
  190. * is translated back into the actual node location in the DOM).
  191. *
  192. * @template N The HTMLElement node class
  193. * @template T The Text node class
  194. */
  195. export type ProtoItem<N, T> = {
  196. math: string; // The math expression itself
  197. start: Location<N, T>; // The starting location of the math
  198. end: Location<N, T>; // The ending location of the math
  199. open?: string; // The opening delimiter
  200. close?: string; // The closing delimiter
  201. n?: number; // The index of the string in which this math is found
  202. display: boolean; // True means display mode, false is inline mode
  203. };
  204. /**
  205. * Produce a proto math item that can be turned into a MathItem
  206. *
  207. * @template N The HTMLElement node class
  208. * @template T The Text node class
  209. */
  210. export function protoItem<N, T>(open: string, math: string, close: string, n: number,
  211. start: number, end: number, display: boolean = null) {
  212. let item: ProtoItem<N, T> = {open: open, math: math, close: close,
  213. n: n, start: {n: start}, end: {n: end}, display: display};
  214. return item;
  215. }
  216. /*****************************************************************/
  217. /**
  218. * Implements the MathItem class
  219. *
  220. * @template N The HTMLElement node class
  221. * @template T The Text node class
  222. * @template D The Document class
  223. */
  224. export abstract class AbstractMathItem<N, T, D> implements MathItem<N, T, D> {
  225. /**
  226. * The source text for the math (e.g., TeX string)
  227. */
  228. public math: string;
  229. /**
  230. * The input jax associated with this item
  231. */
  232. public inputJax: InputJax<N, T, D>;
  233. /**
  234. * True when this math is in display mode
  235. */
  236. public display: boolean;
  237. /**
  238. * Reference to the beginning of the math in the document
  239. */
  240. public start: Location<N, T>;
  241. /**
  242. * Reference to the end of the math in the document
  243. */
  244. public end: Location<N, T>;
  245. /**
  246. * The compiled internal MathML (result of InputJax)
  247. */
  248. public root: MmlNode = null;
  249. /**
  250. * The typeset result (result of OutputJax)
  251. */
  252. public typesetRoot: N = null;
  253. /**
  254. * The metric information about the surrounding environment
  255. */
  256. public metrics: Metrics = {} as Metrics;
  257. /**
  258. * Data private to the input jax
  259. */
  260. public inputData: OptionList = {};
  261. /**
  262. * Data private to the output jax
  263. */
  264. public outputData: OptionList = {};
  265. /**
  266. * The current state of the item (how far in the render actions it has been processed)
  267. */
  268. protected _state: number = STATE.UNPROCESSED;
  269. /**
  270. * @return {boolean} True when this item is an escaped delimiter
  271. */
  272. public get isEscaped(): boolean {
  273. return this.display === null;
  274. }
  275. /**
  276. * @param {string} math The math expression for this item
  277. * @param {Inputjax} jax The input jax to use for this item
  278. * @param {boolean} display True if display mode, false if inline
  279. * @param {Location} start The starting position of the math in the document
  280. * @param {Location} end The ending position of the math in the document
  281. * @constructor
  282. */
  283. constructor (math: string, jax: InputJax<N, T, D>, display: boolean = true,
  284. start: Location<N, T> = {i: 0, n: 0, delim: ''},
  285. end: Location<N, T> = {i: 0, n: 0, delim: ''}) {
  286. this.math = math;
  287. this.inputJax = jax;
  288. this.display = display;
  289. this.start = start;
  290. this.end = end;
  291. this.root = null;
  292. this.typesetRoot = null;
  293. this.metrics = {} as Metrics;
  294. this.inputData = {};
  295. this.outputData = {};
  296. }
  297. /**
  298. * @override
  299. */
  300. public render(document: MathDocument<N, T, D>) {
  301. document.renderActions.renderMath(this, document);
  302. }
  303. /**
  304. * @override
  305. */
  306. public rerender(document: MathDocument<N, T, D>, start: number = STATE.RERENDER) {
  307. if (this.state() >= start) {
  308. this.state(start - 1);
  309. }
  310. document.renderActions.renderMath(this, document, start);
  311. }
  312. /**
  313. * @override
  314. */
  315. public convert(document: MathDocument<N, T, D>, end: number = STATE.LAST) {
  316. document.renderActions.renderConvert(this, document, end);
  317. }
  318. /**
  319. * @override
  320. */
  321. public compile(document: MathDocument<N, T, D>) {
  322. if (this.state() < STATE.COMPILED) {
  323. this.root = this.inputJax.compile(this, document);
  324. this.state(STATE.COMPILED);
  325. }
  326. }
  327. /**
  328. * @override
  329. */
  330. public typeset(document: MathDocument<N, T, D>) {
  331. if (this.state() < STATE.TYPESET) {
  332. this.typesetRoot = document.outputJax[this.isEscaped ? 'escaped' : 'typeset'](this, document);
  333. this.state(STATE.TYPESET);
  334. }
  335. }
  336. /**
  337. * @override
  338. */
  339. public updateDocument(_document: MathDocument<N, T, D>) {}
  340. /**
  341. * @override
  342. */
  343. public removeFromDocument(_restore: boolean = false) {}
  344. /**
  345. * @override
  346. */
  347. public setMetrics(em: number, ex: number, cwidth: number, lwidth: number, scale: number) {
  348. this.metrics = {
  349. em: em, ex: ex,
  350. containerWidth: cwidth,
  351. lineWidth: lwidth,
  352. scale: scale
  353. };
  354. }
  355. /**
  356. * @override
  357. */
  358. public state(state: number = null, restore: boolean = false) {
  359. if (state != null) {
  360. if (state < STATE.INSERTED && this._state >= STATE.INSERTED) {
  361. this.removeFromDocument(restore);
  362. }
  363. if (state < STATE.TYPESET && this._state >= STATE.TYPESET) {
  364. this.outputData = {};
  365. }
  366. if (state < STATE.COMPILED && this._state >= STATE.COMPILED) {
  367. this.inputData = {};
  368. }
  369. this._state = state;
  370. }
  371. return this._state;
  372. }
  373. /**
  374. * @override
  375. */
  376. public reset(restore: boolean = false) {
  377. this.state(STATE.UNPROCESSED, restore);
  378. }
  379. }
  380. /*****************************************************************/
  381. /**
  382. * The various states that a MathItem (or MathDocument) can be in
  383. * (open-ended so that extensions can add to it)
  384. */
  385. export const STATE: {[state: string]: number} = {
  386. UNPROCESSED: 0,
  387. FINDMATH: 10,
  388. COMPILED: 20,
  389. CONVERT: 100,
  390. METRICS: 110,
  391. RERENDER: 125,
  392. TYPESET: 150,
  393. INSERTED: 200,
  394. LAST: 10000
  395. };
  396. /**
  397. * Allocate a new named state
  398. *
  399. * @param {string} name The name of the new state
  400. * @param {number} state The value for the new state
  401. */
  402. export function newState(name: string, state: number) {
  403. if (name in STATE) {
  404. throw Error('State ' + name + ' already exists');
  405. }
  406. STATE[name] = state;
  407. }