temml 0.10.19 → 0.10.21

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
@@ -2070,20 +2070,22 @@ const consolidateNumbers = expression => {
2070
2070
  * Wrap the given array of nodes in an <mrow> node if needed, i.e.,
2071
2071
  * unless the array has length 1. Always returns a single node.
2072
2072
  */
2073
- const makeRow = function(body) {
2073
+ const makeRow = function(body, semisimple = false) {
2074
2074
  if (body.length === 1 && !(body[0] instanceof DocumentFragment)) {
2075
2075
  return body[0];
2076
- } else {
2076
+ } else if (!semisimple) {
2077
2077
  // Suppress spacing on <mo> nodes at both ends of the row.
2078
2078
  if (body[0] instanceof MathNode && body[0].type === "mo" && !body[0].attributes.fence) {
2079
2079
  body[0].attributes.lspace = "0em";
2080
+ body[0].attributes.rspace = "0em";
2080
2081
  }
2081
2082
  const end = body.length - 1;
2082
2083
  if (body[end] instanceof MathNode && body[end].type === "mo" && !body[end].attributes.fence) {
2084
+ body[end].attributes.lspace = "0em";
2083
2085
  body[end].attributes.rspace = "0em";
2084
2086
  }
2085
- return new mathMLTree.MathNode("mrow", body);
2086
2087
  }
2088
+ return new mathMLTree.MathNode("mrow", body);
2087
2089
  };
2088
2090
 
2089
2091
  const isRel = item => {
@@ -2097,10 +2099,10 @@ const isRel = item => {
2097
2099
  * (1) Suppress spacing when an author wraps an operator w/braces, as in {=}.
2098
2100
  * (2) Suppress spacing between two adjacent relations.
2099
2101
  */
2100
- const buildExpression = function(expression, style, isOrdgroup) {
2101
- if (expression.length === 1) {
2102
+ const buildExpression = function(expression, style, semisimple = false) {
2103
+ if (!semisimple && expression.length === 1) {
2102
2104
  const group = buildGroup$1(expression[0], style);
2103
- if (isOrdgroup && group instanceof MathNode && group.type === "mo") {
2105
+ if (group instanceof MathNode && group.type === "mo") {
2104
2106
  // When TeX writers want to suppress spacing on an operator,
2105
2107
  // they often put the operator by itself inside braces.
2106
2108
  group.setAttribute("lspace", "0em");
@@ -2130,8 +2132,8 @@ const buildExpression = function(expression, style, isOrdgroup) {
2130
2132
  * Equivalent to buildExpression, but wraps the elements in an <mrow>
2131
2133
  * if there's more than one. Returns a single node instead of an array.
2132
2134
  */
2133
- const buildExpressionRow = function(expression, style, isOrdgroup) {
2134
- return makeRow(buildExpression(expression, style, isOrdgroup));
2135
+ const buildExpressionRow = function(expression, style, semisimple = false) {
2136
+ return makeRow(buildExpression(expression, style, semisimple), semisimple);
2135
2137
  };
2136
2138
 
2137
2139
  /**
@@ -2816,7 +2818,8 @@ function cdArrow(arrowChar, labels, parser) {
2816
2818
  const arrowGroup = {
2817
2819
  type: "ordgroup",
2818
2820
  mode: "math",
2819
- body: [leftLabel, sizedArrow, rightLabel]
2821
+ body: [leftLabel, sizedArrow, rightLabel],
2822
+ semisimple: true
2820
2823
  };
2821
2824
  return parser.callFunction("\\\\cdparent", [arrowGroup], []);
2822
2825
  }
@@ -3242,7 +3245,7 @@ defineFunction({
3242
3245
  allowedInText: true,
3243
3246
  argTypes: ["raw", "raw"]
3244
3247
  },
3245
- handler({ parser, token }, args, optArgs) {
3248
+ handler({ parser, breakOnTokenText, token }, args, optArgs) {
3246
3249
  const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string;
3247
3250
  let color = "";
3248
3251
  if (model) {
@@ -3252,15 +3255,8 @@ defineFunction({
3252
3255
  color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token);
3253
3256
  }
3254
3257
 
3255
- // Set macro \current@color in current namespace to store the current
3256
- // color, mimicking the behavior of color.sty.
3257
- // This is currently used just to correctly color a \right
3258
- // that follows a \color command.
3259
- parser.gullet.macros.set("\\current@color", color);
3260
-
3261
3258
  // Parse out the implicit body that should be colored.
3262
- // Since \color nodes should not be nested, break on \color.
3263
- const body = parser.parseExpression(true, "\\color");
3259
+ const body = parser.parseExpression(true, breakOnTokenText);
3264
3260
 
3265
3261
  return {
3266
3262
  type: "color",
@@ -3800,18 +3796,10 @@ defineFunction({
3800
3796
  argTypes: ["primitive"]
3801
3797
  },
3802
3798
  handler: (context, args) => {
3803
- // \left case below triggers parsing of \right in
3804
- // `const right = parser.parseFunction();`
3805
- // uses this return value.
3806
- const color = context.parser.gullet.macros.get("\\current@color");
3807
- if (color && typeof color !== "string") {
3808
- throw new ParseError("\\current@color set to non-string in \\right");
3809
- }
3810
3799
  return {
3811
3800
  type: "leftright-right",
3812
3801
  mode: context.parser.mode,
3813
- delim: checkDelimiter(args[0], context).text,
3814
- color // undefined if not set via \color
3802
+ delim: checkDelimiter(args[0], context).text
3815
3803
  };
3816
3804
  }
3817
3805
  });
@@ -3840,8 +3828,7 @@ defineFunction({
3840
3828
  mode: parser.mode,
3841
3829
  body,
3842
3830
  left: delim.text,
3843
- right: right.delim,
3844
- rightColor: right.color
3831
+ right: right.delim
3845
3832
  };
3846
3833
  },
3847
3834
  mathmlBuilder: (group, style) => {
@@ -3864,7 +3851,6 @@ defineFunction({
3864
3851
  if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
3865
3852
  rightNode.setAttribute("stretchy", "true");
3866
3853
  }
3867
- if (group.rightColor) { rightNode.style.color = group.rightColor; }
3868
3854
  inner.push(rightNode);
3869
3855
 
3870
3856
  return makeRow(inner);
@@ -3938,20 +3924,12 @@ const mathmlBuilder$8 = (group, style) => {
3938
3924
  node.style.borderBottom = "0.065em solid";
3939
3925
  break
3940
3926
  case "\\cancel":
3941
- node.style.background = `linear-gradient(to top left,
3942
- rgba(0,0,0,0) 0%,
3943
- rgba(0,0,0,0) calc(50% - 0.06em),
3944
- rgba(0,0,0,1) 50%,
3945
- rgba(0,0,0,0) calc(50% + 0.06em),
3946
- rgba(0,0,0,0) 100%);`;
3927
+ // We can't use an inline background-gradient. It does not work client-side.
3928
+ // So set a class and put the rule in the external CSS file.
3929
+ node.classes.push("tml-cancel");
3947
3930
  break
3948
3931
  case "\\bcancel":
3949
- node.style.background = `linear-gradient(to top right,
3950
- rgba(0,0,0,0) 0%,
3951
- rgba(0,0,0,0) calc(50% - 0.06em),
3952
- rgba(0,0,0,1) 50%,
3953
- rgba(0,0,0,0) calc(50% + 0.06em),
3954
- rgba(0,0,0,0) 100%);`;
3932
+ node.classes.push("tml-bcancel");
3955
3933
  break
3956
3934
  /*
3957
3935
  case "\\longdiv":
@@ -3998,18 +3976,7 @@ rgba(0,0,0,0) 100%);`;
3998
3976
  break
3999
3977
  }
4000
3978
  case "\\xcancel":
4001
- node.style.background = `linear-gradient(to top left,
4002
- rgba(0,0,0,0) 0%,
4003
- rgba(0,0,0,0) calc(50% - 0.06em),
4004
- rgba(0,0,0,1) 50%,
4005
- rgba(0,0,0,0) calc(50% + 0.06em),
4006
- rgba(0,0,0,0) 100%),
4007
- linear-gradient(to top right,
4008
- rgba(0,0,0,0) 0%,
4009
- rgba(0,0,0,0) calc(50% - 0.06em),
4010
- rgba(0,0,0,1) 50%,
4011
- rgba(0,0,0,0) calc(50% + 0.06em),
4012
- rgba(0,0,0,0) 100%);`;
3979
+ node.classes.push("tml-xcancel");
4013
3980
  break
4014
3981
  }
4015
3982
  if (group.backgroundColor) {
@@ -4207,7 +4174,7 @@ const getTag = (group, style, rowNum) => {
4207
4174
  if (tagContents) {
4208
4175
  // The author has written a \tag or a \notag in this row.
4209
4176
  if (tagContents.body) {
4210
- tag = buildExpressionRow(tagContents.body, style);
4177
+ tag = buildExpressionRow(tagContents.body, style, true);
4211
4178
  tag.classes = ["tml-tag"];
4212
4179
  } else {
4213
4180
  // \notag. Return an empty span.
@@ -4254,7 +4221,9 @@ function parseArray(
4254
4221
  parser.gullet.macros.set("\\cr", "\\\\\\relax");
4255
4222
  }
4256
4223
  if (addEqnNum) {
4257
- parser.gullet.macros.set("\\tag", "\\env@tag{\\text{#1}}");
4224
+ parser.gullet.macros.set("\\tag", "\\@ifstar\\envtag@literal\\envtag@paren");
4225
+ parser.gullet.macros.set("\\envtag@paren", "\\env@tag{{(\\text{#1})}}");
4226
+ parser.gullet.macros.set("\\envtag@literal", "\\env@tag{\\text{#1}}");
4258
4227
  parser.gullet.macros.set("\\notag", "\\env@notag");
4259
4228
  parser.gullet.macros.set("\\nonumber", "\\env@notag");
4260
4229
  }
@@ -4295,7 +4264,8 @@ function parseArray(
4295
4264
  cell = {
4296
4265
  type: "ordgroup",
4297
4266
  mode: parser.mode,
4298
- body: cell
4267
+ body: cell,
4268
+ semisimple: true
4299
4269
  };
4300
4270
  row.push(cell);
4301
4271
  const next = parser.fetch().text;
@@ -5128,7 +5098,7 @@ const mathmlBuilder$6 = (group, style) => {
5128
5098
  const mathGroup = buildGroup$1(group.body, newStyle);
5129
5099
 
5130
5100
  if (mathGroup.children.length === 0) { return mathGroup } // empty group, e.g., \mathrm{}
5131
- if (font === "boldsymbol" && ["mo", "mpadded"].includes(mathGroup.type)) {
5101
+ if (font === "boldsymbol" && ["mo", "mpadded", "mrow"].includes(mathGroup.type)) {
5132
5102
  mathGroup.style.fontWeight = "bold";
5133
5103
  return mathGroup
5134
5104
  }
@@ -6485,10 +6455,10 @@ defineFunction({
6485
6455
  },
6486
6456
  mathmlBuilder(group, style) {
6487
6457
  if (group.isCharacterBox) {
6488
- const inner = buildExpression(group.body, style);
6458
+ const inner = buildExpression(group.body, style, true);
6489
6459
  return inner[0]
6490
6460
  } else {
6491
- return buildExpressionRow(group.body, style, true)
6461
+ return buildExpressionRow(group.body, style)
6492
6462
  }
6493
6463
  }
6494
6464
  });
@@ -6508,6 +6478,13 @@ const ordTypes = ["textord", "mathord", "ordgroup", "close", "leftright"];
6508
6478
  // NOTE: Unlike most `builders`s, this one handles not only "op", but also
6509
6479
  // "supsub" since some of them (like \int) can affect super/subscripting.
6510
6480
 
6481
+ const setSpacing = node => {
6482
+ // The user wrote a \mathop{…} function. Change spacing from default to OP spacing.
6483
+ // The most likely spacing for an OP is a thin space per TeXbook p170.
6484
+ node.attributes.lspace = "0.1667em";
6485
+ node.attributes.rspace = "0.1667em";
6486
+ };
6487
+
6511
6488
  const mathmlBuilder$2 = (group, style) => {
6512
6489
  let node;
6513
6490
 
@@ -6519,9 +6496,11 @@ const mathmlBuilder$2 = (group, style) => {
6519
6496
  } else {
6520
6497
  node.setAttribute("movablelimits", "false");
6521
6498
  }
6499
+ if (group.fromMathOp) { setSpacing(node); }
6522
6500
  } else if (group.body) {
6523
6501
  // This is an operator with children. Add them.
6524
6502
  node = new MathNode("mo", buildExpression(group.body, style));
6503
+ if (group.fromMathOp) { setSpacing(node); }
6525
6504
  } else {
6526
6505
  // This is a text operator. Add all of the characters from the operator's name.
6527
6506
  node = new MathNode("mi", [new TextNode(group.name.slice(1))]);
@@ -6641,6 +6620,7 @@ defineFunction({
6641
6620
  limits: true,
6642
6621
  parentIsSupSub: false,
6643
6622
  symbol: isSymbol,
6623
+ fromMathOp: true,
6644
6624
  stack: false,
6645
6625
  name: isSymbol ? arr[0].text : null,
6646
6626
  body: isSymbol ? null : ordargument(body)
@@ -6974,7 +6954,7 @@ defineMacro("\\operatorname",
6974
6954
  defineFunctionBuilders({
6975
6955
  type: "ordgroup",
6976
6956
  mathmlBuilder(group, style) {
6977
- return buildExpressionRow(group.body, style, true);
6957
+ return buildExpressionRow(group.body, style, group.semisimple);
6978
6958
  }
6979
6959
  });
6980
6960
 
@@ -12003,6 +11983,8 @@ var unicodeSymbols = {
12003
11983
 
12004
11984
  /* eslint no-constant-condition:0 */
12005
11985
 
11986
+ const binLeftCancellers = ["bin", "op", "open", "punct", "rel"];
11987
+
12006
11988
  /**
12007
11989
  * This file contains the parser used to parse out a TeX expression from the
12008
11990
  * input. Since TeX isn't context-free, standard parsers don't work particularly
@@ -12772,8 +12754,7 @@ class Parser {
12772
12754
  body: expression,
12773
12755
  // A group formed by \begingroup...\endgroup is a semi-simple group
12774
12756
  // which doesn't affect spacing in math mode, i.e., is transparent.
12775
- // https://tex.stackexchange.com/questions/1930/when-should-one-
12776
- // use-begingroup-instead-of-bgroup
12757
+ // https://tex.stackexchange.com/questions/1930/
12777
12758
  semisimple: text === "\\begingroup" || undefined
12778
12759
  };
12779
12760
  } else {
@@ -12887,7 +12868,11 @@ class Parser {
12887
12868
  // Recognize base symbol
12888
12869
  let symbol;
12889
12870
  if (symbols[this.mode][text]) {
12890
- const group = symbols[this.mode][text].group;
12871
+ let group = symbols[this.mode][text].group;
12872
+ if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) {
12873
+ // Change from a binary operator to a unary (prefix) operator
12874
+ group = "open";
12875
+ }
12891
12876
  const loc = SourceLocation.range(nucleus);
12892
12877
  let s;
12893
12878
  if (Object.prototype.hasOwnProperty.call(ATOMS, group )) {
@@ -13157,7 +13142,7 @@ class Style {
13157
13142
  * https://mit-license.org/
13158
13143
  */
13159
13144
 
13160
- const version = "0.10.19";
13145
+ const version = "0.10.21";
13161
13146
 
13162
13147
  function postProcess(block) {
13163
13148
  const labelMap = {};
@@ -14,7 +14,7 @@
14
14
  * https://mit-license.org/
15
15
  */
16
16
 
17
- const version = "0.10.19";
17
+ const version = "0.10.21";
18
18
 
19
19
  function postProcess(block) {
20
20
  const labelMap = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.10.19",
3
+ "version": "0.10.21",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
package/src/Parser.js CHANGED
@@ -15,6 +15,8 @@ import { isDelimiter } from "./functions/delimsizing"
15
15
  import unicodeAccents from /*preval*/ "./unicodeAccents";
16
16
  import unicodeSymbols from /*preval*/ "./unicodeSymbols";
17
17
 
18
+ const binLeftCancellers = ["bin", "op", "open", "punct", "rel"];
19
+
18
20
  /**
19
21
  * This file contains the parser used to parse out a TeX expression from the
20
22
  * input. Since TeX isn't context-free, standard parsers don't work particularly
@@ -784,8 +786,7 @@ export default class Parser {
784
786
  body: expression,
785
787
  // A group formed by \begingroup...\endgroup is a semi-simple group
786
788
  // which doesn't affect spacing in math mode, i.e., is transparent.
787
- // https://tex.stackexchange.com/questions/1930/when-should-one-
788
- // use-begingroup-instead-of-bgroup
789
+ // https://tex.stackexchange.com/questions/1930/
789
790
  semisimple: text === "\\begingroup" || undefined
790
791
  };
791
792
  } else {
@@ -899,7 +900,11 @@ export default class Parser {
899
900
  // Recognize base symbol
900
901
  let symbol;
901
902
  if (symbols[this.mode][text]) {
902
- const group = symbols[this.mode][text].group;
903
+ let group = symbols[this.mode][text].group;
904
+ if (group === "bin" && binLeftCancellers.includes(this.prevAtomType)) {
905
+ // Change from a binary operator to a unary (prefix) operator
906
+ group = "open"
907
+ }
903
908
  const loc = SourceLocation.range(nucleus);
904
909
  let s;
905
910
  if (Object.prototype.hasOwnProperty.call(ATOMS, group )) {
@@ -135,20 +135,22 @@ const consolidateNumbers = expression => {
135
135
  * Wrap the given array of nodes in an <mrow> node if needed, i.e.,
136
136
  * unless the array has length 1. Always returns a single node.
137
137
  */
138
- export const makeRow = function(body) {
138
+ export const makeRow = function(body, semisimple = false) {
139
139
  if (body.length === 1 && !(body[0] instanceof DocumentFragment)) {
140
140
  return body[0];
141
- } else {
141
+ } else if (!semisimple) {
142
142
  // Suppress spacing on <mo> nodes at both ends of the row.
143
143
  if (body[0] instanceof MathNode && body[0].type === "mo" && !body[0].attributes.fence) {
144
144
  body[0].attributes.lspace = "0em"
145
+ body[0].attributes.rspace = "0em"
145
146
  }
146
147
  const end = body.length - 1
147
148
  if (body[end] instanceof MathNode && body[end].type === "mo" && !body[end].attributes.fence) {
149
+ body[end].attributes.lspace = "0em"
148
150
  body[end].attributes.rspace = "0em"
149
151
  }
150
- return new mathMLTree.MathNode("mrow", body);
151
152
  }
153
+ return new mathMLTree.MathNode("mrow", body);
152
154
  };
153
155
 
154
156
  const isRel = item => {
@@ -162,10 +164,10 @@ const isRel = item => {
162
164
  * (1) Suppress spacing when an author wraps an operator w/braces, as in {=}.
163
165
  * (2) Suppress spacing between two adjacent relations.
164
166
  */
165
- export const buildExpression = function(expression, style, isOrdgroup) {
166
- if (expression.length === 1) {
167
+ export const buildExpression = function(expression, style, semisimple = false) {
168
+ if (!semisimple && expression.length === 1) {
167
169
  const group = buildGroup(expression[0], style);
168
- if (isOrdgroup && group instanceof MathNode && group.type === "mo") {
170
+ if (group instanceof MathNode && group.type === "mo") {
169
171
  // When TeX writers want to suppress spacing on an operator,
170
172
  // they often put the operator by itself inside braces.
171
173
  group.setAttribute("lspace", "0em");
@@ -195,8 +197,8 @@ export const buildExpression = function(expression, style, isOrdgroup) {
195
197
  * Equivalent to buildExpression, but wraps the elements in an <mrow>
196
198
  * if there's more than one. Returns a single node instead of an array.
197
199
  */
198
- export const buildExpressionRow = function(expression, style, isOrdgroup) {
199
- return makeRow(buildExpression(expression, style, isOrdgroup));
200
+ export const buildExpressionRow = function(expression, style, semisimple = false) {
201
+ return makeRow(buildExpression(expression, style, semisimple), semisimple);
200
202
  };
201
203
 
202
204
  /**
@@ -44,7 +44,7 @@ const getTag = (group, style, rowNum) => {
44
44
  if (tagContents) {
45
45
  // The author has written a \tag or a \notag in this row.
46
46
  if (tagContents.body) {
47
- tag = mml.buildExpressionRow(tagContents.body, style)
47
+ tag = mml.buildExpressionRow(tagContents.body, style, true)
48
48
  tag.classes = ["tml-tag"]
49
49
  } else {
50
50
  // \notag. Return an empty span.
@@ -91,7 +91,9 @@ function parseArray(
91
91
  parser.gullet.macros.set("\\cr", "\\\\\\relax");
92
92
  }
93
93
  if (addEqnNum) {
94
- parser.gullet.macros.set("\\tag", "\\env@tag{\\text{#1}}");
94
+ parser.gullet.macros.set("\\tag", "\\@ifstar\\envtag@literal\\envtag@paren")
95
+ parser.gullet.macros.set("\\envtag@paren", "\\env@tag{{(\\text{#1})}}");
96
+ parser.gullet.macros.set("\\envtag@literal", "\\env@tag{\\text{#1}}")
95
97
  parser.gullet.macros.set("\\notag", "\\env@notag");
96
98
  parser.gullet.macros.set("\\nonumber", "\\env@notag")
97
99
  }
@@ -132,7 +134,8 @@ function parseArray(
132
134
  cell = {
133
135
  type: "ordgroup",
134
136
  mode: parser.mode,
135
- body: cell
137
+ body: cell,
138
+ semisimple: true
136
139
  };
137
140
  row.push(cell);
138
141
  const next = parser.fetch().text;
@@ -49,7 +49,8 @@ function cdArrow(arrowChar, labels, parser) {
49
49
  const arrowGroup = {
50
50
  type: "ordgroup",
51
51
  mode: "math",
52
- body: [leftLabel, sizedArrow, rightLabel]
52
+ body: [leftLabel, sizedArrow, rightLabel],
53
+ semisimple: true
53
54
  };
54
55
  return parser.callFunction("\\\\cdparent", [arrowGroup], []);
55
56
  }
@@ -201,7 +201,7 @@ defineFunction({
201
201
  allowedInText: true,
202
202
  argTypes: ["raw", "raw"]
203
203
  },
204
- handler({ parser, token }, args, optArgs) {
204
+ handler({ parser, breakOnTokenText, token }, args, optArgs) {
205
205
  const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string
206
206
  let color = ""
207
207
  if (model) {
@@ -211,15 +211,8 @@ defineFunction({
211
211
  color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token)
212
212
  }
213
213
 
214
- // Set macro \current@color in current namespace to store the current
215
- // color, mimicking the behavior of color.sty.
216
- // This is currently used just to correctly color a \right
217
- // that follows a \color command.
218
- parser.gullet.macros.set("\\current@color", color)
219
-
220
214
  // Parse out the implicit body that should be colored.
221
- // Since \color nodes should not be nested, break on \color.
222
- const body = parser.parseExpression(true, "\\color")
215
+ const body = parser.parseExpression(true, breakOnTokenText)
223
216
 
224
217
  return {
225
218
  type: "color",
@@ -211,18 +211,10 @@ defineFunction({
211
211
  argTypes: ["primitive"]
212
212
  },
213
213
  handler: (context, args) => {
214
- // \left case below triggers parsing of \right in
215
- // `const right = parser.parseFunction();`
216
- // uses this return value.
217
- const color = context.parser.gullet.macros.get("\\current@color");
218
- if (color && typeof color !== "string") {
219
- throw new ParseError("\\current@color set to non-string in \\right");
220
- }
221
214
  return {
222
215
  type: "leftright-right",
223
216
  mode: context.parser.mode,
224
- delim: checkDelimiter(args[0], context).text,
225
- color // undefined if not set via \color
217
+ delim: checkDelimiter(args[0], context).text
226
218
  };
227
219
  }
228
220
  });
@@ -251,8 +243,7 @@ defineFunction({
251
243
  mode: parser.mode,
252
244
  body,
253
245
  left: delim.text,
254
- right: right.delim,
255
- rightColor: right.color
246
+ right: right.delim
256
247
  };
257
248
  },
258
249
  mathmlBuilder: (group, style) => {
@@ -275,7 +266,6 @@ defineFunction({
275
266
  if (group.right === "\u2216" || group.right.indexOf("arrow") > -1) {
276
267
  rightNode.setAttribute("stretchy", "true")
277
268
  }
278
- if (group.rightColor) { rightNode.style.color = group.rightColor }
279
269
  inner.push(rightNode)
280
270
 
281
271
  return mml.makeRow(inner);
@@ -34,20 +34,12 @@ const mathmlBuilder = (group, style) => {
34
34
  node.style.borderBottom = "0.065em solid"
35
35
  break
36
36
  case "\\cancel":
37
- node.style.background = `linear-gradient(to top left,
38
- rgba(0,0,0,0) 0%,
39
- rgba(0,0,0,0) calc(50% - 0.06em),
40
- rgba(0,0,0,1) 50%,
41
- rgba(0,0,0,0) calc(50% + 0.06em),
42
- rgba(0,0,0,0) 100%);`
37
+ // We can't use an inline background-gradient. It does not work client-side.
38
+ // So set a class and put the rule in the external CSS file.
39
+ node.classes.push("tml-cancel")
43
40
  break
44
41
  case "\\bcancel":
45
- node.style.background = `linear-gradient(to top right,
46
- rgba(0,0,0,0) 0%,
47
- rgba(0,0,0,0) calc(50% - 0.06em),
48
- rgba(0,0,0,1) 50%,
49
- rgba(0,0,0,0) calc(50% + 0.06em),
50
- rgba(0,0,0,0) 100%);`
42
+ node.classes.push("tml-bcancel")
51
43
  break
52
44
  /*
53
45
  case "\\longdiv":
@@ -94,18 +86,7 @@ rgba(0,0,0,0) 100%);`
94
86
  break
95
87
  }
96
88
  case "\\xcancel":
97
- node.style.background = `linear-gradient(to top left,
98
- rgba(0,0,0,0) 0%,
99
- rgba(0,0,0,0) calc(50% - 0.06em),
100
- rgba(0,0,0,1) 50%,
101
- rgba(0,0,0,0) calc(50% + 0.06em),
102
- rgba(0,0,0,0) 100%),
103
- linear-gradient(to top right,
104
- rgba(0,0,0,0) 0%,
105
- rgba(0,0,0,0) calc(50% - 0.06em),
106
- rgba(0,0,0,1) 50%,
107
- rgba(0,0,0,0) calc(50% + 0.06em),
108
- rgba(0,0,0,0) 100%);`
89
+ node.classes.push("tml-xcancel")
109
90
  break
110
91
  }
111
92
  if (group.backgroundColor) {
@@ -8,7 +8,7 @@ const mathmlBuilder = (group, style) => {
8
8
  const mathGroup = mml.buildGroup(group.body, newStyle)
9
9
 
10
10
  if (mathGroup.children.length === 0) { return mathGroup } // empty group, e.g., \mathrm{}
11
- if (font === "boldsymbol" && ["mo", "mpadded"].includes(mathGroup.type)) {
11
+ if (font === "boldsymbol" && ["mo", "mpadded", "mrow"].includes(mathGroup.type)) {
12
12
  mathGroup.style.fontWeight = "bold"
13
13
  return mathGroup
14
14
  }
@@ -37,10 +37,10 @@ defineFunction({
37
37
  },
38
38
  mathmlBuilder(group, style) {
39
39
  if (group.isCharacterBox) {
40
- const inner = mml.buildExpression(group.body, style);
40
+ const inner = mml.buildExpression(group.body, style, true);
41
41
  return inner[0]
42
42
  } else {
43
- return mml.buildExpressionRow(group.body, style, true)
43
+ return mml.buildExpressionRow(group.body, style)
44
44
  }
45
45
  }
46
46
  });
@@ -17,6 +17,13 @@ export const ordTypes = ["textord", "mathord", "ordgroup", "close", "leftright"]
17
17
  // NOTE: Unlike most `builders`s, this one handles not only "op", but also
18
18
  // "supsub" since some of them (like \int) can affect super/subscripting.
19
19
 
20
+ const setSpacing = node => {
21
+ // The user wrote a \mathop{…} function. Change spacing from default to OP spacing.
22
+ // The most likely spacing for an OP is a thin space per TeXbook p170.
23
+ node.attributes.lspace = "0.1667em"
24
+ node.attributes.rspace = "0.1667em"
25
+ }
26
+
20
27
  const mathmlBuilder = (group, style) => {
21
28
  let node;
22
29
 
@@ -28,9 +35,11 @@ const mathmlBuilder = (group, style) => {
28
35
  } else {
29
36
  node.setAttribute("movablelimits", "false")
30
37
  }
38
+ if (group.fromMathOp) { setSpacing(node) }
31
39
  } else if (group.body) {
32
40
  // This is an operator with children. Add them.
33
41
  node = new mathMLTree.MathNode("mo", mml.buildExpression(group.body, style));
42
+ if (group.fromMathOp) { setSpacing(node) }
34
43
  } else {
35
44
  // This is a text operator. Add all of the characters from the operator's name.
36
45
  node = new mathMLTree.MathNode("mi", [new mathMLTree.TextNode(group.name.slice(1))]);
@@ -150,6 +159,7 @@ defineFunction({
150
159
  limits: true,
151
160
  parentIsSupSub: false,
152
161
  symbol: isSymbol,
162
+ fromMathOp: true,
153
163
  stack: false,
154
164
  name: isSymbol ? arr[0].text : null,
155
165
  body: isSymbol ? null : ordargument(body)
@@ -4,6 +4,6 @@ import * as mml from "../buildMathML";
4
4
  defineFunctionBuilders({
5
5
  type: "ordgroup",
6
6
  mathmlBuilder(group, style) {
7
- return mml.buildExpressionRow(group.body, style, true);
7
+ return mml.buildExpressionRow(group.body, style, group.semisimple);
8
8
  }
9
9
  });
@@ -8,7 +8,7 @@
8
8
  * https://mit-license.org/
9
9
  */
10
10
 
11
- export const version = "0.10.19";
11
+ export const version = "0.10.21";
12
12
 
13
13
  export function postProcess(block) {
14
14
  const labelMap = {}