Files
craft-libs/crates/math/src/lib.rs
T
Dylan M. Taylorandshuff57 bdf06f1029 Add craft-latex: LaTeX documents read and written, on craft-flow (stacked) (#1038)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-mime: detect, for bytes that name no character set

is_text, decode (BOM, unmarked UTF-16, UTF-8, else the legacy encoding
the bytes look like), legacy and legacy_label, behind a detect feature
that brings in chardetng. Moved from WordCraft's crates/formats/src/txt.rs
at 06c2ae6f with its text-sniffing tests. Also charset::is_known, so a
caller can tell a label decode would read as UTF-8 from one it knows.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* 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.

* Add craft-flow: the flow model the text formats map to, on craft-wordmodel

WordCraft's crates/formats/src/model (07f97737) moved as it is: Flow,
FBlock, Para, Inline, Fmt, tables and cells, pictures, to_doc and
from_doc, picture sniffing, colours, base64 and the Dropped report. Only
the paths changed (wordcraft_doc is craft_wordmodel); the report helpers
are public so the format crates can call them; the image re-encoding
helper stays with WordCraft's RTF writer.

* Add craft-markdown: Markdown read and written, on craft-flow

WordCraft's crates/formats/src/markdown (07f97737) with its 35 tests,
moved onto craft-flow and craft-wordmodel. Character references come from
craft-html. The table cell budget lives in craft-flow, which names it in
its warning. Two test lines that wrote plain text through WordCraft's txt
module stay in WordCraft.

* WIP: craft-latex sources from WordCraft (unmodified)

* Add craft-latex: LaTeX documents read and written, on craft-flow

WordCraft's crates/formats/src/latex (07f97737) with its 21 tests, moved
onto craft-flow and craft-wordmodel; bytes are decoded by craft-mime's
detect. The LaTeX parts of WordCraft's cross-format tests (nesting,
column specs, labels, no TeX commands from the document, the fuzz and
round-trip properties) come along as tests/hostile.rs. The ligature test
builds its three-font paragraph as a flow instead of reading HTML.

* craft-math: latex_text, LaTeX math and the linear format converted as text

WordCraft's crates/formats/src/latex/math.rs (07f97737): the converter its
LaTeX document reader and writer use, which keeps the source's spacing.
It moves here so craft-math is the one place that converts LaTeX math;
craft-latex calls it, and its output does not change. Its tests (from
WordCraft's latex tests and cross-format tests) come along.

* craft-math: latex_text's group finders are public, for craft-latex's length parser

* craft-latex: math through craft_math::latex_text; label timing test by ratio

The document math converter moved to craft-math (#1040) unchanged; this
crate calls it there (and its length parser uses its group finders), so
export output is the same. The label test compared one read with a
ten-second wall clock and failed on a loaded machine; it now compares the
fastest of three reads of 25,000 and 100,000 labels, which a linear read
keeps near 4x and a quadratic one near 16x.

---------

Co-authored-by: shuff57 <62350898+shuff57@users.noreply.github.com>
2026-11-11 20:58:36 -05:00

59 lines
2.5 KiB
Rust

#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
// The code came from an app whose lints allow this.
#![allow(clippy::type_complexity)]
//! Equations: one tree and the syntaxes it is read from and written to.
//!
//! - [`math`]: the tree ([`MNode`], [`MRun`], an argument [`Arg`] is a `Vec<MNode>`), modelled on
//! the Office Math object model (ECMA-376 Part 1 §22.1), and [`Math`], an equation with its kept
//! source OMML.
//! - [`math_linear`]: the linear format (UnicodeMath, the plain text Word shows for an equation),
//! read and written ([`math::parse_linear`], [`math::to_linear`]).
//! - [`math_latex`]: LaTeX math, read and written ([`math_latex::parse_latex`],
//! [`math_latex::to_latex`]), with the named symbols of [`math_symbols`].
//! - [`omml`]: Office Math Markup, read ([`omml::read_omath`], [`omml::read_str`]) and written
//! ([`omml::write_equation`], [`omml::write_str`]).
//! - [`mathml`]: presentation MathML, written ([`mathml::to_mathml`]) and read
//! ([`mathml::parse`], [`mathml::read`], [`mathml::to_tree`]).
//! - [`latex_text`]: LaTeX math and the linear format converted as text, keeping the source's
//! spacing, for LaTeX documents ([`latex_text::latex_to_linear`], [`latex_text::linear_to_latex`]).
//! - [`math_tree`]: the arguments of a node and a walk over every run.
//! - [`symbol_fonts`]: text in symbol fonts (Symbol, Wingdings…) as Unicode, as OMML runs and
//! Word text need it.
//! - [`Rgb`]: the colour of a run.
//!
//! Every reader is bounded (nesting [`math::MAX_DEPTH`], node budgets) and never panics on any
//! input.
//!
//! ```
//! use craft_math::{math, math_latex, mathml, omml};
//!
//! let tree = math::parse_linear("x=(-b±√(b^2-4ac))/2a");
//! assert_eq!(math::to_linear(&tree), "x=(-b±√(b^2-4ac))/2a");
//! assert!(math_latex::to_latex(&tree).contains("\\frac"));
//!
//! let eq = math::Math { nodes: tree.clone(), ..Default::default() };
//! let xml = omml::write_str("", false, &eq, None);
//! assert_eq!(omml::read_str(&xml).map(|m| m.nodes), Some(tree.clone()));
//!
//! let ml = mathml::to_mathml(&tree, false, "");
//! assert_eq!(mathml::parse(&ml).as_ref().map(mathml::to_tree), Some(tree));
//! ```
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeExamples;
pub mod latex_text;
pub mod math;
pub mod math_latex;
pub mod math_linear;
pub mod math_symbols;
pub mod math_tree;
pub mod mathml;
pub mod omml;
mod rgb;
pub mod symbol_fonts;
pub use math::{Arg, MNode, MRun, Math, MathProps};
pub use rgb::Rgb;