diff --git a/BuildVersion.props b/BuildVersion.props
index 83c5ff3..4730ad9 100644
--- a/BuildVersion.props
+++ b/BuildVersion.props
@@ -2,7 +2,7 @@
- 1.0.5
+ 1.0.6
diff --git a/src/SharpEquations/Internal/LaTeX/LaTeXReader.cs b/src/SharpEquations/Internal/LaTeX/LaTeXReader.cs
index f24899c..ad18073 100644
--- a/src/SharpEquations/Internal/LaTeX/LaTeXReader.cs
+++ b/src/SharpEquations/Internal/LaTeX/LaTeXReader.cs
@@ -385,6 +385,8 @@ private static (string Text, int Next) ScriptArgument(Text tex, int i)
["intop"] = "\\int", ["smallint"] = "\\int", ["diagup"] = "\\╱", ["diagdown"] = "\\╲", ["thickspace"] = "\\;",
["And"] = "\\&", ["lvert"] = "|", ["rvert"] = "|", ["idotsint"] = "\\∫⋯\\∫",
["mathscr"] = "\\mathcal", ["operatornamewithlimits"] = "\\operatorname", ["mathstrut"] = "\\vphantom{\\(}",
+ // \Re and \Im, the symbols ℜ and ℑ of LaTeX, which UnicodeMath reads as the function names Re and Im
+ ["Re"] = "ℜ", ["Im"] = "ℑ",
};
private static readonly HashSet OpeningBrackets = new HashSet(StringComparer.Ordinal) { "(", "[", "{", "⟨", "⌈", "⌊" };
@@ -891,6 +893,21 @@ private static string RewriteTeXOnly(string latex)
continue;
}
}
+ else if (cw is "sout" or "hcancel" && Argument(tex, j) is var (struck, afterStruck) && afterStruck >= 0)
+ {
+ // a horizontal strike: \sout of ulem (and of KaTeX), \hcancel
+ result.Append(Enclosure("horizontalstrike", RewriteTeXOnly(struck)));
+ i = afterStruck;
+ continue;
+ }
+ else if (cw == "enclose" && BracedArgument(tex, j) is var (notation, afterNotation) && afterNotation >= 0 &&
+ BracedArgument(tex, OptionalArgument(tex, afterNotation).Next) is var (enclosed, afterEnclosed) && afterEnclosed >= 0)
+ {
+ // \enclose{notation}[attributes]{body} of MathJax: the notations of MathML menclose, parted by commas
+ result.Append(Enclosure(notation, RewriteTeXOnly(enclosed)));
+ i = afterEnclosed;
+ continue;
+ }
else if (FontSwitches.TryGetValue(cw, out var font))
{
// an old font switch, {\rm rxn}, styles the rest of its group: \mathrm{rxn}
@@ -905,6 +922,40 @@ private static string RewriteTeXOnly(string latex)
return result.ToString();
}
+ // the notations of menclose that UnicodeMath has an operator for
+ private static readonly Dictionary EnclosureOperators = new Dictionary(StringComparer.Ordinal)
+ {
+ ["box"] = "▭", ["roundedbox"] = "▢", ["circle"] = "◯", ["longdiv"] = "⟌",
+ };
+
+ // the notations of menclose that UnicodeMath writes as the bits of the mask of ▭ (the sides are those it hides)
+ private static readonly Dictionary EnclosureBits = new Dictionary(StringComparer.Ordinal)
+ {
+ ["top"] = 1, ["bottom"] = 2, ["left"] = 4, ["right"] = 8, ["horizontalstrike"] = 16, ["verticalstrike"] = 32,
+ ["downdiagonalstrike"] = 64, ["updiagonalstrike"] = 128, ["box"] = 15, ["madruwb"] = 10,
+ };
+
+ private static readonly char[] NotationSeparators = { ',', ' ' };
+
+ ///
+ /// The UnicodeMath of a body in an enclosure of MathML notations (\enclose{top,bottom}{x}): its operator (⟌{x}), the
+ /// mask of ▭ (▭(28&{x})), or the square root of the radical; a box when none is known.
+ ///
+ private static string Enclosure(string notation, string body)
+ {
+ var notations = notation.Split(NotationSeparators, StringSplitOptions.RemoveEmptyEntries);
+ if (notations.Length == 1 && notations[0] == "radical")
+ return "\\sqrt{" + body + "}";
+ if (notations.Length == 1 && EnclosureOperators.TryGetValue(notations[0], out var op))
+ return op + "{" + body + "}";
+ // the actuarial, a combining mark before its body in UnicodeMath, and the ▭ of a mask, held through the passes
+ // (which would put the mark after a base, and take the mask for the argument of ▭) in a group of their own
+ if (notations.Length == 1 && notations[0] == "actuarial")
+ return "{{" + HoldMark("⃧") + "(" + body + ")}}";
+ int mask = notations.Sum(n => EnclosureBits.TryGetValue(n, out var bits) ? bits : 0);
+ return mask == 0 || mask == 15 ? "▭{" + body + "}" : "{" + HoldMark("▭") + "(" + (mask ^ 15) + "&{" + body + "})}";
+ }
+
// the font switches of plain TeX, which style the rest of their group, as the LaTeX commands with an argument
private static readonly Dictionary FontSwitches = new Dictionary(StringComparer.Ordinal)
{
diff --git a/src/SharpEquations/Internal/LaTeX/LaTeXSerializer.cs b/src/SharpEquations/Internal/LaTeX/LaTeXSerializer.cs
index dee8921..d27ec3e 100644
--- a/src/SharpEquations/Internal/LaTeX/LaTeXSerializer.cs
+++ b/src/SharpEquations/Internal/LaTeX/LaTeXSerializer.cs
@@ -28,13 +28,26 @@ internal sealed partial class LaTeXSerializer
["⎵"] = "underbracket", ["⏝"] = "underparen", ["⏟"] = "underbrace", ["⏡"] = "undershell", ["▁"] = "underbar",
};
+ // the enclosures of Word's LaTeX, as TeX.js writes them (the others are boxed)
private static readonly Dictionary Enclosures = new Dictionary
{
- ["box"] = "boxed", ["top"] = "", ["bottom"] = "", ["roundedbox"] = "", ["circle"] = "circle", ["longdiv"] = "", ["actuarial"] = "",
- ["cancel"] = "cancel", ["bcancel"] = "bcancel", ["xcancel"] = "xcancel", ["updiagonalstrike"] = "cancel",
+ ["box"] = "boxed", ["circle"] = "circle", ["cancel"] = "cancel", ["bcancel"] = "bcancel", ["xcancel"] = "xcancel", ["updiagonalstrike"] = "cancel",
["downdiagonalstrike"] = "bcancel", ["updiagonalstrike downdiagonalstrike"] = "xcancel", ["downdiagonalstrike updiagonalstrike"] = "xcancel",
};
+ // the enclosures of standard LaTeX that have a command: \boxed, those of the cancel package, \sout of ulem (and
+ // KaTeX) and \sqrt; the others are \enclose of MathJax
+ private static readonly Dictionary StandardEnclosures = new Dictionary
+ {
+ ["box"] = "boxed", ["cancel"] = "cancel", ["bcancel"] = "bcancel", ["xcancel"] = "xcancel", ["updiagonalstrike"] = "cancel",
+ ["downdiagonalstrike"] = "bcancel", ["updiagonalstrike downdiagonalstrike"] = "xcancel", ["downdiagonalstrike updiagonalstrike"] = "xcancel",
+ ["horizontalstrike"] = "sout", ["radical"] = "sqrt",
+ };
+
+ private static readonly char[] NotationSeparators = { ' ' };
+
+ private static readonly string[] Sides = { "top", "bottom", "left", "right" };
+
private static readonly (string Environment, string Intent)[] MatrixIntents =
{
("pmatrix", ":parenthesized-matrix"), ("vmatrix", ":determinant"), ("Vmatrix", ":normed-matrix"), ("bmatrix", ":bracketed-matrix"),
@@ -333,14 +346,18 @@ private string TeX(XElement value, bool noAddParens = false)
ret = Unary(value, notation == "top" ? "\\bar" : "\\underline");
break;
}
- if (standard && notation == "circle")
+ // no notation is a box, as UnicodeMath writes it (▭), and so are the four sides
+ var notations = (notation ?? "").Split(NotationSeparators, StringSplitOptions.RemoveEmptyEntries);
+ if (notations.Length == 0 || notations.Length == 4 && Sides.All(notations.Contains))
notation = "box";
- if (!string.IsNullOrEmpty(notation) && Enclosures.TryGetValue(notation, out var enclosure) && enclosure.Length > 0)
+ if ((standard ? StandardEnclosures : Enclosures).TryGetValue(notation, out var enclosure))
{
ret = Unary(value, "\\" + enclosure);
break;
}
- ret = Unary(value, "\\boxed");
+ // in standard LaTeX the others (longdiv, roundedbox, circle, left, verticalstrike...) are \enclose of
+ // MathJax, with its notations parted by commas
+ ret = standard ? "\\enclose{" + string.Join(",", notations) + "}{" + Nary(value, "", cNode) + "}" : Unary(value, "\\boxed");
break;
}
@@ -558,7 +575,7 @@ private string TeX(XElement value, bool noAddParens = false)
if (standard && val == "&")
return "\\&";
if (standard && IsFunctionName(val))
- return "\\" + val + " ";
+ return FunctionName(val);
if (OperatorMap.TryGetValue(val, out var mapped))
return mapped;
// escaped for the reader, in standard LaTeX its command if it has one (Ⓢ, \circledS)
@@ -649,7 +666,7 @@ private string TeX(XElement value, bool noAddParens = false)
}
else if (IsFunctionName(Text(value)))
{
- ret = "\\" + Text(value) + " ";
+ ret = FunctionName(Text(value));
break;
}
else if (Len(Text(value)) > 1 && SharpEquations.Internal.Text.AsciiPatterns.Letters().IsMatch(Text(value)))
@@ -805,8 +822,14 @@ private static bool IsTallFence(XElement value, string intent)
middle.Any(e => e.DescendantsAndSelf().Any(d => TallElements.Contains(Name(d))));
}
- private const string OpeningFences = "([{⟨|‖⌈⌊";
- private const string ClosingFences = ")]}⟩|‖⌉⌋";
+ // the angle brackets also as MTEF writes them, 〈 and 〉 (U+2329 and U+232A), and as CJK brackets (U+3008 and U+3009)
+ private const string OpeningFences = "([{⟨〈〈|‖⌈⌊";
+ private const string ClosingFences = ")]}⟩〉〉|‖⌉⌋";
+
+ ///
+ /// A function name as its command (\sin); Re and Im as \operatorname, \Re and \Im being the symbols ℜ and ℑ.
+ ///
+ private static string FunctionName(string name) => name is "Re" or "Im" ? "\\operatorname{" + name + "}" : "\\" + name + " ";
/// An mrow between fences that do not pair (]𝑎[, or one side only), read back only with \left and \right.
private static bool IsUnpairedFence(XElement value, string intent)
@@ -916,7 +939,7 @@ private static string StandardIdentifier(XElement value)
if (text.Length == 0)
return "{}";
if (IsFunctionName(text))
- return "\\" + text + " ";
+ return FunctionName(text);
var plain = string.Concat(Characters(text).Where(c => c.Length == 1 && (c[0] >= 'A' && c[0] <= 'Z' || c[0] >= 'a' && c[0] <= 'z')));
return plain.Length == text.Length ? "\\operatorname{" + text + "}" : LaTeXSymbols.Math(text);
}
diff --git a/src/SharpEquations/Internal/LaTeX/LaTeXSymbols.cs b/src/SharpEquations/Internal/LaTeX/LaTeXSymbols.cs
index 99f4ecd..7a8df54 100644
--- a/src/SharpEquations/Internal/LaTeX/LaTeXSymbols.cs
+++ b/src/SharpEquations/Internal/LaTeX/LaTeXSymbols.cs
@@ -208,7 +208,9 @@ private static string Character(string ch)
case "_":
return "\\" + ch;
}
- if (Alphanumerics.TryGetValue(ch, out var styled))
+ // a letterlike symbol with a command of its own is the command (ℑ is \Im, not \mathfrak{I})
+ bool letterlike = ch.Length == 1 && ch[0] is >= '℀' and <= '⅏' && Commands.ContainsKey(ch);
+ if (!letterlike && Alphanumerics.TryGetValue(ch, out var styled))
{
var letter = Commands.TryGetValue(styled.Plain, out var greek) ? "\\" + greek + " " : styled.Plain;
// a capital Greek letter is upright in TeX (\Gamma): italic, it is \mathit{\Gamma}
diff --git a/src/SharpEquations/Resources/mtef2mml.xsl b/src/SharpEquations/Resources/mtef2mml.xsl
index 9041644..cd49f92 100644
--- a/src/SharpEquations/Resources/mtef2mml.xsl
+++ b/src/SharpEquations/Resources/mtef2mml.xsl
@@ -2330,11 +2330,6 @@
℘
-
-
- 𝒫
-
-
P
@@ -2480,9 +2475,9 @@
k
-
+
- l
+ ℓ
diff --git a/tests/SharpEquations.Tests/LaTeX/LaTeXReaderTests.cs b/tests/SharpEquations.Tests/LaTeX/LaTeXReaderTests.cs
index 83f19c0..9ba6a3e 100644
--- a/tests/SharpEquations.Tests/LaTeX/LaTeXReaderTests.cs
+++ b/tests/SharpEquations.Tests/LaTeX/LaTeXReaderTests.cs
@@ -90,7 +90,13 @@ public class LaTeXReaderTests
[Arguments(@"x ^ { 2 } _ i", @"x_i^2", @"𝑥_𝑖^2")]
[Arguments(@"\sum_{\substack{ix", @"\cancel{x}", @"\cancel{𝑥}", @"▭(143&𝑥)")]
[Arguments(@"", @"\bcancel{x}", @"\bcancel{𝑥}", @"▭(79&𝑥)")]
[Arguments(@"", @"\xcancel{x}", @"\xcancel{𝑥}", @"▭(207&𝑥)")]
- [Arguments(@"", null, null, @"◯𝑥")]
+ [Arguments(@"", @"\enclose{circle}{x}", @"\circle{𝑥}", @"◯𝑥")]
+ [Arguments(@"", @"\sout{x}", @"\boxed{𝑥}", @"▭(31&𝑥)")]
+ [Arguments(@"", @"\enclose{verticalstrike}{x}", @"\boxed{𝑥}", @"▭(47&𝑥)")]
+ [Arguments(@"", @"\enclose{longdiv}{x}", @"\boxed{𝑥}", @"⟌𝑥")]
+ [Arguments(@"", @"\enclose{roundedbox}{x}", @"\boxed{𝑥}", @"▢𝑥")]
+ [Arguments(@"", @"\enclose{actuarial}{x}", @"\boxed{𝑥}", "⃧𝑥")]
+ [Arguments(@"", @"\enclose{left}{x}", @"\boxed{𝑥}", @"▭(11&𝑥)")]
+ [Arguments(@"", @"\enclose{right}{x}", @"\boxed{𝑥}", @"▭(7&𝑥)")]
+ [Arguments(@"", @"\enclose{top,bottom}{x}", @"\boxed{𝑥}", @"▭(12&𝑥)")]
+ [Arguments(@"", @"\enclose{madruwb}{x}", @"\boxed{𝑥}", @"▭𝑥")]
+ [Arguments(@"", @"\sqrt{x}", @"\boxed{𝑥}", @"▭𝑥")]
+ [Arguments(@"", @"\boxed{x}\boxed{y}", @"\boxed{𝑥}\boxed{𝑦}", @"▭(0&𝑥)▭𝑦")]
+ [Arguments(@"", @"\ell\Im\Re\wp\hbar\aleph", @"ℓℑℜ℘ℏℵ", @"ℓℑℜ℘ℏℵ")]
+ [Arguments(@"", @"\operatorname{Re}z+\operatorname{Im}z", @"\operatorname{Re}𝑧+\operatorname{Im}𝑧", @"Re𝑧+Im𝑧")]
+ [Arguments("", @"m\langle n\rangle", "𝑚〈𝑛〉", "𝑚〈𝑛〉")]
+ [Arguments("", @"\left\langle\frac{a}{b}\right\rangle\left\langle\frac{a}{b}\right\rangle", null, "〈𝑎/𝑏 〉〈𝑎/𝑏 〉")]
+ [Arguments("", @"|a\rangle\langle b|", null, "|𝑎〉〈𝑏|")]
+ [Arguments("", @"\langle a|b\rangle", null, "〈𝑎|𝑏〉")]
[Arguments(@"", @"{}_a^cC_b", @"{}_𝑎^𝑐𝐶_𝑏", @"_𝑎^𝑐 𝐶_𝑏")]
[Arguments(@"", @"a\,b\quad c", @"𝑎 𝑏 𝑐", @"𝑎 𝑏 𝑐")]
[Arguments(@"", @"\phantom{x}", @"\phantom{𝑥}", @"⟡𝑥")]
@@ -80,10 +97,18 @@ public async Task WritesMathML(string mathML, string? standard, string? word, st
[Arguments("", @"\bcancel{x}")]
[Arguments("", @"\xcancel{x}")]
[Arguments("", @"\boxed{x}")]
+ [Arguments("", @"\sout{a+b}")]
+ [Arguments("", @"\enclose{verticalstrike}{x}")]
+ [Arguments("", @"\enclose{longdiv}{\frac{a}{b}}")]
+ [Arguments("", @"\enclose{roundedbox}{x}")]
+ [Arguments("", @"\enclose{circle}{x}")]
+ [Arguments("", @"\enclose{actuarial}{n}")]
+ [Arguments("", @"\enclose{left}{x}")]
public async Task StrikesAndBoxesReadBack(string mathML, string latex)
{
var equation = Equation.Parse(mathML, EquationFormat.MathML);
await Assert.That(equation.ToString(EquationFormat.LaTeX)).IsEqualTo(latex);
+ await Assert.That(Equation.Parse(latex, EquationFormat.LaTeX)).IsEqualTo(equation);
await Assert.That(Equation.Parse(latex, EquationFormat.LaTeX).ToString(EquationFormat.LaTeX)).IsEqualTo(latex);
await Assert.That(Equation.Parse(equation.ToString(EquationFormat.UnicodeMath), EquationFormat.UnicodeMath).ToString(EquationFormat.LaTeX)).IsEqualTo(latex);
}
diff --git a/tests/SharpEquations.Tests/Mtef/EquationNativeStreamTests.cs b/tests/SharpEquations.Tests/Mtef/EquationNativeStreamTests.cs
index 101596e..cd8e394 100644
--- a/tests/SharpEquations.Tests/Mtef/EquationNativeStreamTests.cs
+++ b/tests/SharpEquations.Tests/Mtef/EquationNativeStreamTests.cs
@@ -69,6 +69,43 @@ public async Task ConvertsSummationOperatorsWithBothLimits()
await Assert.That(math.Value.Any(IsArrow)).IsFalse();
}
+ ///
+ /// MathType objects of the palettes of strikes, symbols, fractions and fences, as standard LaTeX: the horizontal
+ /// strike (\sout), the long division (\enclose{longdiv}), the letterlike symbols (ℓ, ℑ, ℜ, ℘) and the angle brackets
+ /// of MTEF (〈 and 〉, U+2329 and U+232A); the LaTeX reads back to itself.
+ ///
+ [Test]
+ [Arguments("overlines.eqn", @"\sout{a}\sout{b}\sout{c}")]
+ [Arguments("symbols.eqn", @"a\partial b\nabla c\infty d\Im e\Re f\aleph g\angle h\perp i\diamond l\ell m\wp n\degree o\hbar pƛq\int r\hbar sƛt\int u\sum a\prod b\coprod c")]
+ [Arguments("fraction-and-rad.eqn", @"\frac{a}{b}\frac{a}{b}\frac{a}{b}\frac{a}{b}a/b\sqrt{a}\sqrt[b]{a}\enclose{longdiv}{a}\begin{matrix}a\\\enclose{longdiv}{b}\end{matrix}")]
+ [Arguments("operators.eqn", @"a\pm b\mp c\times d\div e\ast f\cdot g\circ h•i\otimes l\oplus m\langle n\rangle")]
+ [Arguments("bra-ket.eqn", @"\langle a|b\rangle")]
+ [Arguments("parentheses.eqn", @"(ab)[ab]\{ab\}\langle ab\rangle|ab|\|ab\||a\rangle\langle b|")]
+ public async Task WritesMathTypePalettesAsLaTeX(string file, string latex)
+ {
+ var equation = Read("npoi/" + file);
+ await Assert.That(equation.ToString(EquationFormat.LaTeX)).IsEqualTo(latex);
+ await Assert.That(Equation.Parse(latex, EquationFormat.LaTeX).ToString(EquationFormat.LaTeX)).IsEqualTo(latex);
+ }
+
+ /// Angle brackets of MTEF around other fences, one of them a bar alone.
+ [Test]
+ public async Task WritesTheAngleBracketsOfMathTypeAmongOtherFences()
+ {
+ await Assert.That(Read("npoi/nested-fences.eqn").ToString(EquationFormat.LaTeX))
+ .IsEqualTo(@"(0[a\{b\langle c\left|d\|e\lfloor f\lceil gc\rceil d\rfloor e\|f\right|g\rangle h\}i]0)");
+ }
+
+ [Test]
+ public async Task ReadsTheLetterlikeSymbolsOfMathType()
+ {
+ var identifiers = Read("npoi/symbols.eqn").ToMathML().Descendants(Mml + "mi").Select(e => e.Value).ToList();
+ await Assert.That(identifiers).Contains("ℓ");
+ await Assert.That(identifiers).Contains("℘");
+ await Assert.That(identifiers).DoesNotContain("𝓁");
+ await Assert.That(identifiers).DoesNotContain("𝒫");
+ }
+
///
/// The spaces of Equation Editor 3.0 (0xEB00 to 0xEB08 in the space typeface), also in MathType equations first
/// written with it, are spaces, not the arrows of MathType's encoding with the same codes.
diff --git a/tests/SharpEquations.Tests/UnicodeMath/UnicodeMathTests.cs b/tests/SharpEquations.Tests/UnicodeMath/UnicodeMathTests.cs
index deb1049..dd0fbaa 100644
--- a/tests/SharpEquations.Tests/UnicodeMath/UnicodeMathTests.cs
+++ b/tests/SharpEquations.Tests/UnicodeMath/UnicodeMathTests.cs
@@ -16,7 +16,7 @@ public class UnicodeMathTests
[Arguments(@"a=b#(1)", @"𝑎=𝑏#(1)", @"a=b\tag{1}")]
[Arguments(@"⒱(a&b@c&d)", @"⒱(𝑎&𝑏@𝑐&𝑑)", @"\begin{vmatrix}a&b\\c&d\end{vmatrix}")]
[Arguments(@"⬍x+⬆y+⬇z+⬌w", @"⬍𝑥+⬆𝑦+⬇𝑧+⬌𝑤", @"\smash{x}+\smash[t]{y}+\smash[b]{z}+\mathrlap{w}")]
- [Arguments(@"▭(1&x)+▭(2&x)+▭(4&x)+▭(8&x)+▭(16&x)+▭(32&x)", @"▭(1&𝑥)+▭(2&𝑥)+▭(4&𝑥)+▭(8&𝑥)+▭(16&𝑥)+▭(32&𝑥)", @"\boxed{x}+\boxed{x}+\boxed{x}+\boxed{x}+\boxed{x}+\boxed{x}")]
+ [Arguments(@"▭(1&x)+▭(2&x)+▭(4&x)+▭(8&x)+▭(16&x)+▭(32&x)", @"▭(1&𝑥)+▭(2&𝑥)+▭(4&𝑥)+▭(8&𝑥)+▭(16&𝑥)+▭(32&𝑥)", @"\enclose{right,left,bottom}{x}+\enclose{right,left,top}{x}+\enclose{right,bottom,top}{x}+\enclose{left,bottom,top}{x}+\enclose{horizontalstrike,right,left,bottom,top}{x}+\enclose{verticalstrike,right,left,bottom,top}{x}")]
[Arguments(@"_a^b x", @"_𝑎^𝑏 𝑥", @"{}_a^bx")]
[Arguments(@"_a x", @"_𝑎 𝑥", @"{}_ax")]
[Arguments(@"^b x", @"^𝑏 𝑥", @"{}^bx")]
diff --git a/tests/data/README.md b/tests/data/README.md
index 52df365..6021a41 100644
--- a/tests/data/README.md
+++ b/tests/data/README.md
@@ -43,6 +43,12 @@ Thilo Planz, and Javier Fernandez.
`mtef/go`: two "Equation Native" streams extracted from MathType OLE objects supplied by the user and attributed to the
Go project in the pasted source. `expected-MathML/` contains the corresponding MathML produced by SharpEquations.
+`mtef/npoi`: seven "Equation Native" streams (`*.eqn`) of the MathType objects of `equations-ole-all.doc` of the test
+data of NPOI (`testcases/test-data/document`), one paragraph of MathType's palettes each: `overlines` (horizontal
+strikes), `operators`, `symbols` (ℓ, ℑ, ℜ, ℘, ℏ, ℵ...), `fraction-and-rad` (with two long divisions), `bra-ket`,
+`parentheses` and `nested-fences` (angle brackets as MTEF writes them, U+2329 and U+232A), extracted from the
+`ObjectPool` of the document.
+
`mtef/mtef-rs`: the "Equation Native" stream extracted from the MathType OLE object with the duplicate filename,
supplied by the user and attributed to [hiro93/mtef-rs](https://github.com/hiro93/mtef-rs).
`expected-MathML/` contains the MathML produced by SharpEquations.
diff --git a/tests/data/mtef/npoi/bra-ket.eqn b/tests/data/mtef/npoi/bra-ket.eqn
new file mode 100644
index 0000000..9630b24
Binary files /dev/null and b/tests/data/mtef/npoi/bra-ket.eqn differ
diff --git a/tests/data/mtef/npoi/fraction-and-rad.eqn b/tests/data/mtef/npoi/fraction-and-rad.eqn
new file mode 100644
index 0000000..d2ea51b
Binary files /dev/null and b/tests/data/mtef/npoi/fraction-and-rad.eqn differ
diff --git a/tests/data/mtef/npoi/nested-fences.eqn b/tests/data/mtef/npoi/nested-fences.eqn
new file mode 100644
index 0000000..35ff0dd
Binary files /dev/null and b/tests/data/mtef/npoi/nested-fences.eqn differ
diff --git a/tests/data/mtef/npoi/operators.eqn b/tests/data/mtef/npoi/operators.eqn
new file mode 100644
index 0000000..27d570c
Binary files /dev/null and b/tests/data/mtef/npoi/operators.eqn differ
diff --git a/tests/data/mtef/npoi/overlines.eqn b/tests/data/mtef/npoi/overlines.eqn
new file mode 100644
index 0000000..c1438fe
Binary files /dev/null and b/tests/data/mtef/npoi/overlines.eqn differ
diff --git a/tests/data/mtef/npoi/parentheses.eqn b/tests/data/mtef/npoi/parentheses.eqn
new file mode 100644
index 0000000..2bf89d5
Binary files /dev/null and b/tests/data/mtef/npoi/parentheses.eqn differ
diff --git a/tests/data/mtef/npoi/symbols.eqn b/tests/data/mtef/npoi/symbols.eqn
new file mode 100644
index 0000000..d348945
Binary files /dev/null and b/tests/data/mtef/npoi/symbols.eqn differ