temml 0.10.3 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
package/dist/temml.mjs CHANGED
@@ -1759,125 +1759,102 @@ for (let i = 0; i < 10; i++) {
1759
1759
  * much of this module.
1760
1760
  */
1761
1761
 
1762
- function setLineBreaks(expression, wrapMode, isDisplayMode, color) {
1763
- if (color === undefined && wrapMode !== "none") {
1764
- // First, make one pass through the expression and split any color nodes.
1765
- const upperLimit = expression.length - 1;
1766
- for (let i = upperLimit; i >= 0; i--) {
1767
- const node = expression[i];
1768
- if (node.type === "mstyle" && node.attributes.mathcolor) {
1769
- const color = node.attributes.mathcolor;
1770
- const fragment = setLineBreaks(node.children, wrapMode, isDisplayMode, color);
1771
- if (!(fragment.type && fragment.type !== "mtable")) {
1772
- expression.splice(i, 1, ...fragment.children);
1773
- }
1774
- }
1775
- }
1776
- }
1777
-
1778
- const tagName = color ? "mstyle" : "mrow";
1779
-
1762
+ function setLineBreaks(expression, wrapMode, isDisplayMode) {
1780
1763
  const mtrs = [];
1781
1764
  let mrows = [];
1782
1765
  let block = [];
1783
1766
  let numTopLevelEquals = 0;
1784
1767
  let canBeBIN = false; // The first node cannot be an infix binary operator.
1785
- for (let i = 0; i < expression.length; i++) {
1786
- const node = expression[i];
1787
- if (node.type && node.type === "mstyle" && node.attributes.mathcolor) {
1788
- if (block.length > 0) {
1789
- // Start a new block. (Insert a soft linebreak.)
1790
- mrows.push(new mathMLTree.MathNode(tagName, block));
1791
- }
1792
- // Insert the mstyle
1793
- mrows.push(node);
1794
- block = [];
1795
- continue
1768
+ let i = 0;
1769
+ while (i < expression.length) {
1770
+ while (expression[i] instanceof DocumentFragment) {
1771
+ expression.splice(i, 1, ...expression[i].children); // Expand the fragment.
1796
1772
  }
1773
+ const node = expression[i];
1797
1774
  if (node.attributes && node.attributes.linebreak &&
1798
1775
  node.attributes.linebreak === "newline") {
1799
1776
  // A hard line break. Create a <mtr> for the current block.
1800
1777
  if (block.length > 0) {
1801
- const element = new mathMLTree.MathNode(tagName, block);
1802
- if (color) { element.setAttribute("mathcolor", color); }
1803
- mrows.push(new mathMLTree.MathNode(tagName, block));
1778
+ mrows.push(new mathMLTree.MathNode("mrow", block));
1804
1779
  }
1805
1780
  mrows.push(node);
1806
1781
  block = [];
1807
1782
  const mtd = new mathMLTree.MathNode("mtd", mrows);
1783
+ mtd.style.textAlign = "left";
1808
1784
  mtrs.push(new mathMLTree.MathNode("mtr", [mtd]));
1809
1785
  mrows = [];
1786
+ i += 1;
1810
1787
  continue
1811
1788
  }
1812
1789
  block.push(node);
1813
- if (node.type && node.type === "mo" && wrapMode === "=") {
1814
- if (node.children.length === 1 && node.children[0].text === "=") {
1790
+ if (node.type && node.type === "mo" && node.children.length === 1) {
1791
+ if (wrapMode === "=" && node.children[0].text === "=") {
1815
1792
  numTopLevelEquals += 1;
1816
1793
  if (numTopLevelEquals > 1) {
1817
1794
  block.pop();
1818
1795
  // Start a new block. (Insert a soft linebreak.)
1819
- const element = new mathMLTree.MathNode(tagName, block);
1820
- if (color) { element.setAttribute("mathcolor", color); }
1796
+ const element = new mathMLTree.MathNode("mrow", block);
1821
1797
  mrows.push(element);
1822
1798
  block = [node];
1823
1799
  }
1824
- }
1825
- } else if (node.type && node.type === "mo" && wrapMode === "tex") {
1826
- // This may be a place for a soft line break.
1827
- if (canBeBIN && !node.attributes.form) {
1828
- // Check if the following node is a \nobreak text node, e.g. "~""
1829
- const next = i < expression.length - 1 ? expression[i + 1] : null;
1830
- let glueIsFreeOfNobreak = true;
1831
- if (
1832
- !(
1833
- next &&
1834
- next.type === "mtext" &&
1835
- next.attributes.linebreak &&
1836
- next.attributes.linebreak === "nobreak"
1837
- )
1838
- ) {
1839
- // We may need to start a new block.
1840
- // First, put any post-operator glue on same line as operator.
1841
- for (let j = i + 1; j < expression.length; j++) {
1842
- const nd = expression[j];
1843
- if (
1844
- nd.type &&
1845
- nd.type === "mspace" &&
1846
- !(nd.attributes.linebreak && nd.attributes.linebreak === "newline")
1847
- ) {
1848
- block.push(nd);
1849
- i += 1;
1800
+ } else if (wrapMode === "tex") {
1801
+ // This may be a place for a soft line break.
1802
+ if (canBeBIN && !node.attributes.form) {
1803
+ // Check if the following node is a \nobreak text node, e.g. "~""
1804
+ const next = i < expression.length - 1 ? expression[i + 1] : null;
1805
+ let glueIsFreeOfNobreak = true;
1806
+ if (
1807
+ !(
1808
+ next &&
1809
+ next.type === "mtext" &&
1810
+ next.attributes.linebreak &&
1811
+ next.attributes.linebreak === "nobreak"
1812
+ )
1813
+ ) {
1814
+ // We may need to start a new block.
1815
+ // First, put any post-operator glue on same line as operator.
1816
+ for (let j = i + 1; j < expression.length; j++) {
1817
+ const nd = expression[j];
1850
1818
  if (
1851
- nd.attributes &&
1852
- nd.attributes.linebreak &&
1853
- nd.attributes.linebreak === "nobreak"
1819
+ nd.type &&
1820
+ nd.type === "mspace" &&
1821
+ !(nd.attributes.linebreak && nd.attributes.linebreak === "newline")
1854
1822
  ) {
1855
- glueIsFreeOfNobreak = false;
1823
+ block.push(nd);
1824
+ i += 1;
1825
+ if (
1826
+ nd.attributes &&
1827
+ nd.attributes.linebreak &&
1828
+ nd.attributes.linebreak === "nobreak"
1829
+ ) {
1830
+ glueIsFreeOfNobreak = false;
1831
+ }
1832
+ } else {
1833
+ break;
1856
1834
  }
1857
- } else {
1858
- break;
1859
1835
  }
1860
1836
  }
1837
+ if (glueIsFreeOfNobreak) {
1838
+ // Start a new block. (Insert a soft linebreak.)
1839
+ const element = new mathMLTree.MathNode("mrow", block);
1840
+ mrows.push(element);
1841
+ block = [];
1842
+ }
1843
+ canBeBIN = false;
1861
1844
  }
1862
- if (glueIsFreeOfNobreak) {
1863
- // Start a new block. (Insert a soft linebreak.)
1864
- const element = new mathMLTree.MathNode(tagName, block);
1865
- if (color) { element.setAttribute("mathcolor", color); }
1866
- mrows.push(element);
1867
- block = [];
1868
- }
1869
- canBeBIN = false;
1845
+ const isOpenDelimiter = node.attributes.form && node.attributes.form === "prefix";
1846
+ // Any operator that follows an open delimiter is unary.
1847
+ canBeBIN = !(node.attributes.separator || isOpenDelimiter);
1848
+ } else {
1849
+ canBeBIN = true;
1870
1850
  }
1871
- const isOpenDelimiter = node.attributes.form && node.attributes.form === "prefix";
1872
- // Any operator that follows an open delimiter is unary.
1873
- canBeBIN = !(node.attributes.separator || isOpenDelimiter);
1874
1851
  } else {
1875
1852
  canBeBIN = true;
1876
1853
  }
1854
+ i += 1;
1877
1855
  }
1878
1856
  if (block.length > 0) {
1879
- const element = new mathMLTree.MathNode(tagName, block);
1880
- if (color) { element.setAttribute("mathcolor", color); }
1857
+ const element = new mathMLTree.MathNode("mrow", block);
1881
1858
  mrows.push(element);
1882
1859
  }
1883
1860
  if (mtrs.length > 0) {
@@ -1964,6 +1941,48 @@ const consolidateText = mrow => {
1964
1941
  return mtext
1965
1942
  };
1966
1943
 
1944
+ const numberRegEx$1 = /^[0-9]$/;
1945
+ const isCommaOrDot = node => {
1946
+ return (node.type === "atom" && node.text === ",") ||
1947
+ (node.type === "textord" && node.text === ".")
1948
+ };
1949
+ const consolidateNumbers = expression => {
1950
+ // Consolidate adjacent numbers. We want to return <mn>1,506.3</mn>,
1951
+ // not <mn>1</mn><mo>,</mo><mn>5</mn><mn>0</mn><mn>6</mn><mi>.</mi><mn>3</mn>
1952
+ if (expression.length < 2) { return }
1953
+ const nums = [];
1954
+ let inNum = false;
1955
+ // Find adjacent numerals
1956
+ for (let i = 0; i < expression.length; i++) {
1957
+ const node = expression[i];
1958
+ if (node.type === "textord" && numberRegEx$1.test(node.text)) {
1959
+ if (!inNum) { nums.push({ start: i }); }
1960
+ inNum = true;
1961
+ } else {
1962
+ if (inNum) { nums[nums.length - 1].end = i - 1; }
1963
+ inNum = false;
1964
+ }
1965
+ }
1966
+ if (inNum) { nums[nums.length - 1].end = expression.length - 1; }
1967
+
1968
+ // Determine if numeral groups are separated by a comma or dot.
1969
+ for (let i = nums.length - 1; i > 0; i--) {
1970
+ if (nums[i - 1].end === nums[i].start - 2 && isCommaOrDot(expression[nums[i].start - 1])) {
1971
+ // Merge the two groups.
1972
+ nums[i - 1].end = nums[i].end;
1973
+ nums.splice(i, 1);
1974
+ }
1975
+ }
1976
+
1977
+ // Consolidate the number nodes
1978
+ for (let i = nums.length - 1; i >= 0; i--) {
1979
+ for (let j = nums[i].start + 1; j <= nums[i].end; j++) {
1980
+ expression[nums[i].start].text += expression[j].text;
1981
+ }
1982
+ expression.splice(nums[i].start + 1, nums[i].end - nums[i].start);
1983
+ }
1984
+ };
1985
+
1967
1986
  /**
1968
1987
  * Wrap the given array of nodes in an <mrow> node if needed, i.e.,
1969
1988
  * unless the array has length 1. Always returns a single node.
@@ -1999,6 +2018,8 @@ const buildExpression = function(expression, style, isOrdgroup) {
1999
2018
  return [group];
2000
2019
  }
2001
2020
 
2021
+ consolidateNumbers(expression);
2022
+
2002
2023
  const groups = [];
2003
2024
  for (let i = 0; i < expression.length; i++) {
2004
2025
  const group = buildGroup$1(expression[i], style);
@@ -3036,11 +3057,15 @@ const validateColor = (color, macros, token) => {
3036
3057
  };
3037
3058
 
3038
3059
  const mathmlBuilder$9 = (group, style) => {
3039
- const inner = buildExpression(group.body, style.withColor(group.color));
3040
- // Wrap with an <mstyle> element.
3041
- const node = wrapWithMstyle(inner);
3042
- node.setAttribute("mathcolor", group.color);
3043
- return node
3060
+ // In LaTeX, color is not supposed to change the spacing of any node.
3061
+ // So instead of wrapping the group in an <mstyle>, we apply
3062
+ // the color individually to each node and return a document fragment.
3063
+ let expr = buildExpression(group.body, style.withColor(group.color));
3064
+ expr = expr.map(e => {
3065
+ e.style.color = group.color;
3066
+ return e
3067
+ });
3068
+ return mathMLTree.newDocumentFragment(expr)
3044
3069
  };
3045
3070
 
3046
3071
  defineFunction({
@@ -7641,7 +7666,7 @@ const smallCaps = Object.freeze({
7641
7666
  // "mathord" and "textord" ParseNodes created in Parser.js from symbol Groups in
7642
7667
  // src/symbols.js.
7643
7668
 
7644
- const numberRegEx$1 = /^\d(?:[\d,.]*\d)?$/; // Keep in sync with numberRegEx in Parser.js
7669
+ const numberRegEx = /^\d(?:[\d,.]*\d)?$/;
7645
7670
  const latinRegEx = /[A-Ba-z]/;
7646
7671
 
7647
7672
  const italicNumber = (text, variant, tag) => {
@@ -7695,7 +7720,7 @@ defineFunctionBuilders({
7695
7720
  const variant = getVariant(group, style) || "normal";
7696
7721
 
7697
7722
  let node;
7698
- if (numberRegEx$1.test(group.text)) {
7723
+ if (numberRegEx.test(group.text)) {
7699
7724
  const tag = group.mode === "text" ? "mtext" : "mn";
7700
7725
  if (variant === "italic" || variant === "bold-italic") {
7701
7726
  return italicNumber(text, variant, tag)
@@ -8008,8 +8033,7 @@ const combiningDiacriticalMarksEndRegex = new RegExp(`${combiningDiacriticalMark
8008
8033
  const tokenRegexString =
8009
8034
  `(${spaceRegexString}+)|` + // whitespace
8010
8035
  `${controlSpaceRegexString}|` + // whitespace
8011
- "(number" + // numbers (in non-strict mode)
8012
- "|[!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint
8036
+ "([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint
8013
8037
  `${combiningDiacriticalMarkString}*` + // ...plus accents
8014
8038
  "|[\uD800-\uDBFF][\uDC00-\uDFFF]" + // surrogate pair
8015
8039
  `${combiningDiacriticalMarkString}*` + // ...plus accents
@@ -8024,12 +8048,7 @@ class Lexer {
8024
8048
  // Separate accents from characters
8025
8049
  this.input = input;
8026
8050
  this.settings = settings;
8027
- this.tokenRegex = new RegExp(
8028
- // Strict Temml, like TeX, lexes one numeral at a time.
8029
- // Default Temml lexes contiguous numerals into a single <mn> element.
8030
- tokenRegexString.replace("number|", settings.strict ? "" : "\\d(?:[\\d,.]*\\d)?|"),
8031
- "g"
8032
- );
8051
+ this.tokenRegex = new RegExp(tokenRegexString, 'g');
8033
8052
  // Category codes. The lexer only supports comment characters (14) for now.
8034
8053
  // MacroExpander additionally distinguishes active (13).
8035
8054
  this.catcodes = {
@@ -11797,8 +11816,6 @@ var unicodeSymbols = {
11797
11816
 
11798
11817
  /* eslint no-constant-condition:0 */
11799
11818
 
11800
- const numberRegEx = /^\d(?:[\d,.]*\d)?$/; // Keep in sync with numberRegEx in symbolsOrd.js
11801
-
11802
11819
  /**
11803
11820
  * This file contains the parser used to parse out a TeX expression from the
11804
11821
  * input. Since TeX isn't context-free, standard parsers don't work particularly
@@ -12720,15 +12737,6 @@ class Parser {
12720
12737
  };
12721
12738
  }
12722
12739
  symbol = s;
12723
- } else if (!this.strict && numberRegEx.test(text)) {
12724
- // A number. Wrap in a <mn> if in math mode; <mtext> otherwise.
12725
- this.consume();
12726
- return {
12727
- type: "textord",
12728
- mode: this.mode,
12729
- loc: SourceLocation.range(nucleus),
12730
- text
12731
- }
12732
12740
  } else if (text.charCodeAt(0) >= 0x80) {
12733
12741
  // no symbol for e.g. ^
12734
12742
  if (this.settings.strict) {
@@ -12968,7 +12976,7 @@ class Style {
12968
12976
  * https://mit-license.org/
12969
12977
  */
12970
12978
 
12971
- const version = "0.10.3";
12979
+ const version = "0.10.5";
12972
12980
 
12973
12981
  function postProcess(block) {
12974
12982
  const labelMap = {};
@@ -14,7 +14,7 @@
14
14
  * https://mit-license.org/
15
15
  */
16
16
 
17
- const version = "0.10.3";
17
+ const version = "0.10.5";
18
18
 
19
19
  function postProcess(block) {
20
20
  const labelMap = {};
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
+ "exports": {
7
+ ".": {
8
+ "require": "./dist/temml.cjs"
9
+ },
10
+ "./*": "./*"
11
+ },
6
12
  "homepage": "https://temml.org",
7
13
  "repository": {
8
14
  "type": "git",
@@ -24,7 +30,7 @@
24
30
  },
25
31
  "scripts": {
26
32
  "lint": "eslint temml.js src",
27
- "unit-test": "node -r esm ./test/unit-test.js",
33
+ "unit-test": "node ./test/unit-test.cjs",
28
34
  "visual-test": "node utils/buildTests.js",
29
35
  "test": "yarn lint && node utils/buildTests.js && yarn unit-test",
30
36
  "minify": "terser test/temml.js -o site/assets/temml.min.js -c -m && terser contrib/mhchem/mhchem.js -o site/assets/mhchem.min.js -c -m",
package/src/Lexer.js CHANGED
@@ -49,8 +49,7 @@ export const combiningDiacriticalMarksEndRegex = new RegExp(`${combiningDiacriti
49
49
  const tokenRegexString =
50
50
  `(${spaceRegexString}+)|` + // whitespace
51
51
  `${controlSpaceRegexString}|` + // whitespace
52
- "(number" + // numbers (in non-strict mode)
53
- "|[!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint
52
+ "([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint
54
53
  `${combiningDiacriticalMarkString}*` + // ...plus accents
55
54
  "|[\uD800-\uDBFF][\uDC00-\uDFFF]" + // surrogate pair
56
55
  `${combiningDiacriticalMarkString}*` + // ...plus accents
@@ -65,12 +64,7 @@ export default class Lexer {
65
64
  // Separate accents from characters
66
65
  this.input = input;
67
66
  this.settings = settings;
68
- this.tokenRegex = new RegExp(
69
- // Strict Temml, like TeX, lexes one numeral at a time.
70
- // Default Temml lexes contiguous numerals into a single <mn> element.
71
- tokenRegexString.replace("number|", settings.strict ? "" : "\\d(?:[\\d,.]*\\d)?|"),
72
- "g"
73
- );
67
+ this.tokenRegex = new RegExp(tokenRegexString, 'g');
74
68
  // Category codes. The lexer only supports comment characters (14) for now.
75
69
  // MacroExpander additionally distinguishes active (13).
76
70
  this.catcodes = {
package/src/Parser.js CHANGED
@@ -16,8 +16,6 @@ import { isDelimiter } from "./functions/delimsizing"
16
16
  import unicodeAccents from /*preval*/ "./unicodeAccents";
17
17
  import unicodeSymbols from /*preval*/ "./unicodeSymbols";
18
18
 
19
- const numberRegEx = /^\d(?:[\d,.]*\d)?$/ // Keep in sync with numberRegEx in symbolsOrd.js
20
-
21
19
  /**
22
20
  * This file contains the parser used to parse out a TeX expression from the
23
21
  * input. Since TeX isn't context-free, standard parsers don't work particularly
@@ -939,15 +937,6 @@ export default class Parser {
939
937
  };
940
938
  }
941
939
  symbol = s;
942
- } else if (!this.strict && numberRegEx.test(text)) {
943
- // A number. Wrap in a <mn> if in math mode; <mtext> otherwise.
944
- this.consume()
945
- return {
946
- type: "textord",
947
- mode: this.mode,
948
- loc: SourceLocation.range(nucleus),
949
- text
950
- }
951
940
  } else if (text.charCodeAt(0) >= 0x80) {
952
941
  // no symbol for e.g. ^
953
942
  if (this.settings.strict) {
@@ -75,6 +75,48 @@ export const consolidateText = mrow => {
75
75
  return mtext
76
76
  }
77
77
 
78
+ const numberRegEx = /^[0-9]$/
79
+ const isCommaOrDot = node => {
80
+ return (node.type === "atom" && node.text === ",") ||
81
+ (node.type === "textord" && node.text === ".")
82
+ }
83
+ const consolidateNumbers = expression => {
84
+ // Consolidate adjacent numbers. We want to return <mn>1,506.3</mn>,
85
+ // not <mn>1</mn><mo>,</mo><mn>5</mn><mn>0</mn><mn>6</mn><mi>.</mi><mn>3</mn>
86
+ if (expression.length < 2) { return }
87
+ const nums = [];
88
+ let inNum = false
89
+ // Find adjacent numerals
90
+ for (let i = 0; i < expression.length; i++) {
91
+ const node = expression[i];
92
+ if (node.type === "textord" && numberRegEx.test(node.text)) {
93
+ if (!inNum) { nums.push({ start: i }) }
94
+ inNum = true
95
+ } else {
96
+ if (inNum) { nums[nums.length - 1].end = i - 1 }
97
+ inNum = false
98
+ }
99
+ }
100
+ if (inNum) { nums[nums.length - 1].end = expression.length - 1 }
101
+
102
+ // Determine if numeral groups are separated by a comma or dot.
103
+ for (let i = nums.length - 1; i > 0; i--) {
104
+ if (nums[i - 1].end === nums[i].start - 2 && isCommaOrDot(expression[nums[i].start - 1])) {
105
+ // Merge the two groups.
106
+ nums[i - 1].end = nums[i].end
107
+ nums.splice(i, 1)
108
+ }
109
+ }
110
+
111
+ // Consolidate the number nodes
112
+ for (let i = nums.length - 1; i >= 0; i--) {
113
+ for (let j = nums[i].start + 1; j <= nums[i].end; j++) {
114
+ expression[nums[i].start].text += expression[j].text
115
+ }
116
+ expression.splice(nums[i].start + 1, nums[i].end - nums[i].start)
117
+ }
118
+ }
119
+
78
120
  /**
79
121
  * Wrap the given array of nodes in an <mrow> node if needed, i.e.,
80
122
  * unless the array has length 1. Always returns a single node.
@@ -110,6 +152,8 @@ export const buildExpression = function(expression, style, isOrdgroup) {
110
152
  return [group];
111
153
  }
112
154
 
155
+ consolidateNumbers(expression)
156
+
113
157
  const groups = [];
114
158
  for (let i = 0; i < expression.length; i++) {
115
159
  const group = buildGroup(expression[i], style);
@@ -1,5 +1,5 @@
1
1
  import defineFunction, { ordargument } from "../defineFunction"
2
- import { wrapWithMstyle } from "../mathMLTree"
2
+ import mathMLTree from "../mathMLTree"
3
3
  import { assertNodeType } from "../parseNode"
4
4
  import ParseError from "../ParseError"
5
5
  import * as mml from "../buildMathML"
@@ -152,11 +152,15 @@ export const validateColor = (color, macros, token) => {
152
152
  }
153
153
 
154
154
  const mathmlBuilder = (group, style) => {
155
- const inner = mml.buildExpression(group.body, style.withColor(group.color))
156
- // Wrap with an <mstyle> element.
157
- const node = wrapWithMstyle(inner)
158
- node.setAttribute("mathcolor", group.color)
159
- return node
155
+ // In LaTeX, color is not supposed to change the spacing of any node.
156
+ // So instead of wrapping the group in an <mstyle>, we apply
157
+ // the color individually to each node and return a document fragment.
158
+ let expr = mml.buildExpression(group.body, style.withColor(group.color))
159
+ expr = expr.map(e => {
160
+ e.style.color = group.color
161
+ return e
162
+ })
163
+ return mathMLTree.newDocumentFragment(expr)
160
164
  }
161
165
 
162
166
  defineFunction({
@@ -7,7 +7,7 @@ import * as mml from "../buildMathML"
7
7
  // "mathord" and "textord" ParseNodes created in Parser.js from symbol Groups in
8
8
  // src/symbols.js.
9
9
 
10
- const numberRegEx = /^\d(?:[\d,.]*\d)?$/ // Keep in sync with numberRegEx in Parser.js
10
+ const numberRegEx = /^\d(?:[\d,.]*\d)?$/
11
11
  const latinRegEx = /[A-Ba-z]/
12
12
 
13
13
  const italicNumber = (text, variant, tag) => {