temml 0.13.3 → 0.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/temml.mjs CHANGED
@@ -68,9 +68,17 @@ ParseError.prototype.__proto__ = Error.prototype;
68
68
  */
69
69
 
70
70
  /**
71
- * Provide a default value if a setting is undefined
71
+ * Look up `key` on a user-supplied `options` object and return its value, or
72
+ * `defaultIfUndefined` if the setting is absent. Reads via
73
+ * `Object.prototype.hasOwnProperty.call` rather than plain property access,
74
+ * so a key that only exists because `Object.prototype` was polluted
75
+ * elsewhere is never mistaken for a setting the caller actually provided.
72
76
  */
73
- const deflt = function(setting, defaultIfUndefined) {
77
+ const deflt = function(options, key, defaultIfUndefined) {
78
+ if (!Object.prototype.hasOwnProperty.call(options, key)) {
79
+ return defaultIfUndefined;
80
+ }
81
+ const setting = options[key];
74
82
  return setting === undefined ? defaultIfUndefined : setting;
75
83
  };
76
84
 
@@ -188,6 +196,27 @@ const smalls = "acegıȷmnopqrsuvwxyzαγεηικμνοπρςστυχωϕ𝐚
188
196
  */
189
197
 
190
198
 
199
+ const FORBIDDEN_MACRO_KEYS = ["__proto__", "prototype", "constructor"];
200
+
201
+ // Sanitize a user-supplied macros object: strip its prototype (so later
202
+ // bracket-notation writes, e.g. from \gdef, can never reach Object.prototype)
203
+ // and delete any own key that could be used to pollute it. This mutates
204
+ // `macros` in place and returns the same reference, rather than a copy,
205
+ // because callers such as auto-render.js rely on that identity to share
206
+ // \gdef'd macros across multiple render calls that pass the same options
207
+ // object.
208
+ const sanitizeMacros = (macros) => {
209
+ if (macros == null || typeof macros !== "object") {
210
+ return Object.create(null);
211
+ }
212
+ for (const key of FORBIDDEN_MACRO_KEYS) {
213
+ if (Object.prototype.hasOwnProperty.call(macros, key)) {
214
+ delete macros[key];
215
+ }
216
+ }
217
+ return Object.setPrototypeOf(macros, null);
218
+ };
219
+
191
220
  /**
192
221
  * The main Settings object
193
222
  */
@@ -195,24 +224,29 @@ class Settings {
195
224
  constructor(options) {
196
225
  // allow null options
197
226
  options = options || {};
198
- this.displayMode = deflt(options.displayMode, false); // boolean
199
- this.annotate = deflt(options.annotate, false); // boolean
200
- this.leqno = deflt(options.leqno, false); // boolean
201
- this.throwOnError = deflt(options.throwOnError, false); // boolean
202
- this.errorColor = deflt(options.errorColor, "#b22222"); // string
203
- this.macros = options.macros || {};
204
- this.wrap = deflt(options.wrap, "none"); // "none" | "tex" | "="
205
- this.xml = deflt(options.xml, false); // boolean
206
- this.colorIsTextColor = deflt(options.colorIsTextColor, false); // boolean
207
- this.strict = deflt(options.strict, false); // boolean
208
- this.trust = deflt(options.trust, false); // trust context. See html.js.
209
- this.maxSize = (options.maxSize === undefined
210
- ? [Infinity, Infinity]
211
- : Array.isArray(options.maxSize)
227
+ this.displayMode = deflt(options, "displayMode", false); // boolean
228
+ this.annotate = deflt(options, "annotate", false); // boolean
229
+ this.leqno = deflt(options, "leqno", false); // boolean
230
+ this.throwOnError = deflt(options, "throwOnError", false); // boolean
231
+ this.errorColor = deflt(options, "errorColor", "#b22222"); // string
232
+ this.macros = sanitizeMacros(
233
+ Object.prototype.hasOwnProperty.call(options, "macros") ? options.macros : undefined
234
+ );
235
+ this.wrap = deflt(options, "wrap", "none"); // "none" | "tex" | "="
236
+ this.xml = deflt(options, "xml", false); // boolean
237
+ this.colorIsTextColor = deflt(options, "colorIsTextColor", false); // boolean
238
+ this.strict = deflt(options, "strict", false); // boolean
239
+ this.trust = deflt(options, "trust", false); // trust context. See html.js.
240
+ const maxSize = Object.prototype.hasOwnProperty.call(options, "maxSize")
212
241
  ? options.maxSize
242
+ : undefined;
243
+ this.maxSize = (maxSize === undefined
244
+ ? [Infinity, Infinity]
245
+ : Array.isArray(maxSize)
246
+ ? maxSize
213
247
  : [Infinity, Infinity]
214
248
  );
215
- this.maxExpand = Math.max(0, deflt(options.maxExpand, 1000)); // number
249
+ this.maxExpand = Math.max(0, deflt(options, "maxExpand", 1000)); // number
216
250
  this.wrapDelimiterPairs = true; // boolean
217
251
  }
218
252
 
@@ -785,7 +819,7 @@ const estimatedWidth = node => {
785
819
  };
786
820
 
787
821
  const stretchyCodePoint = {
788
- widehat: "^",
822
+ widehat: "\u02c6", // Shorter than \u005e, which is appropriate for an accent.
789
823
  widecheck: "ˇ",
790
824
  widetilde: "~",
791
825
  wideparen: "⏜", // \u23dc
@@ -848,19 +882,30 @@ const mathMLnode = function(label) {
848
882
  return node
849
883
  };
850
884
 
851
- const crookedWides = ["\\widetilde", "\\widehat", "\\widecheck", "\\utilde"];
885
+ const wideHats = ["\\widehat", "\\widecheck"];
852
886
 
853
- // TODO: Remove when Chromium stretches \widetilde & \widehat
887
+ // TODO: Remove if/when Chromium stretches \widetilde & \widehat
854
888
  const accentNode = (group) => {
855
889
  const mo = mathMLnode(group.label);
856
- if (crookedWides.includes(group.label)) {
890
+ if (group.label.indexOf("tilde") > -1) {
857
891
  const width = estimatedWidth(group.base);
858
892
  if (1 < width && width < 1.6) {
859
- mo.classes.push("tml-crooked-2");
893
+ mo.classes.push("tml-tilde-2");
860
894
  } else if (1.6 <= width && width < 2.5) {
861
- mo.classes.push("tml-crooked-3");
895
+ mo.classes.push("tml-tilde-3");
862
896
  } else if (2.5 <= width) {
863
- mo.classes.push("tml-crooked-4");
897
+ mo.classes.push("tml-tilde-4");
898
+ }
899
+ } else if (wideHats.includes(group.label)) {
900
+ const width = estimatedWidth(group.base);
901
+ if (0 < width && width <= 1) {
902
+ mo.classes.push("tml-hat-1");
903
+ } else if (1 < width && width < 1.6) {
904
+ mo.classes.push("tml-hat-2");
905
+ } else if (1.6 <= width && width < 2.5) {
906
+ mo.classes.push("tml-hat-3");
907
+ } else if (2.5 <= width) {
908
+ mo.classes.push("tml-hat-4");
864
909
  }
865
910
  }
866
911
  return mo
@@ -901,22 +946,26 @@ const NON_ATOMS = {
901
946
  textord: 1
902
947
  };
903
948
 
904
- const symbols = {
905
- math: {},
906
- text: {}
907
- };
949
+ const symbols = Object.create(null);
950
+ symbols.math = Object.create(null);
951
+ symbols.text = Object.create(null);
908
952
 
909
- /** `acceptUnicodeChar = true` is only applicable if `replace` is set. */
910
953
  function defineSymbol(mode, group, replace, name, acceptUnicodeChar) {
911
- symbols[mode][name] = { group, replace };
954
+ if (mode !== "math" && mode !== "text") {
955
+ throw new TypeError(`Invalid mode: ${mode} called in defineSymbol`);
956
+ }
957
+
958
+ const entry = { group, replace };
959
+ symbols[mode][name] = entry;
912
960
 
961
+ /** `acceptUnicodeChar = true` is only applicable if `replace` is set. */
913
962
  if (acceptUnicodeChar && replace) {
914
- symbols[mode][replace] = symbols[mode][name];
963
+ symbols[mode][replace] = entry;
915
964
  }
916
965
  }
917
966
 
918
967
  // Some abbreviations for commonly used strings.
919
- // This helps minify the code, and also spotting typos using jshint.
968
+ // This helps minify the code, and also spotting typos via linter.
920
969
 
921
970
  // modes:
922
971
  const math = "math";
@@ -1769,7 +1818,7 @@ defineSymbol(math, accent, "\u007e", "\\tilde");
1769
1818
  defineSymbol(math, accent, "\u203e", "\\bar");
1770
1819
  defineSymbol(math, accent, "\u02d8", "\\breve");
1771
1820
  defineSymbol(math, accent, "\u02c7", "\\check");
1772
- defineSymbol(math, accent, "\u005e", "\\hat");
1821
+ defineSymbol(math, accent, "\u02c6", "\\hat");
1773
1822
  defineSymbol(math, accent, "\u2192", "\\vec");
1774
1823
  defineSymbol(math, accent, "\u02d9", "\\dot");
1775
1824
  defineSymbol(math, accent, "\u02da", "\\mathring");
@@ -3546,7 +3595,7 @@ const StyleLevel = {
3546
3595
  * `macros.js` exports this same dictionary again and makes it public.
3547
3596
  * `Parser.js` requires this dictionary via `macros.js`.
3548
3597
  */
3549
- const _macros = {};
3598
+ const _macros = Object.create(null);
3550
3599
 
3551
3600
  // This function might one day accept an additional argument and do more things.
3552
3601
  function defineMacro(name, body) {
@@ -11987,7 +12036,7 @@ class Namespace {
11987
12036
  * of initial (global-level) mappings, which will constantly change
11988
12037
  * according to any global/top-level `set`s done.
11989
12038
  */
11990
- constructor(builtins = {}, globalMacros = {}) {
12039
+ constructor(builtins = Object.create(null), globalMacros = Object.create(null)) {
11991
12040
  this.current = globalMacros;
11992
12041
  this.builtins = builtins;
11993
12042
  this.undefStack = [];
@@ -11997,7 +12046,7 @@ class Namespace {
11997
12046
  * Start a new nested group, affecting future local `set`s.
11998
12047
  */
11999
12048
  beginGroup() {
12000
- this.undefStack.push({});
12049
+ this.undefStack.push(Object.create(null));
12001
12050
  }
12002
12051
 
12003
12052
  /**
@@ -14195,7 +14244,7 @@ class Style {
14195
14244
  * https://mit-license.org/
14196
14245
  */
14197
14246
 
14198
- const version = "0.13.3";
14247
+ const version = "0.13.5";
14199
14248
 
14200
14249
  function postProcess(block) {
14201
14250
  const labelMap = {};
@@ -14516,7 +14565,7 @@ const renderMathInElement = function(elem, options) {
14516
14565
 
14517
14566
  // Enable sharing of global macros defined via `\gdef` between different
14518
14567
  // math elements within a single call to `renderMathInElement`.
14519
- optionsCopy.macros = optionsCopy.macros || {};
14568
+ optionsCopy.macros = optionsCopy.macros || Object.create(null);
14520
14569
 
14521
14570
  renderElem(elem, optionsCopy);
14522
14571
  postProcess(elem);
@@ -14595,7 +14644,7 @@ const generateParseTree = function(expression, options) {
14595
14644
  */
14596
14645
  const definePreamble = function(expression, options) {
14597
14646
  const settings = new Settings(options);
14598
- settings.macros = {};
14647
+ settings.macros = Object.create(null);
14599
14648
  if (!(typeof expression === "string" || expression instanceof String)) {
14600
14649
  throw new TypeError("Temml can only parse string typed expression")
14601
14650
  }
@@ -11,7 +11,7 @@
11
11
  * https://mit-license.org/
12
12
  */
13
13
 
14
- const version = "0.13.3";
14
+ const version = "0.13.5";
15
15
 
16
16
  function postProcess(block) {
17
17
  const labelMap = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.13.3",
3
+ "version": "0.13.5",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
package/src/Namespace.js CHANGED
@@ -15,7 +15,7 @@ export default class Namespace {
15
15
  * of initial (global-level) mappings, which will constantly change
16
16
  * according to any global/top-level `set`s done.
17
17
  */
18
- constructor(builtins = {}, globalMacros = {}) {
18
+ constructor(builtins = Object.create(null), globalMacros = Object.create(null)) {
19
19
  this.current = globalMacros;
20
20
  this.builtins = builtins;
21
21
  this.undefStack = [];
@@ -25,7 +25,7 @@ export default class Namespace {
25
25
  * Start a new nested group, affecting future local `set`s.
26
26
  */
27
27
  beginGroup() {
28
- this.undefStack.push({});
28
+ this.undefStack.push(Object.create(null));
29
29
  }
30
30
 
31
31
  /**
package/src/Settings.js CHANGED
@@ -5,6 +5,27 @@
5
5
 
6
6
  import * as utils from "./utils";
7
7
 
8
+ const FORBIDDEN_MACRO_KEYS = ["__proto__", "prototype", "constructor"];
9
+
10
+ // Sanitize a user-supplied macros object: strip its prototype (so later
11
+ // bracket-notation writes, e.g. from \gdef, can never reach Object.prototype)
12
+ // and delete any own key that could be used to pollute it. This mutates
13
+ // `macros` in place and returns the same reference, rather than a copy,
14
+ // because callers such as auto-render.js rely on that identity to share
15
+ // \gdef'd macros across multiple render calls that pass the same options
16
+ // object.
17
+ const sanitizeMacros = (macros) => {
18
+ if (macros == null || typeof macros !== "object") {
19
+ return Object.create(null);
20
+ }
21
+ for (const key of FORBIDDEN_MACRO_KEYS) {
22
+ if (Object.prototype.hasOwnProperty.call(macros, key)) {
23
+ delete macros[key];
24
+ }
25
+ }
26
+ return Object.setPrototypeOf(macros, null);
27
+ };
28
+
8
29
  /**
9
30
  * The main Settings object
10
31
  */
@@ -12,24 +33,29 @@ export default class Settings {
12
33
  constructor(options) {
13
34
  // allow null options
14
35
  options = options || {};
15
- this.displayMode = utils.deflt(options.displayMode, false); // boolean
16
- this.annotate = utils.deflt(options.annotate, false) // boolean
17
- this.leqno = utils.deflt(options.leqno, false); // boolean
18
- this.throwOnError = utils.deflt(options.throwOnError, false); // boolean
19
- this.errorColor = utils.deflt(options.errorColor, "#b22222"); // string
20
- this.macros = options.macros || {};
21
- this.wrap = utils.deflt(options.wrap, "none") // "none" | "tex" | "="
22
- this.xml = utils.deflt(options.xml, false); // boolean
23
- this.colorIsTextColor = utils.deflt(options.colorIsTextColor, false); // boolean
24
- this.strict = utils.deflt(options.strict, false); // boolean
25
- this.trust = utils.deflt(options.trust, false); // trust context. See html.js.
26
- this.maxSize = (options.maxSize === undefined
27
- ? [Infinity, Infinity]
28
- : Array.isArray(options.maxSize)
36
+ this.displayMode = utils.deflt(options, "displayMode", false); // boolean
37
+ this.annotate = utils.deflt(options, "annotate", false) // boolean
38
+ this.leqno = utils.deflt(options, "leqno", false); // boolean
39
+ this.throwOnError = utils.deflt(options, "throwOnError", false); // boolean
40
+ this.errorColor = utils.deflt(options, "errorColor", "#b22222"); // string
41
+ this.macros = sanitizeMacros(
42
+ Object.prototype.hasOwnProperty.call(options, "macros") ? options.macros : undefined
43
+ );
44
+ this.wrap = utils.deflt(options, "wrap", "none") // "none" | "tex" | "="
45
+ this.xml = utils.deflt(options, "xml", false); // boolean
46
+ this.colorIsTextColor = utils.deflt(options, "colorIsTextColor", false); // boolean
47
+ this.strict = utils.deflt(options, "strict", false); // boolean
48
+ this.trust = utils.deflt(options, "trust", false); // trust context. See html.js.
49
+ const maxSize = Object.prototype.hasOwnProperty.call(options, "maxSize")
29
50
  ? options.maxSize
51
+ : undefined;
52
+ this.maxSize = (maxSize === undefined
53
+ ? [Infinity, Infinity]
54
+ : Array.isArray(maxSize)
55
+ ? maxSize
30
56
  : [Infinity, Infinity]
31
57
  )
32
- this.maxExpand = Math.max(0, utils.deflt(options.maxExpand, 1000)); // number
58
+ this.maxExpand = Math.max(0, utils.deflt(options, "maxExpand", 1000)); // number
33
59
  this.wrapDelimiterPairs = true; // boolean
34
60
  }
35
61
 
@@ -256,7 +256,7 @@ export const renderMathInElement = function(elem, options) {
256
256
 
257
257
  // Enable sharing of global macros defined via `\gdef` between different
258
258
  // math elements within a single call to `renderMathInElement`.
259
- optionsCopy.macros = optionsCopy.macros || {};
259
+ optionsCopy.macros = optionsCopy.macros || Object.create(null);
260
260
 
261
261
  renderElem(elem, optionsCopy);
262
262
  postProcess(elem);
@@ -3,7 +3,7 @@
3
3
  * `macros.js` exports this same dictionary again and makes it public.
4
4
  * `Parser.js` requires this dictionary via `macros.js`.
5
5
  */
6
- export const _macros = {};
6
+ export const _macros = Object.create(null);
7
7
 
8
8
  // This function might one day accept an additional argument and do more things.
9
9
  export default function defineMacro(name, body) {
@@ -5,7 +5,7 @@
5
5
  * https://mit-license.org/
6
6
  */
7
7
 
8
- export const version = "0.13.3";
8
+ export const version = "0.13.5";
9
9
 
10
10
  export function postProcess(block) {
11
11
  const labelMap = {}
package/src/stretchy.js CHANGED
@@ -35,7 +35,7 @@ const estimatedWidth = node => {
35
35
  }
36
36
 
37
37
  const stretchyCodePoint = {
38
- widehat: "^",
38
+ widehat: "\u02c6", // Shorter than \u005e, which is appropriate for an accent.
39
39
  widecheck: "ˇ",
40
40
  widetilde: "~",
41
41
  wideparen: "⏜", // \u23dc
@@ -98,19 +98,30 @@ export const mathMLnode = function(label) {
98
98
  return node
99
99
  }
100
100
 
101
- const crookedWides = ["\\widetilde", "\\widehat", "\\widecheck", "\\utilde"]
101
+ const wideHats = ["\\widehat", "\\widecheck"];
102
102
 
103
- // TODO: Remove when Chromium stretches \widetilde & \widehat
103
+ // TODO: Remove if/when Chromium stretches \widetilde & \widehat
104
104
  export const accentNode = (group) => {
105
105
  const mo = mathMLnode(group.label)
106
- if (crookedWides.includes(group.label)) {
106
+ if (group.label.indexOf("tilde") > -1) {
107
107
  const width = estimatedWidth(group.base)
108
108
  if (1 < width && width < 1.6) {
109
- mo.classes.push("tml-crooked-2")
109
+ mo.classes.push("tml-tilde-2")
110
+ } else if (1.6 <= width && width < 2.5) {
111
+ mo.classes.push("tml-tilde-3")
112
+ } else if (2.5 <= width) {
113
+ mo.classes.push("tml-tilde-4")
114
+ }
115
+ } else if (wideHats.includes(group.label)) {
116
+ const width = estimatedWidth(group.base)
117
+ if (0 < width && width <= 1) {
118
+ mo.classes.push("tml-hat-1")
119
+ } else if (1 < width && width < 1.6) {
120
+ mo.classes.push("tml-hat-2")
110
121
  } else if (1.6 <= width && width < 2.5) {
111
- mo.classes.push("tml-crooked-3")
122
+ mo.classes.push("tml-hat-3")
112
123
  } else if (2.5 <= width) {
113
- mo.classes.push("tml-crooked-4")
124
+ mo.classes.push("tml-hat-4")
114
125
  }
115
126
  }
116
127
  return mo
package/src/symbols.js CHANGED
@@ -33,23 +33,27 @@ export const NON_ATOMS = {
33
33
  textord: 1
34
34
  };
35
35
 
36
- const symbols = {
37
- math: {},
38
- text: {}
39
- };
36
+ const symbols = Object.create(null);
37
+ symbols.math = Object.create(null);
38
+ symbols.text = Object.create(null);
40
39
  export default symbols;
41
40
 
42
- /** `acceptUnicodeChar = true` is only applicable if `replace` is set. */
43
41
  export function defineSymbol(mode, group, replace, name, acceptUnicodeChar) {
44
- symbols[mode][name] = { group, replace };
42
+ if (mode !== "math" && mode !== "text") {
43
+ throw new TypeError(`Invalid mode: ${mode} called in defineSymbol`);
44
+ }
45
+
46
+ const entry = { group, replace };
47
+ symbols[mode][name] = entry;
45
48
 
49
+ /** `acceptUnicodeChar = true` is only applicable if `replace` is set. */
46
50
  if (acceptUnicodeChar && replace) {
47
- symbols[mode][replace] = symbols[mode][name];
51
+ symbols[mode][replace] = entry;
48
52
  }
49
53
  }
50
54
 
51
55
  // Some abbreviations for commonly used strings.
52
- // This helps minify the code, and also spotting typos using jshint.
56
+ // This helps minify the code, and also spotting typos via linter.
53
57
 
54
58
  // modes:
55
59
  const math = "math";
@@ -902,7 +906,7 @@ defineSymbol(math, accent, "\u007e", "\\tilde");
902
906
  defineSymbol(math, accent, "\u203e", "\\bar");
903
907
  defineSymbol(math, accent, "\u02d8", "\\breve");
904
908
  defineSymbol(math, accent, "\u02c7", "\\check");
905
- defineSymbol(math, accent, "\u005e", "\\hat");
909
+ defineSymbol(math, accent, "\u02c6", "\\hat");
906
910
  defineSymbol(math, accent, "\u2192", "\\vec");
907
911
  defineSymbol(math, accent, "\u02d9", "\\dot");
908
912
  defineSymbol(math, accent, "\u02da", "\\mathring");
package/src/utils.js CHANGED
@@ -5,9 +5,17 @@
5
5
  */
6
6
 
7
7
  /**
8
- * Provide a default value if a setting is undefined
8
+ * Look up `key` on a user-supplied `options` object and return its value, or
9
+ * `defaultIfUndefined` if the setting is absent. Reads via
10
+ * `Object.prototype.hasOwnProperty.call` rather than plain property access,
11
+ * so a key that only exists because `Object.prototype` was polluted
12
+ * elsewhere is never mistaken for a setting the caller actually provided.
9
13
  */
10
- export const deflt = function(setting, defaultIfUndefined) {
14
+ export const deflt = function(options, key, defaultIfUndefined) {
15
+ if (!Object.prototype.hasOwnProperty.call(options, key)) {
16
+ return defaultIfUndefined;
17
+ }
18
+ const setting = options[key];
11
19
  return setting === undefined ? defaultIfUndefined : setting;
12
20
  };
13
21
 
package/temml.js CHANGED
@@ -84,7 +84,7 @@ const generateParseTree = function(expression, options) {
84
84
  */
85
85
  const definePreamble = function(expression, options) {
86
86
  const settings = new Settings(options);
87
- settings.macros = {};
87
+ settings.macros = Object.create(null);
88
88
  if (!(typeof expression === "string" || expression instanceof String)) {
89
89
  throw new TypeError("Temml can only parse string typed expression")
90
90
  }