EmpheqUtil.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2021-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 Utilities file for the empheq package.
  19. *
  20. * @author dpvc@mathjax.org (Davide P. Cervone)
  21. */
  22. import ParseUtil from '../ParseUtil.js';
  23. import TexParser from '../TexParser.js';
  24. import {EnvList} from '../StackItem.js';
  25. import {AbstractTags} from '../Tags.js';
  26. import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
  27. import {MmlMtable} from '../../../core/MmlTree/MmlNodes/mtable.js';
  28. import {MmlMtd} from '../../../core/MmlTree/MmlNodes/mtd.js';
  29. import {EmpheqBeginItem} from './EmpheqConfiguration.js';
  30. export const EmpheqUtil = {
  31. /**
  32. * Create the needed envinronment and process it by the give function.
  33. *
  34. * @param {TexParser} parser The active tex parser.
  35. * @param {string} env The environment to create.
  36. * @param {Function} func A function to process the environment.
  37. * @param {any[]} args The arguments for func.
  38. */
  39. environment(parser: TexParser, env: string, func: Function, args: any[]) {
  40. const name = args[0];
  41. const item = parser.itemFactory.create(name + '-begin').setProperties({name: env, end: name});
  42. parser.Push(func(parser, item, ...args.slice(1)));
  43. },
  44. /**
  45. * Parse an options string.
  46. *
  47. * @param {string} text The string to parse.
  48. * @param {{[key:string]:number} allowed Object containing options to allow
  49. * @return {EnvList} The parsed keys
  50. */
  51. splitOptions(text: string, allowed: {[key: string]: number} = null): EnvList {
  52. return ParseUtil.keyvalOptions(text, allowed, true);
  53. },
  54. /**
  55. * Find the number of columns in the table.
  56. *
  57. * @param {MmlMtable} table The table whose columns to count.
  58. * @return {number} The number of columns in the table.
  59. */
  60. columnCount(table: MmlMtable): number {
  61. let m = 0;
  62. for (const row of table.childNodes) {
  63. const n = row.childNodes.length - (row.isKind('mlabeledtr') ? 1 : 0);
  64. if (n > m) m = n;
  65. }
  66. return m;
  67. },
  68. /**
  69. * Create an mpadded element with no height and depth, but whose
  70. * content is the given TeX code with a phantom that is the height and
  71. * depth of the given table.
  72. *
  73. * @param {string} tex The TeX code to put in the box.
  74. * @param {MmlTable} table The table used to size the box.
  75. * @param {TexParser} parser The active tex parser.
  76. * @param {string} env The name of the current environment.
  77. * @return {MmlNode} The mpadded element.
  78. */
  79. cellBlock(tex: string, table: MmlMtable, parser: TexParser, env: string): MmlNode {
  80. const mpadded = parser.create('node', 'mpadded', [], {height: 0, depth: 0, voffset: '-1height'});
  81. const result = new TexParser(tex, parser.stack.env, parser.configuration);
  82. const mml = result.mml();
  83. if (env && result.configuration.tags.label) {
  84. (result.configuration.tags.currentTag as any).env = env;
  85. (result.configuration.tags as AbstractTags).getTag(true);
  86. }
  87. for (const child of (mml.isInferred ? mml.childNodes : [mml])) {
  88. mpadded.appendChild(child);
  89. }
  90. mpadded.appendChild(parser.create('node', 'mphantom', [
  91. parser.create('node', 'mpadded', [table], {width: 0})
  92. ]));
  93. return mpadded;
  94. },
  95. /**
  96. * Make a copy of the table with only the first row and create a phantom element
  97. * that has its height and depth.
  98. *
  99. * @param {MmlMtable} original The original table.
  100. * @param {TexParser} parser The active tex parser.
  101. * @return {MmlNode} The resulting mphantom element.
  102. */
  103. topRowTable(original: MmlMtable, parser: TexParser): MmlNode {
  104. const table = ParseUtil.copyNode(original, parser);
  105. table.setChildren(table.childNodes.slice(0, 1));
  106. table.attributes.set('align', 'baseline 1');
  107. return original.factory.create('mphantom', {}, [parser.create('node', 'mpadded', [table], {width: 0})]);
  108. },
  109. /**
  110. * Add an mpadded element that has zero height and depth but whose content is
  111. * the cell block for the given TeX code followed by a struct the size of the top row.
  112. *
  113. * @param {MmlMtd} mtd The mtd to add content to.
  114. * @param {string} tex The TeX string to put into the cell.
  115. * @param {MmlMtable} table The reference table used for its various heights.
  116. * @param {TexParser} parser The active tex parser.
  117. * @param {srting} env The current environment.
  118. */
  119. rowspanCell(mtd: MmlMtd, tex: string, table: MmlMtable, parser: TexParser, env: string) {
  120. mtd.appendChild(
  121. parser.create('node', 'mpadded', [
  122. this.cellBlock(tex, ParseUtil.copyNode(table, parser), parser, env),
  123. this.topRowTable(table, parser)
  124. ], {height: 0, depth: 0, voffset: 'height'})
  125. );
  126. },
  127. /**
  128. * Add something on the left of the original table.
  129. *
  130. * @param {MmlMtable} table The table to modify.
  131. * @param {MmlMtable} original The original table.
  132. * @param {string} left The TeX code to add to the left.
  133. * @param {TexParser} parser The active tex parser.
  134. * @param {string} env The current environment.
  135. */
  136. left(table: MmlMtable, original: MmlMtable, left: string, parser: TexParser, env: string = '') {
  137. table.attributes.set('columnalign', 'right ' + (table.attributes.get('columnalign') || ''));
  138. table.attributes.set('columnspacing', '0em ' + (table.attributes.get('columnspacing') || ''));
  139. let mtd;
  140. for (const row of table.childNodes.slice(0).reverse()) {
  141. mtd = parser.create('node', 'mtd');
  142. row.childNodes.unshift(mtd);
  143. mtd.parent = row;
  144. if (row.isKind('mlabeledtr')) {
  145. row.childNodes[0] = row.childNodes[1];
  146. row.childNodes[1] = mtd;
  147. }
  148. }
  149. this.rowspanCell(mtd, left, original, parser, env);
  150. },
  151. /**
  152. * Add something on the right of the original table.
  153. *
  154. * @param {MmlMtable} table The table to modify.
  155. * @param {MmlMtable} original The original table.
  156. * @param {string} right The TeX code to add to the right.
  157. * @param {TexParser} parser The active tex parser.
  158. * @param {string} env The current environment.
  159. */
  160. right(table: MmlMtable, original: MmlMtable, right: string, parser: TexParser, env: string = '') {
  161. if (table.childNodes.length === 0) {
  162. table.appendChild(parser.create('node', 'mtr'));
  163. }
  164. const m = EmpheqUtil.columnCount(table);
  165. const row = table.childNodes[0];
  166. while (row.childNodes.length < m) row.appendChild(parser.create('node', 'mtd'));
  167. const mtd = row.appendChild(parser.create('node', 'mtd')) as MmlMtd;
  168. EmpheqUtil.rowspanCell(mtd, right, original, parser, env);
  169. table.attributes.set(
  170. 'columnalign',
  171. (table.attributes.get('columnalign') as string || '').split(/ /).slice(0, m).join(' ') + ' left'
  172. );
  173. table.attributes.set(
  174. 'columnspacing',
  175. (table.attributes.get('columnspacing') as string || '').split(/ /).slice(0, m - 1).join(' ') + ' 0em'
  176. );
  177. },
  178. /**
  179. * Add the left- and right-hand material to the table.
  180. */
  181. adjustTable(empheq: EmpheqBeginItem, parser: TexParser) {
  182. const left = empheq.getProperty('left');
  183. const right = empheq.getProperty('right');
  184. if (left || right) {
  185. const table = empheq.Last;
  186. const original = ParseUtil.copyNode(table, parser);
  187. if (left) this.left(table, original, left, parser);
  188. if (right) this.right(table, original, right, parser);
  189. }
  190. },
  191. /**
  192. * The environments allowed to be used in the empheq environment.
  193. */
  194. allowEnv: {
  195. equation: true,
  196. align: true,
  197. gather: true,
  198. flalign: true,
  199. alignat: true,
  200. multline: true
  201. },
  202. /**
  203. * Checks to see if the given environment is one of the allowed ones.
  204. *
  205. * @param {string} env The environment to check.
  206. * @return {boolean} True if the environment is allowed.
  207. */
  208. checkEnv(env: string): boolean {
  209. return this.allowEnv.hasOwnProperty(env.replace(/\*$/, '')) || false;
  210. }
  211. };