mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-15 06:52:23 +00:00
* Add craft-math: the equation tree with OMML, MathML, LaTeX and UnicodeMath
The math tree, LaTeX, linear-format, symbol and tree-walk modules come
from craft-wordmodel (storytold/craft-libs#963, WordCraft's crates/doc at
07f97737) unchanged, with the run colour (Rgb) and the symbol-font tables
they use. MathML is WordCraft's crates/formats/src/mathml.rs and OMML is
WordCraft's crates/docx/src/read/math.rs and write/math.rs (06c2ae6f /
07f97737), moved onto craft-xml. The HTML-specific MathML tests stay in
WordCraft; the rest moved with the code.
mathml::parse and omml::parse/read_str/write_str are new: string entry
points for callers that have no tree of their own.
* craft-math: read MathLive's and other spellings of LaTeX math commands
\abs{...} (an absolute value), \lvert \rvert \lVert \rVert, \nparallel,
\backprime, \infinity, \cross, \differentialD, \exponentialE and
\imaginaryI (MathLive's d, upright e and i), \emph, \textsf, \hbox,
\mathnormal and \underleftarrow / \underrightarrow /
\underleftrightarrow were unknown and read as literal text. They are read
as equaSHion's typed-input parser reads them (storytold/craft-libs#29).
* craft-math: equaSHion's equation corpus as test data
corpus/equations.tsv, corpus/latex.tsv and the OMML samples of
storytold/craft-libs#29 (7a6ef6b9), unmodified.
* craft-math: corpus round trips, GridCraft's OMML cases, entry points and limits tested; README
The corpus equations go through OMML, MathML and LaTeX and back; the OMML
samples are written back as kept and from the tree. GridCraft's OMML to
LaTeX test cases (crates/xlsx/src/omml.rs) give LaTeX that reads back the
same through the tree. omml::parse and read_str are tested against other
prefixes, the strict namespace, kept OMML, junk, the element cap and deep
nesting.
---------
Co-authored-by: shuff57 <62350898+shuff57@users.noreply.github.com>
42 lines
3.3 KiB
Rust
42 lines
3.3 KiB
Rust
//! OMML read into the tree and written as LaTeX means what GridCraft's direct OMML-to-LaTeX
|
|
//! reader (`crates/xlsx/src/omml.rs`) gave for its test cases, so GridCraft can read equations in
|
|
//! drawings through this crate. The LaTeX is compared by what it reads back as, not byte for byte.
|
|
|
|
use craft_math::{math, math_latex, omml};
|
|
fn tex(inner: &str) -> String {
|
|
let x = format!(r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">{inner}</m:oMath>"#);
|
|
math_latex::to_latex(&omml::read_str(&x).unwrap().nodes)
|
|
}
|
|
#[test]
|
|
fn gridcraft_omml_cases_mean_the_same_latex() {
|
|
let brace = |chr: &str, pos: &str| {
|
|
format!(
|
|
"<m:groupChr><m:groupChrPr><m:chr m:val=\"{chr}\"/><m:pos m:val=\"{pos}\"/></m:groupChrPr><m:e><m:r><m:t>x</m:t></m:r></m:e></m:groupChr>"
|
|
)
|
|
};
|
|
let lim = "<m:lim><m:r><m:t>n</m:t></m:r></m:lim>";
|
|
let under = format!("<m:limLow><m:e>{}</m:e>{lim}</m:limLow>", brace("\u{23DF}", "bot"));
|
|
let cases: Vec<(String, &str)> = vec![
|
|
("<m:r><m:t>x+1</m:t></m:r>".into(), "x+1"),
|
|
("<m:f><m:num><m:r><m:t>a</m:t></m:r></m:num><m:den><m:r><m:t>b</m:t></m:r></m:den></m:f>".into(), "\\frac{a}{b}"),
|
|
("<m:sSup><m:e><m:r><m:t>x</m:t></m:r></m:e><m:sup><m:r><m:t>2</m:t></m:r></m:sup></m:sSup>".into(), "{x}^{2}"),
|
|
("<m:rad><m:radPr><m:degHide m:val=\"1\"/></m:radPr><m:deg/><m:e><m:r><m:t>π</m:t></m:r></m:e></m:rad>".into(), "\\sqrt{\\pi }"),
|
|
("<m:nary><m:naryPr><m:chr m:val=\"∑\"/><m:limLoc m:val=\"undOvr\"/></m:naryPr><m:sub><m:r><m:t>i=1</m:t></m:r></m:sub><m:sup><m:r><m:t>n</m:t></m:r></m:sup><m:e><m:r><m:t>i</m:t></m:r></m:e></m:nary>".into(), "\\sum_{i=1}^{n} {i}"),
|
|
("<m:nary><m:naryPr><m:subHide m:val=\"1\"/><m:supHide m:val=\"1\"/></m:naryPr><m:sub/><m:sup/><m:e><m:r><m:t>f</m:t></m:r></m:e></m:nary>".into(), "\\int {f}"),
|
|
("<m:d><m:dPr><m:begChr m:val=\"[\"/><m:endChr m:val=\"]\"/></m:dPr><m:e><m:m><m:mr><m:e><m:r><m:t>1</m:t></m:r></m:e><m:e><m:r><m:t>0</m:t></m:r></m:e></m:mr><m:mr><m:e><m:r><m:t>0</m:t></m:r></m:e><m:e><m:r><m:t>1</m:t></m:r></m:e></m:mr></m:m></m:e></m:d>".into(), "\\left[ \\begin{matrix}1 & 0 \\\\ 0 & 1\\end{matrix} \\right]"),
|
|
("<m:r><m:rPr><m:sty m:val=\"p\"/></m:rPr><m:t>sin</m:t></m:r><m:r><m:t>x</m:t></m:r>".into(), "\\sin x"),
|
|
("<m:r><m:rPr><m:scr m:val=\"double-struck\"/></m:rPr><m:t>R</m:t></m:r>".into(), "\\mathbb{R}"),
|
|
("<m:r><m:rPr><m:nor/></m:rPr><m:t>if {x}</m:t></m:r>".into(), "\\text{if \\{x\\}}"),
|
|
("<m:acc><m:accPr><m:chr m:val=\"\u{0302}\"/></m:accPr><m:e><m:r><m:t>x</m:t></m:r></m:e></m:acc>".into(), "\\hat{x}"),
|
|
(under.clone(), "\\underbrace{x}_{n}"),
|
|
(format!("<m:sSup><m:e>{under}</m:e><m:sup><m:r><m:t>2</m:t></m:r></m:sup></m:sSup>"), "{\\underbrace{x}_{n}}^{2}"),
|
|
(format!("<m:limUpp><m:e>{}</m:e>{lim}</m:limUpp>", brace("\u{23DE}", "top")), "\\overbrace{x}^{n}"),
|
|
(format!("<m:limLow><m:e><m:r><m:t>a</m:t></m:r></m:e>{lim}</m:limLow>"), "\\underset{n}{a}"),
|
|
(format!("<m:limLow><m:e>{}<m:r><m:t>+1</m:t></m:r></m:e>{lim}</m:limLow>", brace("\u{23DF}", "bot")), "\\underset{n}{\\underbrace{x}+1}"),
|
|
];
|
|
for (x, want) in cases {
|
|
let got = tex(&x);
|
|
assert_eq!(math::to_linear(&math_latex::parse_latex(&got)), math::to_linear(&math_latex::parse_latex(want)), "{x}: {got} for {want}");
|
|
}
|
|
}
|