menclose.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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 CommonMenclose wrapper mixin for the MmlMenclose object
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {AnyWrapper, WrapperConstructor, Constructor, AnyWrapperClass} from '../Wrapper.js';
  23. import * as Notation from '../Notation.js';
  24. import {CommonMsqrt} from './msqrt.js';
  25. import {BBox} from '../../../util/BBox.js';
  26. import {AbstractMmlNode} from '../../../core/MmlTree/MmlNode.js';
  27. import {split} from '../../../util/string.js';
  28. /*****************************************************************/
  29. /**
  30. * The CommonMenclose interface
  31. *
  32. * @template W The menclose wrapper type
  33. */
  34. export interface CommonMenclose<W extends AnyWrapper, S extends CommonMsqrt, N> extends AnyWrapper {
  35. /**
  36. * The notations active on this menclose, and the one to use for the child, if any
  37. */
  38. notations: Notation.List<W, N>;
  39. renderChild: Notation.Renderer<W, N>;
  40. /**
  41. * fake msqrt for radial notation (if used)
  42. */
  43. msqrt: S;
  44. /**
  45. * The padding, thickness, and shape of the arrow head
  46. * (may be overridden using data-padding, data-thickness, and data-arrowhead attibutes)
  47. */
  48. padding: number;
  49. thickness: number;
  50. arrowhead: {x: number, y: number, dx: number};
  51. /**
  52. * The top, right, bottom, and left padding, added by notations
  53. */
  54. TRBL: Notation.PaddingData;
  55. /**
  56. * Look up the data-* attributes and override the default values
  57. */
  58. getParameters(): void;
  59. /**
  60. * Get the notations given in the notation attribute
  61. * and check if any are used to render the child nodes
  62. */
  63. getNotations(): void;
  64. /**
  65. * Remove any redundant notations
  66. */
  67. removeRedundantNotations(): void;
  68. /**
  69. * Run any initialization needed by notations in use
  70. */
  71. initializeNotations(): void;
  72. /**
  73. * @return {Notation.PaddingData} Array of the maximum extra space from the notations along each side
  74. */
  75. getBBoxExtenders(): Notation.PaddingData;
  76. /**
  77. * @return {Notation.PaddingData} Array of padding (i.e., BBox minus border) along each side
  78. */
  79. getPadding(): Notation.PaddingData;
  80. /**
  81. * Each entry in X gets replaced by the corresponding one in Y if it is larger
  82. *
  83. * @param {Notation.PaddingData} X An array of numbers
  84. * @param {Notation.PaddingData} Y An array of numbers that replace smaller ones in X
  85. */
  86. maximizeEntries(X: Notation.PaddingData, Y: Notation.PaddingData): void;
  87. /**
  88. * Get the offset amount for the given direction for vertical and horizontal centering
  89. *
  90. * @param {string} direction The direction 'X' or 'Y' for the offset
  91. * @return {number} The amount of offset in that direction
  92. */
  93. getOffset(direction: string): number;
  94. /**
  95. * @param {number} w The width of the box whose diagonal is needed
  96. * @param {number} h The height of the box whose diagonal is needed
  97. * @return {number[]} The angle and width of the diagonal of the box
  98. */
  99. getArgMod(w: number, h: number): [number, number];
  100. /**
  101. * Create an arrow for output
  102. *
  103. * @param {number} w The length of the arrow
  104. * @param {number} a The angle for the arrow
  105. * @param {boolean} double True if this is a double-headed arrow
  106. * @param {string} offset 'X' for vertical arrow, 'Y' for horizontal
  107. * @param {number} trans Distance to translate in the offset direction
  108. * @return {N} The newly created arrow
  109. */
  110. arrow(w: number, a: number, double: boolean, offset?: string, trans?: number): N;
  111. /**
  112. * Get the angle and width of a diagonal arrow, plus the x and y extension
  113. * past the content bounding box
  114. */
  115. arrowData(): {a: number, W: number, x: number, y: number};
  116. /**
  117. * Get the angle and width for a diagonal arrow
  118. *
  119. * @return {[number, number]} The angle and width
  120. */
  121. arrowAW(): [number, number];
  122. /**
  123. * Create an unattached msqrt wrapper to render the 'radical' notation.
  124. * We replace the inferred mrow of the msqrt with the one from the menclose
  125. * but without changing the parent pointer, so as not to detach it from
  126. * the menclose (which would desrtoy the original MathML tree).
  127. *
  128. * @param {W} child The inferred mrow that is the child of this menclose
  129. * @return {S} The newly created (but detached) msqrt wrapper
  130. */
  131. createMsqrt(child: W): S;
  132. /**
  133. * @return {number[]} The differences between the msqrt bounding box
  134. * and its child bounding box (i.e., the extra space
  135. * created by the radical symbol).
  136. */
  137. sqrtTRBL(): number[];
  138. }
  139. /**
  140. * The CommonMenclose class interface
  141. *
  142. * @template W The menclose wrapper type
  143. * @templare N The DOM node class
  144. */
  145. export interface CommonMencloseClass<W extends AnyWrapper, N> extends AnyWrapperClass {
  146. /**
  147. * The definitions of the various notations
  148. */
  149. notations: Notation.DefList<W, N>;
  150. }
  151. /**
  152. * Shorthand for the CommonMenclose constructor
  153. *
  154. * @template W The menclose wrapper type
  155. */
  156. export type MencloseConstructor<W extends AnyWrapper, S extends CommonMsqrt, N> = Constructor<CommonMenclose<W, S, N>>;
  157. /*****************************************************************/
  158. /**
  159. * The CommonMenclose wrapper mixin for the MmlMenclose object
  160. *
  161. * @template W The menclose wrapper type
  162. * @templare N The DOM node class
  163. * @templare S The msqrt wrapper class
  164. * @template T The Wrapper class constructor type
  165. */
  166. export function CommonMencloseMixin<
  167. W extends AnyWrapper,
  168. S extends CommonMsqrt,
  169. N,
  170. T extends WrapperConstructor
  171. >(Base: T): MencloseConstructor<W, S, N> & T {
  172. return class extends Base {
  173. /**
  174. * The notations active on this menclose, if any
  175. */
  176. public notations: Notation.List<W, N> = {};
  177. /**
  178. * The notation to use for the child, if any
  179. */
  180. public renderChild: Notation.Renderer<W, N> = null;
  181. /**
  182. * fake msqrt for radial notation (if used)
  183. */
  184. public msqrt: S = null;
  185. /**
  186. * The padding of the arrow head (may be overridden using data-padding attibute)
  187. */
  188. public padding: number = Notation.PADDING;
  189. /**
  190. * The thickness of the arrow head (may be overridden using data-thickness attibute)
  191. */
  192. public thickness: number = Notation.THICKNESS;
  193. /**
  194. * The shape of the arrow head (may be overridden using data-arrowhead attibutes)
  195. */
  196. public arrowhead = {x: Notation.ARROWX, y: Notation.ARROWY, dx: Notation.ARROWDX};
  197. /**
  198. * The top, right, bottom, and left padding (added by notations)
  199. */
  200. public TRBL: Notation.PaddingData = [0, 0, 0, 0];
  201. /**
  202. * @override
  203. * @constructor
  204. */
  205. constructor(...args: any[]) {
  206. super(...args);
  207. this.getParameters();
  208. this.getNotations();
  209. this.removeRedundantNotations();
  210. this.initializeNotations();
  211. this.TRBL = this.getBBoxExtenders();
  212. }
  213. /**
  214. * Look up the data-* attributes and override the default values
  215. */
  216. public getParameters() {
  217. const attributes = this.node.attributes;
  218. const padding = attributes.get('data-padding');
  219. if (padding !== undefined) {
  220. this.padding = this.length2em(padding, Notation.PADDING);
  221. }
  222. const thickness = attributes.get('data-thickness');
  223. if (thickness !== undefined) {
  224. this.thickness = this.length2em(thickness, Notation.THICKNESS);
  225. }
  226. const arrowhead = attributes.get('data-arrowhead') as string;
  227. if (arrowhead !== undefined) {
  228. let [x, y, dx] = split(arrowhead);
  229. this.arrowhead = {
  230. x: (x ? parseFloat(x) : Notation.ARROWX),
  231. y: (y ? parseFloat(y) : Notation.ARROWY),
  232. dx: (dx ? parseFloat(dx) : Notation.ARROWDX)
  233. };
  234. }
  235. }
  236. /**
  237. * Get the notations given in the notation attribute
  238. * and check if any are used to render the child nodes
  239. */
  240. public getNotations() {
  241. const Notations = (this.constructor as CommonMencloseClass<W, N>).notations;
  242. for (const name of split(this.node.attributes.get('notation') as string)) {
  243. const notation = Notations.get(name);
  244. if (notation) {
  245. this.notations[name] = notation;
  246. if (notation.renderChild) {
  247. this.renderChild = notation.renderer;
  248. }
  249. }
  250. }
  251. }
  252. /**
  253. * Remove any redundant notations
  254. */
  255. public removeRedundantNotations() {
  256. for (const name of Object.keys(this.notations)) {
  257. if (this.notations[name]) {
  258. const remove = this.notations[name].remove || '';
  259. for (const notation of remove.split(/ /)) {
  260. delete this.notations[notation];
  261. }
  262. }
  263. }
  264. }
  265. /**
  266. * Run any initialization needed by notations in use
  267. */
  268. public initializeNotations() {
  269. for (const name of Object.keys(this.notations)) {
  270. const init = this.notations[name].init;
  271. init && init(this as any);
  272. }
  273. }
  274. /********************************************************/
  275. /**
  276. * @override
  277. */
  278. public computeBBox(bbox: BBox, recompute: boolean = false) {
  279. //
  280. // Combine the BBox from the child and add the extenders
  281. //
  282. let [T, R, B, L] = this.TRBL;
  283. const child = this.childNodes[0].getBBox();
  284. bbox.combine(child, L, 0);
  285. bbox.h += T;
  286. bbox.d += B;
  287. bbox.w += R;
  288. this.setChildPWidths(recompute);
  289. }
  290. /**
  291. * @return {Notation.PaddingData} Array of the maximum extra space from the notations along each side
  292. */
  293. public getBBoxExtenders(): Notation.PaddingData {
  294. let TRBL = [0, 0, 0, 0] as Notation.PaddingData;
  295. for (const name of Object.keys(this.notations)) {
  296. this.maximizeEntries(TRBL, this.notations[name].bbox(this as any));
  297. }
  298. return TRBL;
  299. }
  300. /**
  301. * @return {Notation.PaddingData} Array of padding (i.e., BBox minus border) along each side
  302. */
  303. public getPadding(): Notation.PaddingData {
  304. let BTRBL = [0, 0, 0, 0] as Notation.PaddingData;
  305. for (const name of Object.keys(this.notations)) {
  306. const border = this.notations[name].border;
  307. if (border) {
  308. this.maximizeEntries(BTRBL, border(this as any));
  309. }
  310. }
  311. return [0, 1, 2, 3].map(i => this.TRBL[i] - BTRBL[i]) as Notation.PaddingData;
  312. }
  313. /**
  314. * Each entry in X gets replaced by the corresponding one in Y if it is larger
  315. *
  316. * @param {Notation.PaddingData} X An array of numbers
  317. * @param {Notation.PaddingData} Y An array of numbers that replace smaller ones in X
  318. */
  319. public maximizeEntries(X: Notation.PaddingData, Y: Notation.PaddingData) {
  320. for (let i = 0; i < X.length; i++) {
  321. if (X[i] < Y[i]) {
  322. X[i] = Y[i];
  323. }
  324. }
  325. }
  326. /********************************************************/
  327. /**
  328. * Get the offset amount for the given direction for vertical and horizontal centering
  329. *
  330. * @param {string} direction The direction 'X' or 'Y' for the offset
  331. * @return {number} The amount of offset in that direction
  332. */
  333. public getOffset(direction: string): number {
  334. let [T, R, B, L] = this.TRBL;
  335. const d = (direction === 'X' ? R - L : B - T) / 2;
  336. return (Math.abs(d) > .001 ? d : 0);
  337. }
  338. /**
  339. * @param {number} w The width of the box whose diagonal is needed
  340. * @param {number} h The height of the box whose diagonal is needed
  341. * @return {number[]} The angle and width of the diagonal of the box
  342. */
  343. public getArgMod(w: number, h: number): [number, number] {
  344. return [Math.atan2(h, w), Math.sqrt(w * w + h * h)];
  345. }
  346. /**
  347. * Create an arrow using an svg element
  348. *
  349. * @param {number} w The length of the arrow
  350. * @param {number} a The angle for the arrow
  351. * @param {boolean} double True if this is a double-headed arrow
  352. * @param {string} offset 'X' for vertical arrow, 'Y' for horizontal
  353. * @param {number} dist Distance to translate in the offset direction
  354. * @return {N} The newly created arrow
  355. */
  356. public arrow(_w: number, _a: number, _double: boolean, _offset: string = '', _dist: number = 0): N {
  357. return null as N;
  358. }
  359. /**
  360. * Get the angle and width of a diagonal arrow, plus the x and y extension
  361. * past the content bounding box
  362. *
  363. * @return {Object} The angle, width, and x and y extentions
  364. */
  365. public arrowData(): {a: number, W: number, x: number, y: number} {
  366. const [p, t] = [this.padding, this.thickness];
  367. const r = t * (this.arrowhead.x + Math.max(1, this.arrowhead.dx));
  368. const {h, d, w} = this.childNodes[0].getBBox();
  369. const H = h + d;
  370. const R = Math.sqrt(H * H + w * w);
  371. const x = Math.max(p, r * w / R);
  372. const y = Math.max(p, r * H / R);
  373. const [a, W] = this.getArgMod(w + 2 * x, H + 2 * y);
  374. return {a, W, x, y};
  375. }
  376. /**
  377. * Get the angle and width for a diagonal arrow
  378. *
  379. * @return {[number, number]} The angle and width
  380. */
  381. public arrowAW(): [number, number] {
  382. const {h, d, w} = this.childNodes[0].getBBox();
  383. const [T, R, B, L] = this.TRBL;
  384. return this.getArgMod(L + w + R, T + h + d + B);
  385. }
  386. /********************************************************/
  387. /**
  388. * Create an unattached msqrt wrapper to render the 'radical' notation.
  389. * We replace the inferred mrow of the msqrt with the one from the menclose
  390. * but without changing the parent pointer, so as not to detach it from
  391. * the menclose (which would desrtoy the original MathML tree).
  392. *
  393. * @param {W} child The inferred mrow that is the child of this menclose
  394. * @return {S} The newly created (but detached) msqrt wrapper
  395. */
  396. public createMsqrt(child: W): S {
  397. const mmlFactory = (this.node as AbstractMmlNode).factory;
  398. const mml = mmlFactory.create('msqrt');
  399. mml.inheritAttributesFrom(this.node);
  400. mml.childNodes[0] = child.node;
  401. const node = this.wrap(mml) as S;
  402. node.parent = this;
  403. return node;
  404. }
  405. /**
  406. * @return {number[]} The differences between the msqrt bounding box
  407. * and its child bounding box (i.e., the extra space
  408. * created by the radical symbol).
  409. */
  410. public sqrtTRBL(): [number, number, number, number] {
  411. const bbox = this.msqrt.getBBox();
  412. const cbox = this.msqrt.childNodes[0].getBBox();
  413. return [bbox.h - cbox.h, 0, bbox.d - cbox.d, bbox.w - cbox.w];
  414. }
  415. };
  416. }