Notation.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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 utilities for notations for menclose elements
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {AnyWrapper} from './Wrapper.js';
  23. import {CommonMenclose} from './Wrappers/menclose.js';
  24. /*****************************************************************/
  25. export const ARROWX = 4, ARROWDX = 1, ARROWY = 2; // default relative arrowhead values
  26. export const THICKNESS = .067; // default rule thickness
  27. export const PADDING = .2; // default padding
  28. export const SOLID = THICKNESS + 'em solid'; // a solid border
  29. /*****************************************************************/
  30. /**
  31. * Shorthand for CommonMenclose
  32. */
  33. export type Menclose = CommonMenclose<any, any, any>;
  34. /**
  35. * Top, right, bottom, left padding data
  36. */
  37. export type PaddingData = [number, number, number, number];
  38. /**
  39. * The functions used for notation definitions
  40. *
  41. * @templare N The DOM node class
  42. */
  43. export type Renderer<W extends AnyWrapper, N> = (node: W, child: N) => void;
  44. export type BBoxExtender<W extends AnyWrapper> = (node: W) => PaddingData;
  45. export type BBoxBorder<W extends AnyWrapper> = (node: W) => PaddingData;
  46. export type Initializer<W extends AnyWrapper> = (node: W) => void;
  47. /**
  48. * The definition of a notation
  49. *
  50. * @template W The menclose wrapper class
  51. * @templare N The DOM node class
  52. */
  53. export type NotationDef<W extends AnyWrapper, N> = {
  54. renderer: Renderer<W, N>; // renders the DOM nodes for the notation
  55. bbox: BBoxExtender<W>; // gives the offsets to the child bounding box: [top, right, bottom, left]
  56. border?: BBoxBorder<W>; // gives the amount of the bbox offset that is due to borders on the child
  57. renderChild?: boolean; // true if the notation is used to render the child directly (e.g., radical)
  58. init?: Initializer<W>; // function to be called during wrapper construction
  59. remove?: string; // list of notations that are suppressed by this one
  60. };
  61. /**
  62. * For defining notation maps
  63. *
  64. * @template W The menclose wrapper class
  65. * @templare N The DOM node class
  66. */
  67. export type DefPair<W extends AnyWrapper, N> = [string, NotationDef<W, N>];
  68. export type DefList<W extends AnyWrapper, N> = Map<string, NotationDef<W, N>>;
  69. export type DefPairF<T, W extends AnyWrapper, N> = (name: T) => DefPair<W, N>;
  70. /**
  71. * The list of notations for an menclose element
  72. *
  73. * @template W The menclose wrapper class
  74. * @templare N The DOM node class
  75. */
  76. export type List<W extends AnyWrapper, N> = {[notation: string]: NotationDef<W, N>};
  77. /*****************************************************************/
  78. /**
  79. * The names and indices of sides for borders, padding, etc.
  80. */
  81. export const sideIndex = {top: 0, right: 1, bottom: 2, left: 3};
  82. export type Side = keyof typeof sideIndex;
  83. export const sideNames = Object.keys(sideIndex) as Side[];
  84. /**
  85. * Common BBox and Border functions
  86. */
  87. export const fullBBox = ((node) => new Array(4).fill(node.thickness + node.padding)) as BBoxExtender<Menclose>;
  88. export const fullPadding = ((node) => new Array(4).fill(node.padding)) as BBoxExtender<Menclose>;
  89. export const fullBorder = ((node) => new Array(4).fill(node.thickness)) as BBoxBorder<Menclose>;
  90. /*****************************************************************/
  91. /**
  92. * The length of an arrowhead
  93. */
  94. export const arrowHead = (node: Menclose) => {
  95. return Math.max(node.padding, node.thickness * (node.arrowhead.x + node.arrowhead.dx + 1));
  96. };
  97. /**
  98. * Adjust short bbox for tall arrow heads
  99. */
  100. export const arrowBBoxHD = (node: Menclose, TRBL: PaddingData) => {
  101. if (node.childNodes[0]) {
  102. const {h, d} = node.childNodes[0].getBBox();
  103. TRBL[0] = TRBL[2] = Math.max(0, node.thickness * node.arrowhead.y - (h + d) / 2);
  104. }
  105. return TRBL;
  106. };
  107. /**
  108. * Adjust thin bbox for wide arrow heads
  109. */
  110. export const arrowBBoxW = (node: Menclose, TRBL: PaddingData) => {
  111. if (node.childNodes[0]) {
  112. const {w} = node.childNodes[0].getBBox();
  113. TRBL[1] = TRBL[3] = Math.max(0, node.thickness * node.arrowhead.y - w / 2);
  114. }
  115. return TRBL;
  116. };
  117. /**
  118. * The data for horizontal and vertical arrow notations
  119. * [angle, double, isVertical, remove]
  120. */
  121. export const arrowDef = {
  122. up: [-Math.PI / 2, false, true, 'verticalstrike'],
  123. down: [ Math.PI / 2, false, true, 'verticakstrike'],
  124. right: [ 0, false, false, 'horizontalstrike'],
  125. left: [ Math.PI, false, false, 'horizontalstrike'],
  126. updown: [ Math.PI / 2, true, true, 'verticalstrike uparrow downarrow'],
  127. leftright: [ 0, true, false, 'horizontalstrike leftarrow rightarrow']
  128. } as {[name: string]: [number, boolean, boolean, string]};
  129. /**
  130. * The data for diagonal arrow notations
  131. * [c, pi, double, remove]
  132. */
  133. export const diagonalArrowDef = {
  134. updiagonal: [-1, 0, false, 'updiagonalstrike northeastarrow'],
  135. northeast: [-1, 0, false, 'updiagonalstrike updiagonalarrow'],
  136. southeast: [ 1, 0, false, 'downdiagonalstrike'],
  137. northwest: [ 1, Math.PI, false, 'downdiagonalstrike'],
  138. southwest: [-1, Math.PI, false, 'updiagonalstrike'],
  139. northeastsouthwest: [-1, 0, true, 'updiagonalstrike northeastarrow updiagonalarrow southwestarrow'],
  140. northwestsoutheast: [ 1, 0, true, 'downdiagonalstrike northwestarrow southeastarrow']
  141. } as {[name: string]: [number, number, boolean, string]};
  142. /**
  143. * The BBox functions for horizontal and vertical arrows
  144. */
  145. export const arrowBBox = {
  146. up: (node) => arrowBBoxW(node, [arrowHead(node), 0, node.padding, 0]),
  147. down: (node) => arrowBBoxW(node, [node.padding, 0, arrowHead(node), 0]),
  148. right: (node) => arrowBBoxHD(node, [0, arrowHead(node), 0, node.padding]),
  149. left: (node) => arrowBBoxHD(node, [0, node.padding, 0, arrowHead(node)]),
  150. updown: (node) => arrowBBoxW(node, [arrowHead(node), 0, arrowHead(node), 0]),
  151. leftright: (node) => arrowBBoxHD(node, [0, arrowHead(node), 0, arrowHead(node)])
  152. } as {[name: string]: BBoxExtender<Menclose>};
  153. /*****************************************************************/
  154. /**
  155. * @param {Renderer} render The function for adding the border to the node
  156. * @return {string => DefPair} The function returingn the notation definition
  157. * for the notation having a line on the given side
  158. */
  159. export const CommonBorder = function<W extends Menclose, N>(render: Renderer<W, N>): DefPairF<Side, W, N> {
  160. /**
  161. * @param {string} side The side on which a border should appear
  162. * @return {DefPair} The notation definition for the notation having a line on the given side
  163. */
  164. return (side: Side) => {
  165. const i = sideIndex[side];
  166. return [side, {
  167. //
  168. // Add the border to the main child object
  169. //
  170. renderer: render,
  171. //
  172. // Indicate the extra space on the given side
  173. //
  174. bbox: (node) => {
  175. const bbox = [0, 0, 0, 0] as PaddingData;
  176. bbox[i] = node.thickness + node.padding;
  177. return bbox;
  178. },
  179. //
  180. // Indicate the border on the given side
  181. //
  182. border: (node) => {
  183. const bbox = [0, 0, 0, 0] as PaddingData;
  184. bbox[i] = node.thickness;
  185. return bbox;
  186. }
  187. }];
  188. };
  189. };
  190. /**
  191. * @param {Renderer} render The function for adding the borders to the node
  192. * @return {(sring, Side, Side) => DefPair} The function returning the notation definition
  193. * for the notation having lines on two sides
  194. */
  195. export const CommonBorder2 = function<W extends Menclose, N>(render: Renderer<W, N>):
  196. (name: string, side1: Side, side2: Side) => DefPair<W, N> {
  197. /**
  198. * @param {string} name The name of the notation to define
  199. * @param {Side} side1 The first side to get a border
  200. * @param {Side} side2 The second side to get a border
  201. * @return {DefPair} The notation definition for the notation having lines on two sides
  202. */
  203. return (name: string, side1: Side, side2: Side) => {
  204. const i1 = sideIndex[side1];
  205. const i2 = sideIndex[side2];
  206. return [name, {
  207. //
  208. // Add the border along the given sides
  209. //
  210. renderer: render,
  211. //
  212. // Mark the extra space along the two sides
  213. //
  214. bbox: (node) => {
  215. const t = node.thickness + node.padding;
  216. const bbox = [0, 0, 0, 0] as PaddingData;
  217. bbox[i1] = bbox[i2] = t;
  218. return bbox;
  219. },
  220. //
  221. // Indicate the border on the two sides
  222. //
  223. border: (node) => {
  224. const bbox = [0, 0, 0, 0] as PaddingData;
  225. bbox[i1] = bbox[i2] = node.thickness;
  226. return bbox;
  227. },
  228. //
  229. // Remove the single side notations, if present
  230. //
  231. remove: side1 + ' ' + side2
  232. }];
  233. };
  234. };
  235. /*****************************************************************/
  236. /**
  237. * @param {string => Renderer} render The function for adding the strike to the node
  238. * @return {string => DefPair} The function returning the notation definition for the diagonal strike
  239. */
  240. export const CommonDiagonalStrike = function<W extends Menclose, N>(render: (sname: string) => Renderer<W, N>):
  241. DefPairF<string, W, N> {
  242. /**
  243. * @param {string} name The name of the diagonal strike to define
  244. * @return {DefPair} The notation definition for the diagonal strike
  245. */
  246. return (name: string) => {
  247. const cname = 'mjx-' + name.charAt(0) + 'strike';
  248. return [name + 'diagonalstrike', {
  249. //
  250. // Find the angle and width from the bounding box size and create the diagonal line
  251. //
  252. renderer: render(cname),
  253. //
  254. // Add padding all around
  255. //
  256. bbox: fullBBox
  257. }];
  258. };
  259. };
  260. /*****************************************************************/
  261. /**
  262. * @param {Renderer} render The function to add the arrow to the node
  263. * @return {string => DefPair} The funciton returning the notation definition for the diagonal arrow
  264. */
  265. export const CommonDiagonalArrow = function<W extends Menclose, N>(render: Renderer<W, N>): DefPairF<string, W, N> {
  266. /**
  267. * @param {string} name The name of the diagonal arrow to define
  268. * @return {DefPair} The notation definition for the diagonal arrow
  269. */
  270. return (name: string) => {
  271. const [c, pi, double, remove] = diagonalArrowDef[name];
  272. return [name + 'arrow', {
  273. //
  274. // Find the angle and width from the bounding box size and create
  275. // the arrow from them and the other arrow data
  276. //
  277. renderer: (node, _child) => {
  278. const [a, W] = node.arrowAW();
  279. const arrow = node.arrow(W, c * (a - pi), double);
  280. render(node, arrow);
  281. },
  282. //
  283. // Add space for the arrowhead all around
  284. //
  285. bbox: (node) => {
  286. const {a, x, y} = node.arrowData();
  287. const [ax, ay, adx] = [node.arrowhead.x, node.arrowhead.y, node.arrowhead.dx];
  288. const [b, ar] = node.getArgMod(ax + adx, ay);
  289. const dy = y + (b > a ? node.thickness * ar * Math.sin(b - a) : 0);
  290. const dx = x + (b > Math.PI / 2 - a ? node.thickness * ar * Math.sin(b + a - Math.PI / 2) : 0);
  291. return [dy, dx, dy, dx];
  292. },
  293. //
  294. // Remove redundant notations
  295. //
  296. remove: remove
  297. }];
  298. };
  299. };
  300. /**
  301. * @param {Renderer} render The function to add the arrow to the node
  302. * @return {string => DefPair} The function returning the notation definition for the arrow
  303. */
  304. export const CommonArrow = function<W extends Menclose, N>(render: Renderer<W, N>): DefPairF<string, W, N> {
  305. /**
  306. * @param {string} name The name of the horizontal or vertical arrow to define
  307. * @return {DefPair} The notation definition for the arrow
  308. */
  309. return (name: string) => {
  310. const [angle, double, isVertical, remove] = arrowDef[name];
  311. return [name + 'arrow', {
  312. //
  313. // Get the arrow height and depth from the bounding box and the arrow direction
  314. // then create the arrow from that and the other data
  315. //
  316. renderer: (node, _child) => {
  317. const {w, h, d} = node.getBBox();
  318. const [W, offset] = (isVertical ? [h + d, 'X'] : [w, 'Y']);
  319. const dd = node.getOffset(offset);
  320. const arrow = node.arrow(W, angle, double, offset, dd);
  321. render(node, arrow);
  322. },
  323. //
  324. // Add the padding to the proper sides
  325. //
  326. bbox: arrowBBox[name],
  327. //
  328. // Remove redundant notations
  329. //
  330. remove: remove
  331. }];
  332. };
  333. };