tex2typst 0.3.29 → 0.4.1

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/convert.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { TexNode, TexToken, TexTokenType, TexFuncCall, TexGroup, TexSupSub,
2
2
  TexText, TexBeginEnd, TexLeftRight, TexTerminal} from "./tex-types";
3
- import type { Tex2TypstOptions } from "./exposed-types";
3
+ import type { Tex2TypstOptions, Typst2TexOptions } from "./exposed-types";
4
4
  import { TypstFraction, TypstFuncCall, TypstGroup, TypstLeftright, TypstMarkupFunc, TypstMatrixLike, TypstNode, TypstSupsub, TypstTerminal } from "./typst-types";
5
5
  import { TypstNamedParams } from "./typst-types";
6
6
  import { TypstSupsubData } from "./typst-types";
@@ -8,7 +8,7 @@ import { TypstToken } from "./typst-types";
8
8
  import { TypstTokenType } from "./typst-types";
9
9
  import { symbolMap, reverseSymbolMap } from "./map";
10
10
  import { array_includes, array_intersperse, array_split } from "./generic";
11
- import { assert } from "./util";
11
+ import { assert } from "./utils";
12
12
  import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS } from "./tex-tokenizer";
13
13
 
14
14
 
@@ -224,7 +224,7 @@ function appendWithBracketsIfNeeded(node: TypstNode): TypstNode {
224
224
  }
225
225
  }
226
226
 
227
- export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2TypstOptions = {}): TypstNode {
227
+ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2TypstOptions): TypstNode {
228
228
  switch (abstractNode.type) {
229
229
  case 'terminal': {
230
230
  const node = abstractNode as TexTerminal;
@@ -433,6 +433,22 @@ export function convert_tex_node_to_typst(abstractNode: TexNode, options: Tex2Ty
433
433
  );
434
434
  }
435
435
 
436
+ // \not X -> X.not
437
+ if (node.head.value === '\\not') {
438
+ const sym = convert_tex_node_to_typst(node.args[0], options);
439
+ assert(sym.type === "terminal");
440
+ if(sym.head.type === TypstTokenType.SYMBOL) {
441
+ return new TypstToken(TypstTokenType.SYMBOL, sym.head.value + '.not').toNode();
442
+ } else {
443
+ switch(sym.head.value) {
444
+ case '=':
445
+ return new TypstToken(TypstTokenType.SYMBOL, 'eq.not').toNode();
446
+ default:
447
+ throw new Error(`Not supported: \\not ${sym.head.value}`);
448
+ }
449
+ }
450
+ }
451
+
436
452
  if (node.head.value === '\\overset') {
437
453
  return convert_overset(node, options);
438
454
  }
@@ -573,14 +589,6 @@ const TYPST_BINARY_FUNCTIONS: string[] = [
573
589
  ];
574
590
  */
575
591
 
576
- function apply_escape_if_needed(c: TexToken): TexToken {
577
- if (['{', '}', '%'].includes(c.value)) {
578
- return new TexToken(TexTokenType.ELEMENT, '\\' + c.value);
579
- }
580
- return c;
581
- }
582
-
583
-
584
592
  function typst_token_to_tex(token: TypstToken): TexToken {
585
593
  switch (token.type) {
586
594
  case TypstTokenType.NONE:
@@ -588,12 +596,28 @@ function typst_token_to_tex(token: TypstToken): TexToken {
588
596
  return TexToken.EMPTY;
589
597
  case TypstTokenType.SYMBOL: {
590
598
  const _typst_symbol_to_tex = function(symbol: string): string {
591
- if (reverseSymbolMap.has(symbol)) {
592
- return '\\' + reverseSymbolMap.get(symbol)!;
593
- } else {
594
- return '\\' + symbol;
599
+ switch(symbol) {
600
+ case 'eq':
601
+ return '=';
602
+ case 'plus':
603
+ return '+';
604
+ case 'minus':
605
+ return '-';
606
+ case 'percent':
607
+ return '%';
608
+ default: {
609
+ if (reverseSymbolMap.has(symbol)) {
610
+ return '\\' + reverseSymbolMap.get(symbol);
611
+ } else {
612
+ return '\\' + symbol;
613
+ }
614
+ }
595
615
  }
596
616
  }
617
+ if (token.value.endsWith('.not')) {
618
+ const sym = _typst_symbol_to_tex(token.value.slice(0, -4));
619
+ return new TexToken(TexTokenType.COMMAND, sym.startsWith('\\') ? `\\not${sym}` : `\\not ${sym}`);
620
+ }
597
621
  return new TexToken(TexTokenType.COMMAND, _typst_symbol_to_tex(token.value));
598
622
  }
599
623
  case TypstTokenType.ELEMENT: {
@@ -637,7 +661,9 @@ function typst_token_to_tex(token: TypstToken): TexToken {
637
661
 
638
662
  const TEX_NODE_COMMA = new TexToken(TexTokenType.ELEMENT, ',').toNode();
639
663
 
640
- export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
664
+ export function convert_typst_node_to_tex(abstractNode: TypstNode, options: Typst2TexOptions): TexNode {
665
+ const convert_node = (node: TypstNode) => convert_typst_node_to_tex(node, options);
666
+
641
667
  switch (abstractNode.type) {
642
668
  case 'terminal': {
643
669
  const node = abstractNode as TypstTerminal;
@@ -676,7 +702,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
676
702
 
677
703
  case 'group': {
678
704
  const node = abstractNode as TypstGroup;
679
- const args = node.items.map(convert_typst_node_to_tex);
705
+ const args = node.items.map(convert_node);
680
706
  const alignment_char = new TexToken(TexTokenType.CONTROL, '&').toNode();
681
707
  const newline_char = new TexToken(TexTokenType.CONTROL, '\\\\').toNode();
682
708
  if (array_includes(args, alignment_char)) {
@@ -693,7 +719,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
693
719
  }
694
720
  case 'leftright': {
695
721
  const node = abstractNode as TypstLeftright;
696
- const body = convert_typst_node_to_tex(node.body);
722
+ const body = convert_node(node.body);
697
723
  let left = node.left? typst_token_to_tex(node.left) : new TexToken(TexTokenType.ELEMENT, '.');
698
724
  let right = node.right? typst_token_to_tex(node.right) : new TexToken(TexTokenType.ELEMENT, '.');
699
725
  // const is_over_high = node.isOverHigh();
@@ -715,7 +741,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
715
741
  // `\left\| a + \frac{1}{3} \right\|` <- `norm(a + 1/3)`
716
742
  case 'norm': {
717
743
  const arg0 = node.args[0];
718
- const body = convert_typst_node_to_tex(arg0);
744
+ const body = convert_node(arg0);
719
745
  if (node.isOverHigh()) {
720
746
  return new TexLeftRight({
721
747
  body: body,
@@ -736,7 +762,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
736
762
  const left = "\\l" + node.head.value;
737
763
  const right = "\\r" + node.head.value;
738
764
  const arg0 = node.args[0];
739
- const body = convert_typst_node_to_tex(arg0);
765
+ const body = convert_node(arg0);
740
766
  const left_node = new TexToken(TexTokenType.COMMAND, left);
741
767
  const right_node = new TexToken(TexTokenType.COMMAND, right);
742
768
  if (node.isOverHigh()) {
@@ -752,22 +778,22 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
752
778
  // special hook for root
753
779
  case 'root': {
754
780
  const [degree, radicand] = node.args;
755
- const data = convert_typst_node_to_tex(degree);
756
- return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\sqrt'), [convert_typst_node_to_tex(radicand)], data);
781
+ const data = convert_node(degree);
782
+ return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\sqrt'), [convert_node(radicand)], data);
757
783
  }
758
784
  // special hook for overbrace and underbrace
759
785
  case 'overbrace':
760
786
  case 'underbrace': {
761
787
  const [body, label] = node.args;
762
- const base = new TexFuncCall(typst_token_to_tex(node.head), [convert_typst_node_to_tex(body)]);
763
- const script = convert_typst_node_to_tex(label);
788
+ const base = new TexFuncCall(typst_token_to_tex(node.head), [convert_node(body)]);
789
+ const script = convert_node(label);
764
790
  const data = node.head.value === 'overbrace' ? { base, sup: script, sub: null } : { base, sub: script, sup: null };
765
791
  return new TexSupSub(data);
766
792
  }
767
793
  // special hook for vec
768
794
  // "vec(a, b, c)" -> "\begin{pmatrix}a\\ b\\ c\end{pmatrix}"
769
795
  case 'vec': {
770
- const tex_matrix = node.args.map(convert_typst_node_to_tex).map((n) => [n]);
796
+ const tex_matrix = node.args.map(arg => [convert_node(arg)]);
771
797
  return new TexBeginEnd(new TexToken(TexTokenType.LITERAL, 'pmatrix'), tex_matrix);
772
798
  }
773
799
  // special hook for op
@@ -798,21 +824,47 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
798
824
  }
799
825
  return new TexFuncCall(
800
826
  new TexToken(TexTokenType.COMMAND, command),
801
- [convert_typst_node_to_tex(node.args[1])]
827
+ [convert_node(node.args[1])]
802
828
  );
803
829
  }
830
+ // display(...) -> \displaystyle ... \textstyle
831
+ // The postprocessor will remove \textstyle if it is the end of the math code
832
+ case 'display': {
833
+ const arg0 = node.args[0];
834
+ const group = new TexGroup([
835
+ TexToken.COMMAND_DISPLAYSTYLE.toNode(),
836
+ convert_node(arg0),
837
+ ]);
838
+ if (!options.blockMathMode) {
839
+ group.items.push(TexToken.COMMAND_TEXTSTYLE.toNode());
840
+ }
841
+ return group;
842
+ }
843
+ // inline(...) -> \textstyle ... \displaystyle
844
+ // The postprocessor will remove \displaystyle if it is the end of the math code
845
+ case 'inline': {
846
+ const arg0 = node.args[0];
847
+ const group = new TexGroup([
848
+ TexToken.COMMAND_TEXTSTYLE.toNode(),
849
+ convert_node(arg0),
850
+ ]);
851
+ if (options.blockMathMode) {
852
+ group.items.push(TexToken.COMMAND_DISPLAYSTYLE.toNode());
853
+ }
854
+ return group;
855
+ }
804
856
  // general case
805
857
  default: {
806
858
  const func_name_tex = typst_token_to_tex(node.head);
807
859
  const is_known_func = TEX_UNARY_COMMANDS.includes(func_name_tex.value.substring(1))
808
860
  || TEX_BINARY_COMMANDS.includes(func_name_tex.value.substring(1));
809
861
  if (func_name_tex.value.length > 0 && is_known_func) {
810
- return new TexFuncCall(func_name_tex, node.args.map(convert_typst_node_to_tex));
862
+ return new TexFuncCall(func_name_tex, node.args.map(convert_node));
811
863
  } else {
812
864
  return new TexGroup([
813
865
  typst_token_to_tex(node.head).toNode(),
814
866
  new TexToken(TexTokenType.ELEMENT, '(').toNode(),
815
- ...array_intersperse(node.args.map(convert_typst_node_to_tex), TEX_NODE_COMMA),
867
+ ...array_intersperse(node.args.map(convert_node), TEX_NODE_COMMA),
816
868
  new TexToken(TexTokenType.ELEMENT, ')').toNode()
817
869
  ]);
818
870
  }
@@ -828,7 +880,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
828
880
  const color = node.options['fill'];
829
881
  return new TexFuncCall(
830
882
  new TexToken(TexTokenType.COMMAND, '\\textcolor'),
831
- [convert_typst_node_to_tex(color), convert_typst_node_to_tex(node.fragments[0])]
883
+ [convert_node(color), convert_node(node.fragments[0])]
832
884
  )
833
885
  }
834
886
  }
@@ -840,8 +892,8 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
840
892
  case 'supsub': {
841
893
  const node = abstractNode as TypstSupsub;
842
894
  const { base, sup, sub } = node;
843
- const sup_tex = sup? convert_typst_node_to_tex(sup) : null;
844
- const sub_tex = sub? convert_typst_node_to_tex(sub) : null;
895
+ const sup_tex = sup? convert_node(sup) : null;
896
+ const sub_tex = sub? convert_node(sub) : null;
845
897
 
846
898
  // special hook for limits
847
899
  // `limits(+)^a` -> `\overset{a}{+}`
@@ -849,7 +901,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
849
901
  // `limits(+)_a^b` -> `\overset{b}{\underset{a}{+}}`
850
902
  if (base.head.eq(new TypstToken(TypstTokenType.SYMBOL, 'limits'))) {
851
903
  const limits = base as TypstFuncCall;
852
- const body_in_limits = convert_typst_node_to_tex(limits.args[0]);
904
+ const body_in_limits = convert_node(limits.args[0]);
853
905
  if (sup_tex !== null && sub_tex === null) {
854
906
  return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\overset'), [sup_tex, body_in_limits]);
855
907
  } else if (sup_tex === null && sub_tex !== null) {
@@ -860,7 +912,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
860
912
  }
861
913
  }
862
914
 
863
- const base_tex = convert_typst_node_to_tex(base);
915
+ const base_tex = convert_node(base);
864
916
 
865
917
  const res = new TexSupSub({
866
918
  base: base_tex,
@@ -871,7 +923,7 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
871
923
  }
872
924
  case 'matrixLike': {
873
925
  const node = abstractNode as TypstMatrixLike;
874
- const tex_matrix = node.matrix.map(row => row.map(convert_typst_node_to_tex));
926
+ const tex_matrix = node.matrix.map(row => row.map(convert_node));
875
927
  if (node.head.eq(TypstMatrixLike.MAT)) {
876
928
  let env_type = 'pmatrix'; // typst mat use delim:"(" by default
877
929
  if (node.options) {
@@ -918,8 +970,8 @@ export function convert_typst_node_to_tex(abstractNode: TypstNode): TexNode {
918
970
  case 'fraction': {
919
971
  const node = abstractNode as TypstFraction;
920
972
  const [numerator, denominator] = node.args;
921
- const num_tex = convert_typst_node_to_tex(numerator);
922
- const den_tex = convert_typst_node_to_tex(denominator);
973
+ const num_tex = convert_node(numerator);
974
+ const den_tex = convert_node(denominator);
923
975
  return new TexFuncCall(new TexToken(TexTokenType.COMMAND, '\\frac'), [num_tex, den_tex]);
924
976
  }
925
977
  default:
@@ -6,17 +6,21 @@
6
6
  * Any undocumented options may be not working at present or break in the future!
7
7
  */
8
8
  export interface Tex2TypstOptions {
9
- nonStrict?: boolean; /** default is true */
10
- preferShorthands?: boolean; /** default is true */
11
- keepSpaces?: boolean; /** default is false */
12
- fracToSlash?: boolean; /** default is true */
13
- inftyToOo?: boolean; /** default is false */
14
- optimize?: boolean; /** default is true */
15
- customTexMacros?: { [key: string]: string; };
9
+ nonStrict: boolean; /** default is true */
10
+ preferShorthands: boolean; /** default is true */
11
+ keepSpaces: boolean; /** default is false */
12
+ fracToSlash: boolean; /** default is true */
13
+ inftyToOo: boolean; /** default is false */
14
+ optimize: boolean; /** default is true */
15
+ customTexMacros: { [key: string]: string; };
16
16
  }
17
17
 
18
- export declare function tex2typst(tex: string, options?: Tex2TypstOptions): string;
19
- export declare function typst2tex(typst: string): string;
18
+ export interface Typst2TexOptions {
19
+ blockMathMode: boolean; /** default is true */
20
+ }
21
+
22
+ export declare function tex2typst(tex: string, options?: Partial<Tex2TypstOptions>): string;
23
+ export declare function typst2tex(typst: string, options?: Partial<Typst2TexOptions>): string;
20
24
 
21
25
  export declare const symbolMap: Map<string, string>;
22
26
  export declare const shorthandMap: Map<string, string>;
package/src/generic.ts CHANGED
@@ -1,8 +1,8 @@
1
- interface IEquatable {
2
- eq(other: IEquatable): boolean;
1
+ interface IEquatable<T> {
2
+ eq(other: T): boolean;
3
3
  }
4
4
 
5
- export function array_equal<T extends IEquatable>(a: T[], b: T[]): boolean {
5
+ export function array_equal<T extends IEquatable<T>>(a: T[], b: T[]): boolean {
6
6
  /*
7
7
  if (a.length !== b.length) {
8
8
  return false;
@@ -17,7 +17,7 @@ export function array_equal<T extends IEquatable>(a: T[], b: T[]): boolean {
17
17
  return a.length === b.length && a.every((x, i) => x.eq(b[i]));
18
18
  }
19
19
 
20
- export function array_find<T extends IEquatable>(array: T[], item: T, start: number = 0): number {
20
+ export function array_find<T extends IEquatable<T>>(array: T[], item: T, start: number = 0): number {
21
21
  /*
22
22
  for (let i = start; i < array.length; i++) {
23
23
  if (array[i].eq(item)) {
@@ -30,7 +30,7 @@ export function array_find<T extends IEquatable>(array: T[], item: T, start: num
30
30
  return index === -1 ? -1 : index + start;
31
31
  }
32
32
 
33
- export function array_includes<T extends IEquatable>(array: T[], item: T): boolean {
33
+ export function array_includes<T extends IEquatable<T>>(array: T[], item: T): boolean {
34
34
  /*
35
35
  for (const x of array) {
36
36
  if (x.eq(item)) {
@@ -44,7 +44,7 @@ export function array_includes<T extends IEquatable>(array: T[], item: T): boole
44
44
 
45
45
  // e.g. input array=['a', 'b', '+', 'c', '+', 'd', 'e'], sep = '+'
46
46
  // return [['a', 'b'], ['c'], ['d', 'e']]
47
- export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
47
+ export function array_split<T extends IEquatable<T>>(array: T[], sep: T): T[][] {
48
48
  const res: T[][] = [];
49
49
  let current_slice: T[] = [];
50
50
  for (const i of array) {
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseTex } from "./tex-parser";
2
- import type { Tex2TypstOptions } from "./exposed-types";
2
+ import type { Tex2TypstOptions, Typst2TexOptions } from "./exposed-types";
3
3
  import { TypstWriter } from "./typst-writer";
4
4
  import { type TypstWriterOptions } from "./typst-types";
5
5
  import { convert_tex_node_to_typst, convert_typst_node_to_tex } from "./convert";
@@ -9,7 +9,7 @@ import { TexWriter } from "./tex-writer";
9
9
  import { shorthandMap } from "./typst-shorthands";
10
10
 
11
11
 
12
- export function tex2typst(tex: string, options?: Tex2TypstOptions): string {
12
+ export function tex2typst(tex: string, options: Partial<Tex2TypstOptions> = {}): string {
13
13
  const opt: Tex2TypstOptions = {
14
14
  nonStrict: true,
15
15
  preferShorthands: true,
@@ -20,14 +20,12 @@ export function tex2typst(tex: string, options?: Tex2TypstOptions): string {
20
20
  customTexMacros: {}
21
21
  };
22
22
 
23
- if(options !== undefined) {
24
- if (typeof options !== 'object') {
25
- throw new Error("options must be an object");
26
- }
27
- for (const key in opt) {
28
- if (key in options) {
29
- opt[key as keyof Tex2TypstOptions] = options[key as keyof Tex2TypstOptions] as any;
30
- }
23
+ if (typeof options !== 'object') {
24
+ throw new Error("options must be an object");
25
+ }
26
+ for (const key in opt) {
27
+ if (key in options) {
28
+ opt[key as keyof Tex2TypstOptions] = options[key as keyof Tex2TypstOptions] as any;
31
29
  }
32
30
  }
33
31
 
@@ -38,9 +36,22 @@ export function tex2typst(tex: string, options?: Tex2TypstOptions): string {
38
36
  return writer.finalize();
39
37
  }
40
38
 
41
- export function typst2tex(typst: string): string {
39
+ export function typst2tex(typst: string, options: Partial<Typst2TexOptions> = {}): string {
40
+ const opt: Typst2TexOptions = {
41
+ blockMathMode: true,
42
+ };
43
+
44
+ if (typeof options !== 'object') {
45
+ throw new Error("options must be an object");
46
+ }
47
+ for (const key in opt) {
48
+ if (key in options) {
49
+ opt[key as keyof Typst2TexOptions] = options[key as keyof Typst2TexOptions] as any;
50
+ }
51
+ }
52
+
42
53
  const typstTree = parseTypst(typst);
43
- const texTree = convert_typst_node_to_tex(typstTree);
54
+ const texTree = convert_typst_node_to_tex(typstTree, opt);
44
55
  const writer = new TexWriter();
45
56
  writer.append(texTree);
46
57
  return writer.finalize();
package/src/map.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  const symbolMap = new Map<string, string>([
2
2
  ['displaystyle', 'display'],
3
+ ['textstyle', 'inline'],
3
4
  ['hspace', '#h'],
4
5
 
5
6
  ['|', 'bar.v.double'],
package/src/tex-parser.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { TexBeginEnd, TexFuncCall, TexLeftRight, TexNode, TexGroup, TexSupSub, TexSupsubData, TexText, TexToken, TexTokenType } from "./tex-types";
2
- import { assert } from "./util";
3
- import { array_find, array_join } from "./generic";
2
+ import { assert } from "./utils";
3
+ import { array_find, array_join, array_split } from "./generic";
4
4
  import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS, tokenize_tex } from "./tex-tokenizer";
5
5
 
6
6
  const IGNORED_COMMANDS = [
@@ -446,59 +446,31 @@ export class LatexParser {
446
446
  // ignore whitespaces and '\n' after \begin{envName}
447
447
  pos += eat_whitespaces(tokens, pos).length;
448
448
 
449
- const allRows: TexNode[][] = [];
450
- let row: TexNode[] = [];
451
- allRows.push(row);
452
- let group = new TexGroup([]);
453
- row.push(group);
449
+ let closure: TexNode;
450
+ [closure, pos] = this.parseClosure(tokens, pos, closingToken);
454
451
 
455
- while (pos < tokens.length) {
456
- if (tokens[pos].eq(closingToken)) {
457
- break;
458
- }
459
-
460
- const [res, newPos] = this.parseNextExpr(tokens, pos);
461
- pos = newPos;
462
-
463
- if (res.head.type === TexTokenType.SPACE || res.head.type === TexTokenType.NEWLINE) {
464
- if (!this.space_sensitive && res.head.value.replace(/ /g, '').length === 0) {
465
- continue;
466
- }
467
- if (!this.newline_sensitive && res.head.value === '\n') {
468
- continue;
469
- }
470
- }
471
-
472
- if (res.head.eq(new TexToken(TexTokenType.CONTROL, '\\\\'))) {
473
- row = [];
474
- group = new TexGroup([]);
475
- row.push(group);
476
- allRows.push(row);
477
- } else if (res.head.eq(new TexToken(TexTokenType.CONTROL, '&'))) {
478
- group = new TexGroup([]);
479
- row.push(group);
480
- } else {
481
- group.items.push(res);
482
- }
483
- }
484
-
485
- if (pos >= tokens.length) {
452
+ if (pos === -1) {
486
453
  return [[], -1];
487
454
  }
488
455
 
489
- // ignore spaces and '\n' before \end{envName}
490
- if (allRows.length > 0 && allRows[allRows.length - 1].length > 0) {
491
- const last_cell = allRows[allRows.length - 1][allRows[allRows.length - 1].length - 1];
492
- if (last_cell.type === 'ordgroup') {
493
- const last_cell_items = (last_cell as TexGroup).items;
494
- while(last_cell_items.length > 0 && [TexTokenType.SPACE, TexTokenType.NEWLINE].includes(last_cell_items[last_cell_items.length - 1].head.type)) {
495
- last_cell_items.pop();
496
- }
456
+ let allRows: TexNode[][];
457
+ if (closure.type === 'ordgroup') {
458
+ const elements = (closure as TexGroup).items;
459
+ // ignore spaces and '\n' before \end{envName}
460
+ while(elements.length > 0 && [TexTokenType.SPACE, TexTokenType.NEWLINE].includes(elements[elements.length - 1].head.type)) {
461
+ elements.pop();
497
462
  }
463
+ allRows = array_split(elements, new TexToken(TexTokenType.CONTROL, '\\\\').toNode())
464
+ .map(row => {
465
+ return array_split(row, new TexToken(TexTokenType.CONTROL, '&').toNode())
466
+ .map(arr => new TexGroup(arr));
467
+ });
468
+ } else {
469
+ allRows = [[closure]];
498
470
  }
499
471
 
500
472
  this.alignmentDepth--;
501
- return [allRows, pos + 1];
473
+ return [allRows, pos];
502
474
  }
503
475
  }
504
476
 
@@ -41,6 +41,7 @@ export const TEX_UNARY_COMMANDS = [
41
41
  'mathrel',
42
42
  'mathbin',
43
43
  'mathop',
44
+ 'not',
44
45
  ]
45
46
 
46
47
  export const TEX_BINARY_COMMANDS = [
package/src/tex-types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { assert } from "./util";
1
+ import { assert } from "./utils";
2
2
 
3
3
  /**
4
4
  * ELEMENT: 0-9, a-z, A-Z, punctuations such as +-/*,:; etc.
@@ -44,6 +44,8 @@ export class TexToken {
44
44
  }
45
45
 
46
46
  public static readonly EMPTY = new TexToken(TexTokenType.EMPTY, '');
47
+ public static readonly COMMAND_DISPLAYSTYLE = new TexToken(TexTokenType.COMMAND, '\\displaystyle');
48
+ public static readonly COMMAND_TEXTSTYLE = new TexToken(TexTokenType.COMMAND, '\\textstyle');
47
49
  }
48
50
 
49
51
 
package/src/tex-writer.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TexNode, TexToken, writeTexTokenBuffer } from "./tex-types";
1
+ import { TexNode, TexToken, TexTokenType, writeTexTokenBuffer } from "./tex-types";
2
2
 
3
3
 
4
4
 
@@ -12,6 +12,15 @@ export class TexWriter {
12
12
  }
13
13
 
14
14
  protected flushQueue() {
15
+ // remove \textstyle or \displaystyle if it is the end of the math code
16
+ while (this.queue.length > 0) {
17
+ const last_token = this.queue[this.queue.length - 1];
18
+ if (last_token.eq(TexToken.COMMAND_DISPLAYSTYLE) || last_token.eq(TexToken.COMMAND_TEXTSTYLE)) {
19
+ this.queue.pop();
20
+ } else {
21
+ break;
22
+ }
23
+ }
15
24
  for (let i = 0; i < this.queue.length; i++) {
16
25
  this.buffer = writeTexTokenBuffer(this.buffer, this.queue[i]);
17
26
  }