From a8cb36cabfbd554e271b72fbf9073a8a5db044c7 Mon Sep 17 00:00:00 2001 From: Thomas M Kehrenberg Date: Tue, 24 Mar 2026 11:40:15 +0100 Subject: [PATCH] Remove `Node::Fenced` --- crates/math-core/src/character_class.rs | 139 +++++++++++++++++- crates/math-core/src/environments.rs | 47 +++--- crates/math-core/src/parser.rs | 58 ++++---- ...r__tests__displaystyle_ended_by_right.snap | 46 ++++-- .../math_core__parser__tests__genfrac.snap | 36 +++-- .../math_core__parser__tests__matrix.snap | 46 ++++-- .../conversion_test__equal_before_right.snap | 6 +- ...on_test__left_right_different_stretch.snap | 2 +- .../conversion_test__middle_bracket.snap | 20 ++- .../conversion_test__middle_uparrow.snap | 20 ++- .../conversion_test__middle_vert.snap | 20 ++- ...version_test__overset_with_left_right.snap | 6 +- .../conversion_test__sine_at_left.snap | 6 +- .../conversion_test__sine_at_right.snap | 6 +- .../conversion_test__stretchy_brace.snap | 14 +- .../conversion_test__stretchy_bracket.snap | 14 +- .../tests/snapshots/wiki_test__wiki141.snap | 2 +- .../tests/snapshots/wiki_test__wiki142.snap | 12 +- .../tests/snapshots/wiki_test__wiki143.snap | 8 +- .../tests/snapshots/wiki_test__wiki209.snap | 8 +- .../tests/snapshots/wiki_test__wiki217.snap | 38 +++-- crates/mathml-renderer/src/ast.rs | 49 +----- crates/mathml-renderer/src/symbol.rs | 102 +------------ 23 files changed, 357 insertions(+), 348 deletions(-) diff --git a/crates/math-core/src/character_class.rs b/crates/math-core/src/character_class.rs index fe83be80..5688426b 100644 --- a/crates/math-core/src/character_class.rs +++ b/crates/math-core/src/character_class.rs @@ -1,4 +1,9 @@ -use mathml_renderer::attribute::TextTransform; +use mathml_renderer::{ + arena::Arena, + ast::Node, + attribute::{MathSpacing, OpAttrs, RowAttr, Style, TextTransform}, + symbol::{self, MathMLOperator, OrdCategory, OrdLike, Rel, RelCategory}, +}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum Class { @@ -39,6 +44,138 @@ pub enum MathVariant { Transform(TextTransform), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Stretchy { + /// The operator is always stretchy (e.g. `(`, `)`). + Always = 1, + /// The operator is only stretchy as a pre- or postfix operator (e.g. `|`). + PrePostfix, + /// The operator is never stretchy (e.g. `/`). + Never, + /// The operator is always stretchy but isn't symmetric (e.g. `↑`). + AlwaysAsymmetric, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DelimiterSpacing { + /// Never has any spacing, even when used as an infix operator (e.g. `(`, `)`). + Zero, + /// Has relation spacing when used as an infix operator, but not when used as a prefix or + /// postfix operator (e.g. `|`). + InfixRelation, + /// Always has relation spacing, even when used as a prefix or postfix operator (e.g. `↑`). + Relation, + /// Always has some spacing, even when used as a prefix or postfix operator (e.g. `/`). + Other, +} + +/// A stretchable operator. +/// +/// It can be created from an `OrdLike` or a `Rel` if the operator is stretchable. This struct +/// carries all the information needed to know how to make the operator stretchy and how to set +/// spacing around it. +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub struct StretchableOp { + op: MathMLOperator, + pub stretchy: Stretchy, + pub spacing: DelimiterSpacing, +} + +impl StretchableOp { + #[inline] + pub const fn as_op(self) -> MathMLOperator { + self.op + } + + /// Creates a `StretchableOp` from an `OrdLike` if it's stretchable. Returns `None` if the + /// operator isn't stretchable. + pub const fn from_ord(ord: OrdLike) -> Option { + let (stretchy, spacing) = match ord.category() { + OrdCategory::F | OrdCategory::G => (Stretchy::Always, DelimiterSpacing::Zero), + OrdCategory::FGandForceDefault => { + (Stretchy::PrePostfix, DelimiterSpacing::InfixRelation) + } + OrdCategory::K => (Stretchy::Never, DelimiterSpacing::Zero), + OrdCategory::KButUsedToBeB => (Stretchy::Never, DelimiterSpacing::Other), + OrdCategory::D | OrdCategory::E | OrdCategory::I | OrdCategory::IK => { + return None; + } + }; + Some(StretchableOp { + op: ord.as_op(), + stretchy, + spacing, + }) + } + + /// Creates a `StretchableOp` from a `Rel` if it's stretchable. Returns `None` if the operator + /// isn't stretchable. + pub const fn from_rel(rel: Rel) -> Option { + match rel.category() { + RelCategory::A => Some(StretchableOp { + op: rel.as_op(), + stretchy: Stretchy::AlwaysAsymmetric, + spacing: DelimiterSpacing::Relation, + }), + RelCategory::Default => None, + } + } +} + +/// Creates a fenced expression where opening and closing delimiters are stretched to fit the height +/// of the content. If `open` or `close` is `None`, no delimiter will be rendered on that side. +pub fn fenced<'arena>( + arena: &'arena Arena, + mut content: Vec<&'arena Node<'arena>>, + open: Option, + close: Option, + style: Option