temml 0.9.2 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  | Library | Minified JavaScript + CSS |
4
4
  |:--------------|:-------------------------:|
5
- | Temml | 146 KB |
5
+ | Temml | 147 KB |
6
6
  | MathJax 2.7.5 | 338 KB |
7
7
  | KaTeX | 280 KB |
8
8
  | TeXZilla | 168 KB |
@@ -104,7 +104,7 @@ describe("A delimiter splitter", function() {
104
104
  ]);
105
105
  });
106
106
 
107
- it("splits mutliple times", function() {
107
+ it("splits multiple times", function() {
108
108
  expect("hello ( world ) boo ( more ) stuff").toSplitInto("(", ")", [
109
109
  { type: "text", data: "hello " },
110
110
  { type: "math", data: " world ", rawData: "( world )", display: false },
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable */
2
- /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
2
+ /* -*- Mode: JavaScript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
3
3
  /* vim: set ts=2 et sw=2 tw=80: */
4
4
 
5
5
  /*************************************************************
@@ -1696,7 +1696,7 @@ temml.__defineMacro("\\tripleDashBetweenDoubleLine", `\\kern0.075em\\mathrlap{\\
1696
1696
  };
1697
1697
 
1698
1698
  //
1699
- // Helpers for code anaylsis
1699
+ // Helpers for code analysis
1700
1700
  // Will show type error at calling position
1701
1701
  //
1702
1702
  /** @param {number} a */
@@ -43,10 +43,6 @@ mo.tml-prime {
43
43
  font-feature-settings: 'salt';
44
44
  }
45
45
 
46
- .oldstylenums {
47
- font-feature-settings: 'onum';
48
- }
49
-
50
46
  /* AMS environment auto-numbering via CSS counter. */
51
47
  .tml-eqn::before {
52
48
  counter-increment: tmlEqnNo;
@@ -52,11 +52,6 @@ math {
52
52
  font-family: "Temml";
53
53
  }
54
54
 
55
- .oldstylenums {
56
- font-family: "Cambria Math", math;
57
- font-feature-settings: 'onum';
58
- }
59
-
60
55
  mo.tml-prime {
61
56
  font-family: Temml;
62
57
  }
@@ -50,11 +50,6 @@ math {
50
50
  font-family: "Cambria Math", 'STIXTwoMath-Regular', "Times New Roman", math;
51
51
  }
52
52
 
53
- .oldstylenums {
54
- font-family: "Cambria Math", math;
55
- font-feature-settings: 'onum';
56
- }
57
-
58
53
  mo.tml-prime {
59
54
  font-feature-settings: 'ssty';
60
55
  }
@@ -32,10 +32,6 @@ math .mathscr {
32
32
  font-family: "Temml";
33
33
  }
34
34
 
35
- math .oldstylenums {
36
- font-feature-settings: 'onum';
37
- }
38
-
39
35
  mo.tml-prime {
40
36
  font-family: Temml;
41
37
  }
@@ -40,11 +40,6 @@ math {
40
40
  font-feature-settings: 'ss01';
41
41
  }
42
42
 
43
- .oldstylenums {
44
- font-family: "Cambria Math", math;
45
- font-feature-settings: 'onum';
46
- }
47
-
48
43
  mo.tml-prime {
49
44
  font-feature-settings: 'ss04';
50
45
  }
package/dist/temml.cjs CHANGED
@@ -668,7 +668,7 @@ class TextNode {
668
668
 
669
669
  /**
670
670
  * Converts the text node into a string
671
- * (representing the text iteself).
671
+ * (representing the text itself).
672
672
  */
673
673
  toText() {
674
674
  return this.text;
@@ -845,7 +845,6 @@ defineSymbol(math, rel, "\u226a", "\\ll", true);
845
845
  defineSymbol(math, rel, "\u226b", "\\gg", true);
846
846
  defineSymbol(math, rel, "\u224d", "\\asymp", true);
847
847
  defineSymbol(math, rel, "\u2225", "\\parallel");
848
- defineSymbol(math, rel, "\u22c8", "\\bowtie", true);
849
848
  defineSymbol(math, rel, "\u2323", "\\smile", true);
850
849
  defineSymbol(math, rel, "\u2291", "\\sqsubseteq", true);
851
850
  defineSymbol(math, rel, "\u2292", "\\sqsupseteq", true);
@@ -1162,7 +1161,6 @@ defineSymbol(math, rel, "\u22d9", "\\gggtr");
1162
1161
  defineSymbol(math, bin, "\u22b2", "\\lhd");
1163
1162
  defineSymbol(math, bin, "\u22b3", "\\rhd");
1164
1163
  defineSymbol(math, rel, "\u2242", "\\eqsim", true);
1165
- defineSymbol(math, rel, "\u22c8", "\\Join");
1166
1164
  defineSymbol(math, rel, "\u2251", "\\Doteq", true);
1167
1165
  defineSymbol(math, rel, "\u297d", "\\strictif", true);
1168
1166
  defineSymbol(math, rel, "\u297c", "\\strictfi", true);
@@ -1188,6 +1186,11 @@ defineSymbol(math, bin, "\u22ba", "\\intercal", true);
1188
1186
  defineSymbol(math, bin, "\u22d2", "\\doublecap");
1189
1187
  defineSymbol(math, bin, "\u22d3", "\\doublecup");
1190
1188
  defineSymbol(math, bin, "\u22a0", "\\boxtimes", true);
1189
+ defineSymbol(math, bin, "\u22c8", "\\bowtie", true);
1190
+ defineSymbol(math, bin, "\u22c8", "\\Join");
1191
+ defineSymbol(math, bin, "\u27d5", "\\leftouterjoin", true);
1192
+ defineSymbol(math, bin, "\u27d6", "\\rightouterjoin", true);
1193
+ defineSymbol(math, bin, "\u27d7", "\\fullouterjoin", true);
1191
1194
 
1192
1195
  // AMS Arrows
1193
1196
  // Note: unicode-math maps \u21e2 to their own function \rightdasharrow.
@@ -1234,6 +1237,8 @@ defineSymbol(math, textord, "\u2018", "`");
1234
1237
  defineSymbol(math, textord, "$", "\\$");
1235
1238
  defineSymbol(text, textord, "$", "\\$");
1236
1239
  defineSymbol(text, textord, "$", "\\textdollar");
1240
+ defineSymbol(math, textord, "¢", "\\cent");
1241
+ defineSymbol(text, textord, "¢", "\\cent");
1237
1242
  defineSymbol(math, textord, "%", "\\%");
1238
1243
  defineSymbol(text, textord, "%", "\\%");
1239
1244
  defineSymbol(math, textord, "_", "\\_");
@@ -1890,7 +1895,7 @@ function setLineBreaks(expression, wrapMode, isDisplayMode, color) {
1890
1895
  }
1891
1896
 
1892
1897
  /**
1893
- * This file converts a parse tree into a cooresponding MathML tree. The main
1898
+ * This file converts a parse tree into a corresponding MathML tree. The main
1894
1899
  * entry point is the `buildMathML` function, which takes a parse tree from the
1895
1900
  * parser.
1896
1901
  */
@@ -3148,13 +3153,12 @@ defineFunction({
3148
3153
  names: ["\\\\"],
3149
3154
  props: {
3150
3155
  numArgs: 0,
3151
- numOptionalArgs: 1,
3152
- argTypes: ["size"],
3156
+ numOptionalArgs: 0,
3153
3157
  allowedInText: true
3154
3158
  },
3155
3159
 
3156
3160
  handler({ parser }, args, optArgs) {
3157
- const size = optArgs[0];
3161
+ const size = parser.gullet.future().text === "[" ? parser.parseSizeGroup(true) : null;
3158
3162
  const newLine = !parser.settings.displayMode;
3159
3163
  return {
3160
3164
  type: "cr",
@@ -3522,6 +3526,11 @@ const delimiters = [
3522
3526
  "."
3523
3527
  ];
3524
3528
 
3529
+ // Export isDelimiter for benefit of parser.
3530
+ const dels = ["}", "\\left", "\\middle", "\\right"];
3531
+ const isDelimiter = str => str.length > 0 &&
3532
+ (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str));
3533
+
3525
3534
  // Metrics of the different sizes. Found by looking at TeX's output of
3526
3535
  // $\bigl| // \Bigl| \biggl| \Biggl| \showlists$
3527
3536
  // Used to create stacked delimiters of appropriate sizes in makeSizedDelim.
@@ -4914,7 +4923,6 @@ defineFunction({
4914
4923
  "\\mathscr",
4915
4924
  "\\mathsf",
4916
4925
  "\\mathtt",
4917
- "\\oldstylenums",
4918
4926
 
4919
4927
  // aliases
4920
4928
  "\\Bbb",
@@ -6205,10 +6213,6 @@ const noSuccessor = ["\\smallint"];
6205
6213
  // Math operators (e.g. \sin) need a space between these types and themselves:
6206
6214
  const ordTypes = ["textord", "mathord", "ordgroup", "close", "leftright"];
6207
6215
 
6208
- const dels$1 = ["}", "\\left", "\\middle", "\\right"];
6209
- const isDelimiter$1 = str => str.length > 0 &&
6210
- (delimiters.includes(str) || delimiterSizes[str] || dels$1.includes(str));
6211
-
6212
6216
  // NOTE: Unlike most `builders`s, this one handles not only "op", but also
6213
6217
  // "supsub" since some of them (like \int) can affect super/subscripting.
6214
6218
 
@@ -6429,7 +6433,7 @@ defineFunction({
6429
6433
  parentIsSupSub: false,
6430
6434
  symbol: false,
6431
6435
  stack: false,
6432
- isFollowedByDelimiter: isDelimiter$1(next),
6436
+ isFollowedByDelimiter: isDelimiter(next),
6433
6437
  needsLeadingSpace: prevAtomType.length > 0 && ordTypes.includes(prevAtomType),
6434
6438
  name: funcName
6435
6439
  };
@@ -6454,7 +6458,7 @@ defineFunction({
6454
6458
  parentIsSupSub: false,
6455
6459
  symbol: false,
6456
6460
  stack: false,
6457
- isFollowedByDelimiter: isDelimiter$1(next),
6461
+ isFollowedByDelimiter: isDelimiter(next),
6458
6462
  needsLeadingSpace: prevAtomType.length > 0 && ordTypes.includes(prevAtomType),
6459
6463
  name: funcName
6460
6464
  };
@@ -6540,11 +6544,7 @@ function defineMacro(name, body) {
6540
6544
  _macros[name] = body;
6541
6545
  }
6542
6546
 
6543
- const dels = ["}", "\\left", "\\middle", "\\right"];
6544
- const isDelimiter = str => str.length > 0 &&
6545
- (delimiters.includes(str) || delimiterSizes[str] || dels.includes(str));
6546
-
6547
- // NOTE: Unlike most builders, this one handles not only
6547
+ // NOTE: Unlike most builders, this one handles not only
6548
6548
  // "operatorname", but also "supsub" since \operatorname* can
6549
6549
  // affect super/subscripting.
6550
6550
 
@@ -6554,8 +6554,12 @@ const mathmlBuilder$1 = (group, style) => {
6554
6554
  // Is expression a string or has it something like a fraction?
6555
6555
  let isAllString = true; // default
6556
6556
  for (let i = 0; i < expression.length; i++) {
6557
- const node = expression[i];
6557
+ let node = expression[i];
6558
6558
  if (node instanceof mathMLTree.MathNode) {
6559
+ if (node.type === "mrow" && node.children.length === 1 &&
6560
+ node.children[0] instanceof mathMLTree.MathNode) {
6561
+ node = node.children[0];
6562
+ }
6559
6563
  switch (node.type) {
6560
6564
  case "mi":
6561
6565
  case "mn":
@@ -6613,7 +6617,9 @@ const mathmlBuilder$1 = (group, style) => {
6613
6617
  let wrapper;
6614
6618
  if (isAllString) {
6615
6619
  wrapper = new mathMLTree.MathNode("mi", expression);
6616
- wrapper.setAttribute("mathvariant", "normal");
6620
+ if (expression[0].text.length === 1) {
6621
+ wrapper.setAttribute("mathvariant", "normal");
6622
+ }
6617
6623
  } else {
6618
6624
  wrapper = new mathMLTree.MathNode("mrow", expression);
6619
6625
  }
@@ -7151,6 +7157,7 @@ defineFunctionBuilders({
7151
7157
  let isOver;
7152
7158
  let isSup;
7153
7159
  let appendApplyFunction = false;
7160
+ let appendSpace = false;
7154
7161
  let needsLeadingSpace = false;
7155
7162
 
7156
7163
  if (group.base && group.base.type === "horizBrace") {
@@ -7165,6 +7172,7 @@ defineFunctionBuilders({
7165
7172
  (group.base.type === "op" || group.base.type === "operatorname")) {
7166
7173
  group.base.parentIsSupSub = true;
7167
7174
  appendApplyFunction = !group.base.symbol;
7175
+ appendSpace = appendApplyFunction && !group.isFollowedByDelimiter;
7168
7176
  needsLeadingSpace = group.base.needsLeadingSpace;
7169
7177
  }
7170
7178
 
@@ -7252,6 +7260,11 @@ defineFunctionBuilders({
7252
7260
  } else {
7253
7261
  node = mathMLTree.newDocumentFragment([node, operator]);
7254
7262
  }
7263
+ if (appendSpace) {
7264
+ const space = new mathMLTree.MathNode("mspace");
7265
+ space.setAttribute("width", "0.1667em"); // thin space.
7266
+ node.children.push(space);
7267
+ }
7255
7268
  } else if (symbolRegEx.test(nodeType)) {
7256
7269
  // Wrap in a <mrow>. Otherwise Firefox stretchy parens will not stretch to include limits.
7257
7270
  node = new mathMLTree.MathNode("mrow", [node]);
@@ -7328,8 +7341,7 @@ const fontMap = {
7328
7341
  mathfrak: "fraktur",
7329
7342
  mathscr: "script",
7330
7343
  mathsf: "sans-serif",
7331
- mathtt: "monospace",
7332
- oldstylenums: "oldstylenums"
7344
+ mathtt: "monospace"
7333
7345
  };
7334
7346
 
7335
7347
  /**
@@ -7399,8 +7411,6 @@ const getVariant = function(group, style) {
7399
7411
  return "sans-serif"
7400
7412
  case "mathtt":
7401
7413
  return "monospace"
7402
- case "oldstylenums":
7403
- return "oldstylenums"
7404
7414
  }
7405
7415
 
7406
7416
  let text = group.text;
@@ -7696,10 +7706,7 @@ defineFunctionBuilders({
7696
7706
  let node;
7697
7707
  if (numberRegEx$1.test(group.text)) {
7698
7708
  const tag = group.mode === "text" ? "mtext" : "mn";
7699
- if (variant === "oldstylenums") {
7700
- const ms = new mathMLTree.MathNode("mstyle", [text], ["oldstylenums"]);
7701
- node = new mathMLTree.MathNode(tag, [ms]);
7702
- } else if (variant === "italic" || variant === "bold-italic") {
7709
+ if (variant === "italic" || variant === "bold-italic") {
7703
7710
  return italicNumber(text, variant, tag)
7704
7711
  } else {
7705
7712
  if (variant !== "normal") {
@@ -8890,7 +8897,7 @@ defineMacro("\\incoh", `{\\mkern5mu\\rule{}{0.7em}\\mathrlap{\\smash{\\raise2mu{
8890
8897
  defineMacro("\\standardstate", "\\text{\\tiny\\char`⦵}");
8891
8898
 
8892
8899
  /* eslint-disable */
8893
- /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
8900
+ /* -*- Mode: JavaScript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
8894
8901
  /* vim: set ts=2 et sw=2 tw=80: */
8895
8902
 
8896
8903
  /*************************************************************
@@ -10587,7 +10594,7 @@ defineMacro("\\tripleDashBetweenDoubleLine", `\\kern0.075em\\mathrlap{\\mathrlap
10587
10594
  };
10588
10595
 
10589
10596
  //
10590
- // Helpers for code anaylsis
10597
+ // Helpers for code analysis
10591
10598
  // Will show type error at calling position
10592
10599
  //
10593
10600
  /** @param {number} a */
@@ -11012,15 +11019,15 @@ class MacroExpander {
11012
11019
  * Expand the next token only once if possible.
11013
11020
  *
11014
11021
  * If the token is expanded, the resulting tokens will be pushed onto
11015
- * the stack in reverse order and will be returned as an array,
11016
- * also in reverse order.
11022
+ * the stack in reverse order, and the number of such tokens will be
11023
+ * returned. This number might be zero or positive.
11017
11024
  *
11018
- * If not, the next token will be returned without removing it
11019
- * from the stack. This case can be detected by a `Token` return value
11020
- * instead of an `Array` return value.
11025
+ * If not, the return value is `false`, and the next token remains at the
11026
+ * top of the stack.
11021
11027
  *
11022
11028
  * In either case, the next token will be on the top of the stack,
11023
- * or the stack will be empty.
11029
+ * or the stack will be empty (in case of empty expansion
11030
+ * and no other tokens).
11024
11031
  *
11025
11032
  * Used to implement `expandAfterFuture` and `expandNextToken`.
11026
11033
  *
@@ -11036,7 +11043,7 @@ class MacroExpander {
11036
11043
  throw new ParseError("Undefined control sequence: " + name);
11037
11044
  }
11038
11045
  this.pushToken(topToken);
11039
- return topToken;
11046
+ return false;
11040
11047
  }
11041
11048
  this.expansionCount++;
11042
11049
  if (this.expansionCount > this.settings.maxExpand) {
@@ -11070,7 +11077,7 @@ class MacroExpander {
11070
11077
  }
11071
11078
  // Concatenate expansion onto top of stack.
11072
11079
  this.pushTokens(tokens);
11073
- return tokens;
11080
+ return tokens.length;
11074
11081
  }
11075
11082
 
11076
11083
  /**
@@ -11089,14 +11096,13 @@ class MacroExpander {
11089
11096
  */
11090
11097
  expandNextToken() {
11091
11098
  for (;;) {
11092
- const expanded = this.expandOnce();
11093
- // expandOnce returns Token if and only if it's fully expanded.
11094
- if (expanded instanceof Token) {
11099
+ if (this.expandOnce() === false) { // fully expanded
11100
+ const token = this.stack.pop();
11095
11101
  // The token after \noexpand is interpreted as if its meaning were ‘\relax’
11096
- if (expanded.treatAsRelax) {
11097
- expanded.text = "\\relax";
11102
+ if (token.treatAsRelax) {
11103
+ token.text = "\\relax";
11098
11104
  }
11099
- return this.stack.pop(); // === expanded
11105
+ return token
11100
11106
  }
11101
11107
  }
11102
11108
 
@@ -11122,15 +11128,15 @@ class MacroExpander {
11122
11128
  const oldStackLength = this.stack.length;
11123
11129
  this.pushTokens(tokens);
11124
11130
  while (this.stack.length > oldStackLength) {
11125
- const expanded = this.expandOnce(true); // expand only expandable tokens
11126
- // expandOnce returns Token if and only if it's fully expanded.
11127
- if (expanded instanceof Token) {
11128
- if (expanded.treatAsRelax) {
11131
+ // Expand only expandable tokens
11132
+ if (this.expandOnce(true) === false) { // fully expanded
11133
+ const token = this.stack.pop();
11134
+ if (token.treatAsRelax) {
11129
11135
  // the expansion of \noexpand is the token itself
11130
- expanded.noexpand = false;
11131
- expanded.treatAsRelax = false;
11136
+ token.noexpand = false;
11137
+ token.treatAsRelax = false;
11132
11138
  }
11133
- output.push(this.stack.pop());
11139
+ output.push(token);
11134
11140
  }
11135
11141
  }
11136
11142
  return output;
@@ -11430,6 +11436,36 @@ const uSubsAndSups = Object.freeze({
11430
11436
  '\u1DBF': 'θ'
11431
11437
  });
11432
11438
 
11439
+ // Used for Unicode input of calligraphic and script letters
11440
+ const asciiFromScript = Object.freeze({
11441
+ "\ud835\udc9c": "A",
11442
+ "\u212c": "B",
11443
+ "\ud835\udc9e": "C",
11444
+ "\ud835\udc9f": "D",
11445
+ "\u2130": "E",
11446
+ "\u2131": "F",
11447
+ "\ud835\udca2": "G",
11448
+ "\u210B": "H",
11449
+ "\u2110": "I",
11450
+ "\ud835\udca5": "J",
11451
+ "\ud835\udca6": "K",
11452
+ "\u2112": "L",
11453
+ "\u2113": "M",
11454
+ "\ud835\udca9": "N",
11455
+ "\ud835\udcaa": "O",
11456
+ "\ud835\udcab": "P",
11457
+ "\ud835\udcac": "Q",
11458
+ "\u211B": "R",
11459
+ "\ud835\udcae": "S",
11460
+ "\ud835\udcaf": "T",
11461
+ "\ud835\udcb0": "U",
11462
+ "\ud835\udcb1": "V",
11463
+ "\ud835\udcb2": "W",
11464
+ "\ud835\udcb3": "X",
11465
+ "\ud835\udcb4": "Y",
11466
+ "\ud835\udcb5": "Z"
11467
+ });
11468
+
11433
11469
  // Mapping of Unicode accent characters to their LaTeX equivalent in text and
11434
11470
  // math mode (when they exist).
11435
11471
  var unicodeAccents = {
@@ -11928,7 +11964,7 @@ class Parser {
11928
11964
  * Parses an "expression", which is a list of atoms.
11929
11965
  *
11930
11966
  * `breakOnInfix`: Should the parsing stop when we hit infix nodes? This
11931
- * happens when functions have higher precendence han infix
11967
+ * happens when functions have higher precedence han infix
11932
11968
  * nodes in implicit parses.
11933
11969
  *
11934
11970
  * `breakOnTokenText`: The text of the token that the expression should end
@@ -12179,12 +12215,16 @@ class Parser {
12179
12215
  return base
12180
12216
  } else {
12181
12217
  // We got either a superscript or subscript, create a supsub
12218
+ const isFollowedByDelimiter = (!base || base.type !== "op" && base.type !== "operatorname")
12219
+ ? undefined
12220
+ : isDelimiter(this.nextToken.text);
12182
12221
  return {
12183
12222
  type: "supsub",
12184
12223
  mode: this.mode,
12185
12224
  base: base,
12186
12225
  sup: superscript,
12187
- sub: subscript
12226
+ sub: subscript,
12227
+ isFollowedByDelimiter
12188
12228
  }
12189
12229
  }
12190
12230
  } else {
@@ -12345,7 +12385,7 @@ class Parser {
12345
12385
  while (true) {
12346
12386
  const ch = this.fetch().text;
12347
12387
  // \ufe0e is the Unicode variation selector to supress emoji. Ignore it.
12348
- if (ch === " " || ch === "\ufe0e") {
12388
+ if (ch === " " || ch === "\u00a0" || ch === "\ufe0e") {
12349
12389
  this.consume();
12350
12390
  } else {
12351
12391
  break
@@ -12665,6 +12705,22 @@ class Parser {
12665
12705
  text
12666
12706
  };
12667
12707
  } else {
12708
+ if (asciiFromScript[text]) {
12709
+ // Unicode 14 disambiguates chancery from roundhand.
12710
+ // See https://www.unicode.org/charts/PDF/U1D400.pdf
12711
+ this.consume();
12712
+ const nextCode = this.fetch().text.charCodeAt(0);
12713
+ // mathcal is Temml default. Use mathscript if called for.
12714
+ const font = nextCode === 0xfe01 ? "mathscr" : "mathcal";
12715
+ if (nextCode === 0xfe00 || nextCode === 0xfe01) { this.consume(); }
12716
+ return {
12717
+ type: "font",
12718
+ mode: "math",
12719
+ font,
12720
+ body: { type: "mathord", mode: "math", loc, text: asciiFromScript[text] }
12721
+ }
12722
+ }
12723
+ // Default ord character. No disambiguation necessary.
12668
12724
  s = {
12669
12725
  type: group,
12670
12726
  mode: this.mode,
@@ -12921,7 +12977,7 @@ class Style {
12921
12977
  * https://mit-license.org/
12922
12978
  */
12923
12979
 
12924
- const version = "0.9.2";
12980
+ const version = "0.10.2";
12925
12981
 
12926
12982
  function postProcess(block) {
12927
12983
  const labelMap = {};