katex 0.14.0 → 0.15.2

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/src/Namespace.js CHANGED
@@ -15,7 +15,7 @@ export type Mapping<Value> = {[string]: Value};
15
15
  export default class Namespace<Value> {
16
16
  current: Mapping<Value>;
17
17
  builtins: Mapping<Value>;
18
- undefStack: Mapping<Value>[];
18
+ undefStack: Mapping<?Value>[];
19
19
 
20
20
  /**
21
21
  * Both arguments are optional. The first argument is an object of
@@ -48,7 +48,7 @@ export default class Namespace<Value> {
48
48
  const undefs = this.undefStack.pop();
49
49
  for (const undef in undefs) {
50
50
  if (undefs.hasOwnProperty(undef)) {
51
- if (undefs[undef] === undefined) {
51
+ if (undefs[undef] == null) {
52
52
  delete this.current[undef];
53
53
  } else {
54
54
  this.current[undef] = undefs[undef];
@@ -97,8 +97,9 @@ export default class Namespace<Value> {
97
97
  * Local set() sets the current value and (when appropriate) adds an undo
98
98
  * operation to the undo stack. Global set() may change the undo
99
99
  * operation at every level, so takes time linear in their number.
100
+ * A value of undefined means to delete existing definitions.
100
101
  */
101
- set(name: string, value: Value, global: boolean = false) {
102
+ set(name: string, value: ?Value, global: boolean = false) {
102
103
  if (global) {
103
104
  // Global set is equivalent to setting in all groups. Simulate this
104
105
  // by destroying any undos currently scheduled for this name,
@@ -119,6 +120,10 @@ export default class Namespace<Value> {
119
120
  top[name] = this.current[name];
120
121
  }
121
122
  }
122
- this.current[name] = value;
123
+ if (value == null) {
124
+ delete this.current[name];
125
+ } else {
126
+ this.current[name] = value;
127
+ }
123
128
  }
124
129
  }
package/src/Parser.js CHANGED
@@ -150,6 +150,27 @@ export default class Parser {
150
150
  }
151
151
  }
152
152
 
153
+ /**
154
+ * Fully parse a separate sequence of tokens as a separate job.
155
+ * Tokens should be specified in reverse order, as in a MacroDefinition.
156
+ */
157
+ subparse(tokens: Token[]): AnyParseNode[] {
158
+ // Save the next token from the current job.
159
+ const oldToken = this.nextToken;
160
+ this.consume();
161
+
162
+ // Run the new job, terminating it with an excess '}'
163
+ this.gullet.pushToken(new Token("}"));
164
+ this.gullet.pushTokens(tokens);
165
+ const parse = this.parseExpression(false);
166
+ this.expect("}");
167
+
168
+ // Restore the next token from the current job.
169
+ this.nextToken = oldToken;
170
+
171
+ return parse;
172
+ }
173
+
153
174
  static endOfExpression: string[] = ["}", "\\endgroup", "\\end", "\\right", "&"];
154
175
 
155
176
  /**
package/src/Settings.js CHANGED
@@ -52,23 +52,180 @@ export type TrustContextTypes = {
52
52
  export type AnyTrustContext = $Values<TrustContextTypes>;
53
53
  export type TrustFunction = (context: AnyTrustContext) => ?boolean;
54
54
 
55
- export type SettingsOptions = {
56
- displayMode?: boolean;
57
- output?: "html" | "mathml" | "htmlAndMathml";
58
- leqno?: boolean;
59
- fleqn?: boolean;
60
- throwOnError?: boolean;
61
- errorColor?: string;
62
- macros?: MacroMap;
63
- minRuleThickness?: number;
64
- colorIsTextColor?: boolean;
65
- strict?: boolean | "ignore" | "warn" | "error" | StrictFunction;
66
- trust?: boolean | TrustFunction;
67
- maxSize?: number;
68
- maxExpand?: number;
69
- globalGroup?: boolean;
55
+ export type SettingsOptions = $Shape<Settings>;
56
+
57
+ type EnumType = {| enum: string[] |};
58
+ type Type = "boolean" | "string" | "number" | "object" | "function" | EnumType;
59
+ type Schema = {
60
+ [$Keys<SettingsOptions>]: {
61
+ /**
62
+ * Allowed type(s) of the value.
63
+ */
64
+ type: Type | Type[];
65
+ /**
66
+ * The default value. If not specified, false for boolean, an empty string
67
+ * for string, 0 for number, an empty object for object, or the first item
68
+ * for enum will be used. If multiple types are allowed, the first allowed
69
+ * type will be used for determining the default value.
70
+ */
71
+ default?: any;
72
+ /**
73
+ * The description.
74
+ */
75
+ description?: string;
76
+ /**
77
+ * The function to process the option.
78
+ */
79
+ processor?: (any) => any,
80
+ /**
81
+ * The command line argument. See Commander.js docs for more information.
82
+ * If not specified, the name prefixed with -- will be used. Set false not
83
+ * to add to the CLI.
84
+ */
85
+ cli?: string | false;
86
+ /**
87
+ * The default value for the CLI.
88
+ */
89
+ cliDefault?: any;
90
+ /**
91
+ * The description for the CLI. If not specified, the description for the
92
+ * option will be used.
93
+ */
94
+ cliDescription?: string;
95
+ /**
96
+ * The custom argument processor for the CLI. See Commander.js docs for
97
+ * more information.
98
+ */
99
+ cliProcessor?: (any, any) => any;
100
+ };
101
+ };
102
+
103
+ // TODO: automatically generate documentation
104
+ // TODO: check all properties on Settings exist
105
+ // TODO: check the type of a property on Settings matches
106
+ export const SETTINGS_SCHEMA: Schema = {
107
+ displayMode: {
108
+ type: "boolean",
109
+ description: "Render math in display mode, which puts the math in " +
110
+ "display style (so \\int and \\sum are large, for example), and " +
111
+ "centers the math on the page on its own line.",
112
+ cli: "-d, --display-mode",
113
+ },
114
+ output: {
115
+ type: {enum: ["htmlAndMathml", "html", "mathml"]},
116
+ description: "Determines the markup language of the output.",
117
+ cli: "-F, --format <type>",
118
+ },
119
+ leqno: {
120
+ type: "boolean",
121
+ description: "Render display math in leqno style (left-justified tags).",
122
+ },
123
+ fleqn: {
124
+ type: "boolean",
125
+ description: "Render display math flush left.",
126
+ },
127
+ throwOnError: {
128
+ type: "boolean",
129
+ default: true,
130
+ cli: "-t, --no-throw-on-error",
131
+ cliDescription: "Render errors (in the color given by --error-color) ins" +
132
+ "tead of throwing a ParseError exception when encountering an error.",
133
+ },
134
+ errorColor: {
135
+ type: "string",
136
+ default: "#cc0000",
137
+ cli: "-c, --error-color <color>",
138
+ cliDescription: "A color string given in the format 'rgb' or 'rrggbb' " +
139
+ "(no #). This option determines the color of errors rendered by the " +
140
+ "-t option.",
141
+ cliProcessor: (color) => "#" + color,
142
+ },
143
+ macros: {
144
+ type: "object",
145
+ cli: "-m, --macro <def>",
146
+ cliDescription: "Define custom macro of the form '\\foo:expansion' (use " +
147
+ "multiple -m arguments for multiple macros).",
148
+ cliDefault: [],
149
+ cliProcessor: (def, defs) => {
150
+ defs.push(def);
151
+ return defs;
152
+ },
153
+ },
154
+ minRuleThickness: {
155
+ type: "number",
156
+ description: "Specifies a minimum thickness, in ems, for fraction lines," +
157
+ " `\\sqrt` top lines, `{array}` vertical lines, `\\hline`, " +
158
+ "`\\hdashline`, `\\underline`, `\\overline`, and the borders of " +
159
+ "`\\fbox`, `\\boxed`, and `\\fcolorbox`.",
160
+ processor: (t) => Math.max(0, t),
161
+ cli: "--min-rule-thickness <size>",
162
+ cliProcessor: parseFloat,
163
+ },
164
+ colorIsTextColor: {
165
+ type: "boolean",
166
+ description: "Makes \\color behave like LaTeX's 2-argument \\textcolor, " +
167
+ "instead of LaTeX's one-argument \\color mode change.",
168
+ cli: "-b, --color-is-text-color",
169
+ },
170
+ strict: {
171
+ type: [{enum: ["warn", "ignore", "error"]}, "boolean", "function"],
172
+ description: "Turn on strict / LaTeX faithfulness mode, which throws an " +
173
+ "error if the input uses features that are not supported by LaTeX.",
174
+ cli: "-S, --strict",
175
+ cliDefault: false,
176
+ },
177
+ trust: {
178
+ type: ["boolean", "function"],
179
+ description: "Trust the input, enabling all HTML features such as \\url.",
180
+ cli: "-T, --trust",
181
+ },
182
+ maxSize: {
183
+ type: "number",
184
+ default: Infinity,
185
+ description: "If non-zero, all user-specified sizes, e.g. in " +
186
+ "\\rule{500em}{500em}, will be capped to maxSize ems. Otherwise, " +
187
+ "elements and spaces can be arbitrarily large",
188
+ processor: (s) => Math.max(0, s),
189
+ cli: "-s, --max-size <n>",
190
+ cliProcessor: parseInt,
191
+ },
192
+ maxExpand: {
193
+ type: "number",
194
+ default: 1000,
195
+ description: "Limit the number of macro expansions to the specified " +
196
+ "number, to prevent e.g. infinite macro loops. If set to Infinity, " +
197
+ "the macro expander will try to fully expand as in LaTeX.",
198
+ processor: (n) => Math.max(0, n),
199
+ cli: "-e, --max-expand <n>",
200
+ cliProcessor: (n) => (n === "Infinity" ? Infinity : parseInt(n)),
201
+ },
202
+ globalGroup: {
203
+ type: "boolean",
204
+ cli: false,
205
+ },
70
206
  };
71
207
 
208
+ function getDefaultValue(schema): any {
209
+ if (schema.default) {
210
+ return schema.default;
211
+ }
212
+ const type = schema.type;
213
+ const defaultType = Array.isArray(type) ? type[0] : type;
214
+ if (typeof defaultType !== 'string') {
215
+ return defaultType.enum[0];
216
+ }
217
+ switch (defaultType) {
218
+ case 'boolean':
219
+ return false;
220
+ case 'string':
221
+ return '';
222
+ case 'number':
223
+ return 0;
224
+ case 'object':
225
+ return {};
226
+ }
227
+ }
228
+
72
229
  /**
73
230
  * The main Settings object
74
231
  *
@@ -98,23 +255,17 @@ export default class Settings {
98
255
  constructor(options: SettingsOptions) {
99
256
  // allow null options
100
257
  options = options || {};
101
- this.displayMode = utils.deflt(options.displayMode, false);
102
- this.output = utils.deflt(options.output, "htmlAndMathml");
103
- this.leqno = utils.deflt(options.leqno, false);
104
- this.fleqn = utils.deflt(options.fleqn, false);
105
- this.throwOnError = utils.deflt(options.throwOnError, true);
106
- this.errorColor = utils.deflt(options.errorColor, "#cc0000");
107
- this.macros = options.macros || {};
108
- this.minRuleThickness = Math.max(
109
- 0,
110
- utils.deflt(options.minRuleThickness, 0)
111
- );
112
- this.colorIsTextColor = utils.deflt(options.colorIsTextColor, false);
113
- this.strict = utils.deflt(options.strict, "warn");
114
- this.trust = utils.deflt(options.trust, false);
115
- this.maxSize = Math.max(0, utils.deflt(options.maxSize, Infinity));
116
- this.maxExpand = Math.max(0, utils.deflt(options.maxExpand, 1000));
117
- this.globalGroup = utils.deflt(options.globalGroup, false);
258
+ for (const prop in SETTINGS_SCHEMA) {
259
+ if (SETTINGS_SCHEMA.hasOwnProperty(prop)) {
260
+ // $FlowFixMe
261
+ const schema = SETTINGS_SCHEMA[prop];
262
+ // TODO: validate options
263
+ // $FlowFixMe
264
+ this[prop] = options[prop] !== undefined ? (schema.processor
265
+ ? schema.processor(options[prop]) : options[prop])
266
+ : getDefaultValue(schema);
267
+ }
268
+ }
118
269
  }
119
270
 
120
271
  /**
@@ -4,10 +4,12 @@ import Style from "../Style";
4
4
  import defineEnvironment from "../defineEnvironment";
5
5
  import {parseCD} from "./cd";
6
6
  import defineFunction from "../defineFunction";
7
+ import defineMacro from "../defineMacro";
7
8
  import mathMLTree from "../mathMLTree";
8
9
  import ParseError from "../ParseError";
9
10
  import {assertNodeType, assertSymbolNodeType} from "../parseNode";
10
11
  import {checkSymbolNodeType} from "../parseNode";
12
+ import {Token} from "../Token";
11
13
  import {calculateSize, makeEm} from "../units";
12
14
  import utils from "../utils";
13
15
 
@@ -54,6 +56,18 @@ const validateAmsEnvironmentContext = context => {
54
56
  }
55
57
  };
56
58
 
59
+ // autoTag (an argument to parseArray) can be one of three values:
60
+ // * undefined: Regular (not-top-level) array; no tags on each row
61
+ // * true: Automatic equation numbering, overridable by \tag
62
+ // * false: Tags allowed on each row, but no automatic numbering
63
+ // This function *doesn't* work with the "split" environment name.
64
+ function getAutoTag(name): ?boolean {
65
+ if (name.indexOf("ed") === -1) {
66
+ return name.indexOf("*") === -1;
67
+ }
68
+ // return undefined;
69
+ }
70
+
57
71
  /**
58
72
  * Parse the body of the environment, with rows delimited by \\ and
59
73
  * columns delimited by &, and create a nested list in row-major order
@@ -68,7 +82,7 @@ function parseArray(
68
82
  cols,
69
83
  arraystretch,
70
84
  colSeparationType,
71
- addEqnNum,
85
+ autoTag,
72
86
  singleRow,
73
87
  emptySingleRow,
74
88
  maxNumCols,
@@ -79,7 +93,7 @@ function parseArray(
79
93
  cols?: AlignSpec[],
80
94
  arraystretch?: number,
81
95
  colSeparationType?: ColSeparationType,
82
- addEqnNum?: boolean,
96
+ autoTag?: ?boolean,
83
97
  singleRow?: boolean,
84
98
  emptySingleRow?: boolean,
85
99
  maxNumCols?: number,
@@ -116,6 +130,29 @@ function parseArray(
116
130
  const rowGaps = [];
117
131
  const hLinesBeforeRow = [];
118
132
 
133
+ const tags = (autoTag != null ? [] : undefined);
134
+
135
+ // amsmath uses \global\@eqnswtrue and \global\@eqnswfalse to represent
136
+ // whether this row should have an equation number. Simulate this with
137
+ // a \@eqnsw macro set to 1 or 0.
138
+ function beginRow() {
139
+ if (autoTag) {
140
+ parser.gullet.macros.set("\\@eqnsw", "1", true);
141
+ }
142
+ }
143
+ function endRow() {
144
+ if (tags) {
145
+ if (parser.gullet.macros.get("\\df@tag")) {
146
+ tags.push(parser.subparse([new Token("\\df@tag")]));
147
+ parser.gullet.macros.set("\\df@tag", undefined, true);
148
+ } else {
149
+ tags.push(Boolean(autoTag) &&
150
+ parser.gullet.macros.get("\\@eqnsw") === "1");
151
+ }
152
+ }
153
+ }
154
+ beginRow();
155
+
119
156
  // Test for \hline at the top of the array.
120
157
  hLinesBeforeRow.push(getHLines(parser));
121
158
 
@@ -154,6 +191,7 @@ function parseArray(
154
191
  }
155
192
  parser.consume();
156
193
  } else if (next === "\\end") {
194
+ endRow();
157
195
  // Arrays terminate newlines with `\crcr` which consumes a `\cr` if
158
196
  // the last line is empty. However, AMS environments keep the
159
197
  // empty row if it's the only one.
@@ -179,12 +217,14 @@ function parseArray(
179
217
  size = parser.parseSizeGroup(true);
180
218
  }
181
219
  rowGaps.push(size ? size.value : null);
220
+ endRow();
182
221
 
183
222
  // check for \hline(s) following the row separator
184
223
  hLinesBeforeRow.push(getHLines(parser));
185
224
 
186
225
  row = [];
187
226
  body.push(row);
227
+ beginRow();
188
228
  } else {
189
229
  throw new ParseError("Expected & or \\\\ or \\cr or \\end",
190
230
  parser.nextToken);
@@ -207,7 +247,7 @@ function parseArray(
207
247
  hskipBeforeAndAfter,
208
248
  hLinesBeforeRow,
209
249
  colSeparationType,
210
- addEqnNum,
250
+ tags,
211
251
  leqno,
212
252
  };
213
253
  }
@@ -339,17 +379,27 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
339
379
  let colSep;
340
380
  let colDescrNum;
341
381
 
342
- const eqnNumSpans = [];
343
- if (group.addEqnNum) {
344
- // An environment with automatic equation numbers.
345
- // Create node(s) that will trigger CSS counter increment.
382
+ const tagSpans = [];
383
+ if (group.tags && group.tags.some((tag) => tag)) {
384
+ // An environment with manual tags and/or automatic equation numbers.
385
+ // Create node(s), the latter of which trigger CSS counter increment.
346
386
  for (r = 0; r < nr; ++r) {
347
387
  const rw = body[r];
348
388
  const shift = rw.pos - offset;
349
- const eqnTag = buildCommon.makeSpan(["eqn-num"], [], options);
350
- eqnTag.depth = rw.depth;
351
- eqnTag.height = rw.height;
352
- eqnNumSpans.push({type: "elem", elem: eqnTag, shift});
389
+ const tag = group.tags[r];
390
+ let tagSpan;
391
+ if (tag === true) { // automatic numbering
392
+ tagSpan = buildCommon.makeSpan(["eqn-num"], [], options);
393
+ } else if (tag === false) {
394
+ // \nonumber/\notag or starred environment
395
+ tagSpan = buildCommon.makeSpan([], [], options);
396
+ } else { // manual \tag
397
+ tagSpan = buildCommon.makeSpan([],
398
+ html.buildExpression(tag, options, true), options);
399
+ }
400
+ tagSpan.depth = rw.depth;
401
+ tagSpan.height = rw.height;
402
+ tagSpans.push({type: "elem", elem: tagSpan, shift});
353
403
  }
354
404
  }
355
405
 
@@ -465,12 +515,12 @@ const htmlBuilder: HtmlBuilder<"array"> = function(group, options) {
465
515
  }, options);
466
516
  }
467
517
 
468
- if (!group.addEqnNum) {
518
+ if (tagSpans.length === 0) {
469
519
  return buildCommon.makeSpan(["mord"], [body], options);
470
520
  } else {
471
521
  let eqnNumCol = buildCommon.makeVList({
472
522
  positionType: "individualShift",
473
- children: eqnNumSpans,
523
+ children: tagSpans,
474
524
  }, options);
475
525
  eqnNumCol = buildCommon.makeSpan(["tag"], [eqnNumCol], options);
476
526
  return buildCommon.makeFragment([body, eqnNumCol]);
@@ -494,7 +544,7 @@ const mathmlBuilder: MathMLBuilder<"array"> = function(group, options) {
494
544
  row.push(new mathMLTree.MathNode("mtd",
495
545
  [mml.buildGroup(rw[j], options)]));
496
546
  }
497
- if (group.addEqnNum) {
547
+ if (group.tags && group.tags[i]) {
498
548
  row.unshift(glue);
499
549
  row.push(glue);
500
550
  if (group.leqno) {
@@ -631,14 +681,15 @@ const alignedHandler = function(context, args) {
631
681
  }
632
682
  const cols = [];
633
683
  const separationType = context.envName.indexOf("at") > -1 ? "alignat" : "align";
684
+ const isSplit = context.envName === "split";
634
685
  const res = parseArray(context.parser,
635
686
  {
636
687
  cols,
637
688
  addJot: true,
638
- addEqnNum: context.envName === "align" || context.envName === "alignat",
689
+ autoTag: isSplit ? undefined : getAutoTag(context.envName),
639
690
  emptySingleRow: true,
640
691
  colSeparationType: separationType,
641
- maxNumCols: context.envName === "split" ? 2 : undefined,
692
+ maxNumCols: isSplit ? 2 : undefined,
642
693
  leqno: context.parser.settings.leqno,
643
694
  },
644
695
  "display"
@@ -984,7 +1035,7 @@ defineEnvironment({
984
1035
  }],
985
1036
  addJot: true,
986
1037
  colSeparationType: "gather",
987
- addEqnNum: context.envName === "gather",
1038
+ autoTag: getAutoTag(context.envName),
988
1039
  emptySingleRow: true,
989
1040
  leqno: context.parser.settings.leqno,
990
1041
  };
@@ -1017,7 +1068,7 @@ defineEnvironment({
1017
1068
  handler(context) {
1018
1069
  validateAmsEnvironmentContext(context);
1019
1070
  const res = {
1020
- addEqnNum: context.envName === "equation",
1071
+ autoTag: getAutoTag(context.envName),
1021
1072
  emptySingleRow: true,
1022
1073
  singleRow: true,
1023
1074
  maxNumCols: 1,
@@ -1043,6 +1094,9 @@ defineEnvironment({
1043
1094
  mathmlBuilder,
1044
1095
  });
1045
1096
 
1097
+ defineMacro("\\nonumber", "\\gdef\\@eqnsw{0}");
1098
+ defineMacro("\\notag", "\\nonumber");
1099
+
1046
1100
  // Catch \hline outside array environment
1047
1101
  defineFunction({
1048
1102
  type: "text", // Doesn't matter what this is.
@@ -22,7 +22,7 @@ function mathmlBuilder(group: ParseNode<"mclass">, options) {
22
22
  const inner = mml.buildExpression(group.body, options);
23
23
 
24
24
  if (group.mclass === "minner") {
25
- return mathMLTree.newDocumentFragment(inner);
25
+ node = new mathMLTree.MathNode("mpadded", inner);
26
26
  } else if (group.mclass === "mord") {
27
27
  if (group.isCharacterBox) {
28
28
  node = inner[0];
@@ -49,6 +49,9 @@ function mathmlBuilder(group: ParseNode<"mclass">, options) {
49
49
  } else if (group.mclass === "mopen" || group.mclass === "mclose") {
50
50
  node.attributes.lspace = "0em";
51
51
  node.attributes.rspace = "0em";
52
+ } else if (group.mclass === "minner") {
53
+ node.attributes.lspace = "0.0556em"; // 1 mu is the most likely option
54
+ node.attributes.width = "+0.1111em";
52
55
  }
53
56
  // MathML <mo> default space is 5/18 em, so <mrel> needs no action.
54
57
  // Ref: https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mo
@@ -0,0 +1,17 @@
1
+ //@flow
2
+ import defineFunction from "../defineFunction";
3
+
4
+ defineFunction({
5
+ type: "internal",
6
+ names: ["\\relax"],
7
+ props: {
8
+ numArgs: 0,
9
+ allowedInText: true,
10
+ },
11
+ handler({parser}) {
12
+ return {
13
+ type: "internal",
14
+ mode: parser.mode,
15
+ };
16
+ },
17
+ });
package/src/functions.js CHANGED
@@ -37,6 +37,7 @@ import "./functions/ordgroup";
37
37
  import "./functions/overline";
38
38
  import "./functions/phantom";
39
39
  import "./functions/raisebox";
40
+ import "./functions/relax";
40
41
  import "./functions/rule";
41
42
  import "./functions/sizing";
42
43
  import "./functions/smash";
package/src/macros.js CHANGED
@@ -368,7 +368,7 @@ defineMacro("\\substack", "\\begin{subarray}{c}#1\\end{subarray}");
368
368
  // \renewcommand{\colon}{\nobreak\mskip2mu\mathpunct{}\nonscript
369
369
  // \mkern-\thinmuskip{:}\mskip6muplus1mu\relax}
370
370
  defineMacro("\\colon", "\\nobreak\\mskip2mu\\mathpunct{}" +
371
- "\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu");
371
+ "\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax");
372
372
 
373
373
  // \newcommand{\boxed}[1]{\fbox{\m@th$\displaystyle#1$}}
374
374
  defineMacro("\\boxed", "\\fbox{$\\displaystyle{#1}$}");
package/src/parseNode.js CHANGED
@@ -39,7 +39,8 @@ type ParseNodeTypes = {
39
39
  body: AnyParseNode[][], // List of rows in the (2D) array.
40
40
  rowGaps: (?Measurement)[],
41
41
  hLinesBeforeRow: Array<boolean[]>,
42
- addEqnNum?: boolean,
42
+ // Whether each row should be automatically numbered, or an explicit tag
43
+ tags?: (boolean | AnyParseNode[])[],
43
44
  leqno?: boolean,
44
45
  isCD?: boolean,
45
46
  |},
package/src/parseTree.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Parser from "./Parser";
8
8
  import ParseError from "./ParseError";
9
+ import {Token} from "./Token";
9
10
 
10
11
  import type Settings from "./Settings";
11
12
  import type {AnyParseNode} from "./parseNode";
@@ -34,12 +35,11 @@ const parseTree = function(toParse: string, settings: Settings): AnyParseNode[]
34
35
  if (!settings.displayMode) {
35
36
  throw new ParseError("\\tag works only in display equations");
36
37
  }
37
- parser.gullet.feed("\\df@tag");
38
38
  tree = [{
39
39
  type: "tag",
40
40
  mode: "text",
41
41
  body: tree,
42
- tag: parser.parse(),
42
+ tag: parser.subparse([new Token("\\df@tag")]),
43
43
  }];
44
44
  }
45
45