mmultiscripts.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2018-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 CommonMmultiscripts wrapper mixin for the MmlMmultiscripts object
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {AnyWrapper, Constructor} from '../Wrapper.js';
  23. import {CommonMsubsup, MsubsupConstructor} from './msubsup.js';
  24. import {BBox} from '../../../util/BBox.js';
  25. /*****************************************************************/
  26. /**
  27. * The data about the scripts and base
  28. */
  29. export type ScriptData = {
  30. base: BBox;
  31. sub: BBox; // combined bbox for all subscripts
  32. sup: BBox; // combined bbox for all superscripts
  33. psub: BBox; // combined bbox for all presubscripts
  34. psup: BBox; // combined bbox for all presuperscripts
  35. numPrescripts: number;
  36. numScripts: number;
  37. };
  38. export type ScriptDataName = keyof ScriptData;
  39. /**
  40. * The lists of all the individual script bboxes
  41. */
  42. export type ScriptLists = {
  43. base: BBox[];
  44. subList: BBox[];
  45. supList: BBox[];
  46. psubList: BBox[];
  47. psupList: BBox[];
  48. };
  49. export type ScriptListName = keyof ScriptLists;
  50. /**
  51. * The type of script that follows the given type
  52. */
  53. export const NextScript: {[key: string]: ScriptListName} = {
  54. base: 'subList',
  55. subList: 'supList',
  56. supList: 'subList',
  57. psubList: 'psupList',
  58. psupList: 'psubList',
  59. };
  60. /**
  61. * The names of the scripts (for looping)
  62. */
  63. export const ScriptNames = ['sup', 'sup', 'psup', 'psub'] as ScriptDataName[];
  64. /*****************************************************************/
  65. /**
  66. * The CommonMmultiscripts interface
  67. *
  68. * @template W The child-node Wrapper class
  69. */
  70. export interface CommonMmultiscripts<W extends AnyWrapper> extends CommonMsubsup<W> {
  71. /**
  72. * The cached data for the various bounding boxes
  73. */
  74. scriptData: ScriptData;
  75. /**
  76. * The index of the child following the <mprescripts/> tag
  77. */
  78. firstPrescript: number;
  79. /**
  80. * @param {BBox} pre The prescript bounding box
  81. * @param {BBox} post The postcript bounding box
  82. * @return {BBox} The combined bounding box
  83. */
  84. combinePrePost(pre: BBox, post: BBox): BBox;
  85. /**
  86. * Compute the bounding box information about all the scripts
  87. */
  88. getScriptData(): void;
  89. /**
  90. * @return {ScriptLists} The bounding boxes for all the scripts divided into lists by position
  91. */
  92. getScriptBBoxLists(): ScriptLists;
  93. /**
  94. * Pad the second list, if it is one short
  95. *
  96. * @param {BBox[]} list1 The first list
  97. * @param {BBox[]} list2 The second list
  98. */
  99. padLists(list1: BBox[], list2: BBox[]): void;
  100. /**
  101. * @param {BBox} bbox1 The bbox for the combined subscripts
  102. * @param {BBox} bbox2 The bbox for the combined superscripts
  103. * @param {BBox[]} list1 The list of subscripts to combine
  104. * @param {BBox[]} list2 The list of superscripts to combine
  105. */
  106. combineBBoxLists(bbox1: BBox, bbox2: BBox, list1: BBox[], list2: BBox[]): void;
  107. /**
  108. * @param {BBox} bbox The bounding box from which to get the (scaled) width, height, and depth
  109. */
  110. getScaledWHD(bbox: BBox): void;
  111. }
  112. /**
  113. * Shorthand for the CommonMmultiscripts constructor
  114. *
  115. * @template W The child-node Wrapper class
  116. */
  117. export type MmultiscriptsConstructor<W extends AnyWrapper> = Constructor<CommonMmultiscripts<W>>;
  118. /*****************************************************************/
  119. /**
  120. * The CommonMmultiscripts wrapper mixin for the MmlMmultiscripts object
  121. *
  122. * @template W The child-node Wrapper class
  123. * @template T The Wrapper class constructor type
  124. */
  125. export function CommonMmultiscriptsMixin<
  126. W extends AnyWrapper,
  127. T extends MsubsupConstructor<W>
  128. >(Base: T): MmultiscriptsConstructor<W> & T {
  129. return class extends Base {
  130. /**
  131. * The cached data for the various bounding boxes
  132. */
  133. public scriptData: ScriptData = null;
  134. /**
  135. * The index of the child following the <mprescripts/> tag
  136. */
  137. public firstPrescript = 0;
  138. /**
  139. * @override
  140. */
  141. constructor(...args: any[]) {
  142. super(...args);
  143. this.getScriptData();
  144. }
  145. /*************************************************************/
  146. /**
  147. * @param {BBox} pre The prescript bounding box
  148. * @param {BBox} post The postcript bounding box
  149. * @return {BBox} The combined bounding box
  150. */
  151. public combinePrePost(pre: BBox, post: BBox): BBox {
  152. const bbox = new BBox(pre);
  153. bbox.combine(post, 0, 0);
  154. return bbox;
  155. }
  156. /*************************************************************/
  157. /**
  158. * @override
  159. */
  160. public computeBBox(bbox: BBox, recompute: boolean = false) {
  161. //
  162. // Get the bounding boxes, and combine the pre- and post-scripts
  163. // to get a common offset for both
  164. //
  165. const scriptspace = this.font.params.scriptspace;
  166. const data = this.scriptData;
  167. const sub = this.combinePrePost(data.sub, data.psub);
  168. const sup = this.combinePrePost(data.sup, data.psup);
  169. const [u, v] = this.getUVQ(sub, sup);
  170. //
  171. // Lay out the pre-scripts, then the base, then the post-scripts
  172. //
  173. bbox.empty();
  174. if (data.numPrescripts) {
  175. bbox.combine(data.psup, scriptspace, u);
  176. bbox.combine(data.psub, scriptspace, v);
  177. }
  178. bbox.append(data.base);
  179. if (data.numScripts) {
  180. const w = bbox.w;
  181. bbox.combine(data.sup, w, u);
  182. bbox.combine(data.sub, w, v);
  183. bbox.w += scriptspace;
  184. }
  185. bbox.clean();
  186. this.setChildPWidths(recompute);
  187. }
  188. /**
  189. * Compute the bounding box information about all the scripts
  190. */
  191. public getScriptData() {
  192. //
  193. // Initialize the bounding box data
  194. //
  195. const data: ScriptData = this.scriptData = {
  196. base: null, sub: BBox.empty(), sup: BBox.empty(), psub: BBox.empty(), psup: BBox.empty(),
  197. numPrescripts: 0, numScripts: 0
  198. };
  199. //
  200. // Get the bboxes for all the scripts and combine them into the scriptData
  201. //
  202. const lists = this.getScriptBBoxLists();
  203. this.combineBBoxLists(data.sub, data.sup, lists.subList, lists.supList);
  204. this.combineBBoxLists(data.psub, data.psup, lists.psubList, lists.psupList);
  205. data.base = lists.base[0];
  206. //
  207. // Save the lengths and return the data
  208. //
  209. data.numPrescripts = lists.psubList.length;
  210. data.numScripts = lists.subList.length;
  211. }
  212. /**
  213. * @return {ScriptLists} The bounding boxes for all the scripts divided into lists by position
  214. */
  215. public getScriptBBoxLists(): ScriptLists {
  216. const lists: ScriptLists = {
  217. base: [], subList: [], supList: [], psubList: [], psupList: []
  218. };
  219. //
  220. // The first entry is the base, and then they altername sub- and superscripts.
  221. // Once we find the <mprescripts/> element, switch to presub- and presuperscript lists.
  222. //
  223. let script: ScriptListName = 'base';
  224. for (const child of this.childNodes) {
  225. if (child.node.isKind('mprescripts')) {
  226. script = 'psubList';
  227. } else {
  228. lists[script].push(child.getOuterBBox());
  229. script = NextScript[script];
  230. }
  231. }
  232. //
  233. // The index of the first prescript (skip over base, sub- and superscripts, and mprescripts)
  234. //
  235. this.firstPrescript = lists.subList.length + lists.supList.length + 2;
  236. //
  237. // Make sure the lists are the same length
  238. //
  239. this.padLists(lists.subList, lists.supList);
  240. this.padLists(lists.psubList, lists.psupList);
  241. return lists;
  242. }
  243. /**
  244. * Pad the second list, if it is one short
  245. *
  246. * @param {BBox[]} list1 The first list
  247. * @param {BBox[]} list2 The second list
  248. */
  249. public padLists(list1: BBox[], list2: BBox[]) {
  250. if (list1.length > list2.length) {
  251. list2.push(BBox.empty());
  252. }
  253. }
  254. /**
  255. * @param {BBox} bbox1 The bbox for the combined subscripts
  256. * @param {BBox} bbox2 The bbox for the combined superscripts
  257. * @param {BBox[]} list1 The list of subscripts to combine
  258. * @param {BBox[]} list2 The list of superscripts to combine
  259. */
  260. public combineBBoxLists(bbox1: BBox, bbox2: BBox, list1: BBox[], list2: BBox[]) {
  261. for (let i = 0; i < list1.length; i++) {
  262. const [w1, h1, d1] = this.getScaledWHD(list1[i]);
  263. const [w2, h2, d2] = this.getScaledWHD(list2[i]);
  264. const w = Math.max(w1, w2);
  265. bbox1.w += w;
  266. bbox2.w += w;
  267. if (h1 > bbox1.h) bbox1.h = h1;
  268. if (d1 > bbox1.d) bbox1.d = d1;
  269. if (h2 > bbox2.h) bbox2.h = h2;
  270. if (d2 > bbox2.d) bbox2.d = d2;
  271. }
  272. }
  273. /**
  274. * @param {BBox} bbox The bounding box from which to get the (scaled) width, height, and depth
  275. */
  276. public getScaledWHD(bbox: BBox) {
  277. const {w, h, d, rscale} = bbox;
  278. return [w * rscale, h * rscale, d * rscale];
  279. }
  280. /*************************************************************/
  281. /**
  282. * @override
  283. */
  284. public getUVQ(subbox: BBox, supbox: BBox) {
  285. if (!this.UVQ) {
  286. let [u, v, q] = [0, 0, 0];
  287. if (subbox.h === 0 && subbox.d === 0) {
  288. //
  289. // Use placement for superscript only
  290. //
  291. u = this.getU();
  292. } else if (supbox.h === 0 && supbox.d === 0) {
  293. //
  294. // Use placement for subsccript only
  295. //
  296. u = -this.getV();
  297. } else {
  298. //
  299. // Use placement for both
  300. //
  301. [u, v, q] = super.getUVQ(subbox, supbox);
  302. }
  303. this.UVQ = [u, v, q];
  304. }
  305. return this.UVQ;
  306. }
  307. };
  308. }