Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 45 additions & 34 deletions crates/math-core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ use mathml_renderer::{
arena::{Arena, Buffer},
ast::Node,
attribute::{
LetterAttr, MathSpacing, MathVariant, OpAttrs, ParenType, RowAttr, StretchMode, Style,
TextTransform,
LetterAttr, MathSpacing, MathVariant, OpAttrs, ParenType, RowAttr, Style, TextTransform,
},
length::Length,
symbol::{self, OpCategory, OrdCategory, RelCategory, StretchableOp},
symbol::{self, OpCategory, OrdCategory, RelCategory, StretchableOp, Stretchy},
};

use crate::{
Expand Down Expand Up @@ -908,15 +907,12 @@ where
node_vec_to_node(self.arena, &content, matches!(parse_as, ParseAs::Arg)),
));
}
ref tok @ (Token::Open(paren) | Token::Close(paren)) => 'open_close: {
ref tok @ (Token::Open(paren) | Token::Close(paren)) => {
let open = matches!(tok, Token::Open(_));
if open {
class = Class::Open;
}
let Some(stretchable_op) = paren.as_stretchable_op() else {
break 'open_close Err(LatexError(span.into(), LatexErrKind::Internal));
};
let attr = if matches!(paren.category(), OrdCategory::FGandForceDefault) {
let mut attr = if matches!(paren.category(), OrdCategory::FGandForceDefault) {
// For this category of symbol, we have to force the form attribute
// in order to get correct spacing.
if open {
Expand All @@ -927,31 +923,36 @@ where
} else {
OpAttrs::empty()
};
Ok(Node::StretchableOp(
stretchable_op,
StretchMode::NoStretch,
attr,
))
if matches!(
paren.category(),
OrdCategory::F | OrdCategory::G | OrdCategory::FGandForceDefault
) {
// Symbols from these categories are automatically stretchy,
// so we have to explicitly disable that here.
attr |= OpAttrs::STRETCHY_FALSE;
}
Ok(Node::Operator {
op: paren.as_op(),
attrs: attr,
left: None,
right: None,
})
}
Token::SquareBracketOpen => {
class = Class::Open;
const SQ_L_BRACKET: StretchableOp =
symbol::LEFT_SQUARE_BRACKET.as_stretchable_op().unwrap();
Ok(Node::StretchableOp(
SQ_L_BRACKET,
StretchMode::NoStretch,
OpAttrs::empty(),
))
}
Token::SquareBracketClose => {
const SQ_R_BRACKET: StretchableOp =
symbol::RIGHT_SQUARE_BRACKET.as_stretchable_op().unwrap();
Ok(Node::StretchableOp(
SQ_R_BRACKET,
StretchMode::NoStretch,
OpAttrs::empty(),
))
Ok(Node::Operator {
op: symbol::LEFT_SQUARE_BRACKET.as_op(),
attrs: OpAttrs::STRETCHY_FALSE,
left: None,
right: None,
})
}
Token::SquareBracketClose => Ok(Node::Operator {
op: symbol::RIGHT_SQUARE_BRACKET.as_op(),
attrs: OpAttrs::STRETCHY_FALSE,
left: None,
right: None,
}),
Token::Left => {
let tok_loc = self.next_token()?;
let open_paren = if matches!(tok_loc.token(), Token::Letter('.', Mode::MathOrText))
Expand Down Expand Up @@ -982,11 +983,12 @@ where
Token::Middle => {
let tok_loc = self.next_token()?;
let op = extract_delimiter(tok_loc, DelimiterModifier::Middle)?;
Ok(Node::StretchableOp(
op,
StretchMode::Middle,
OpAttrs::empty(),
))
Ok(Node::Operator {
op: op.as_op(),
attrs: middle_stretch_attrs(op),
left: None,
right: None,
})
}
Token::Big(size, paren_type) => {
let tok_loc = self.next_token()?;
Expand Down Expand Up @@ -1761,6 +1763,15 @@ pub(crate) fn node_vec_to_node<'arena>(
}
}

/// Get the attributes for a middle operator (which needs to stretch symmetrically).
fn middle_stretch_attrs(op: StretchableOp) -> OpAttrs {
match op.stretchy {
Stretchy::PrePostfix | Stretchy::Never => OpAttrs::STRETCHY_TRUE,
Stretchy::AlwaysAsymmetric => OpAttrs::SYMMETRIC_TRUE,
_ => OpAttrs::empty(),
}
}

fn extract_delimiter(tok: TokSpan<'_>, location: DelimiterModifier) -> ParseResult<StretchableOp> {
let (tok, span) = tok.into_parts();
const SQ_L_BRACKET: StretchableOp = symbol::LEFT_SQUARE_BRACKET.as_stretchable_op().unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,25 @@ expression: "\\pmod{3}_4"
value: 1.0,
unit: Em,
)),
StretchableOp(StretchableOp('(', Always), NoStretch, OpAttrs("")),
Operator(
op: '(',
attrs: OpAttrs("STRETCHY_FALSE"),
left: None,
right: None,
),
IdentifierStr("mod"),
Space(Length(
value: 0.33333334,
unit: Em,
)),
Number("3"),
Sub(
target: StretchableOp(StretchableOp(')', Always), NoStretch, OpAttrs("")),
target: Operator(
op: ')',
attrs: OpAttrs("STRETCHY_FALSE"),
left: None,
right: None,
),
symbol: Number("4"),
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ expression: "x\\bra{\\uparrow} + \\ket{\\downarrow}y"
<mi>x</mi>
<mo stretchy="false">⟨</mo>
<mo stretchy="false" lspace="0" rspace="0">↑</mo>
<mo form="postfix" stretchy="false">|</mo>
<mo stretchy="false" form="postfix">|</mo>
<mo>+</mo>
<mo form="prefix" stretchy="false">|</mo>
<mo stretchy="false" form="prefix">|</mo>
<mo stretchy="false" lspace="0" rspace="0">↓</mo>
<mo stretchy="false">⟩</mo>
<mi>y</mi>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ expression: "x + \\lVert + y + \\rVert + z"
<math>
<mi>x</mi>
<mo>+</mo>
<mo form="prefix" stretchy="false">‖</mo>
<mo stretchy="false" form="prefix">‖</mo>
<mo lspace="0" rspace="0">+</mo>
<mi>y</mi>
<mo lspace="0" rspace="0">+</mo>
<mo form="postfix" stretchy="false">‖</mo>
<mo stretchy="false" form="postfix">‖</mo>
<mo>+</mo>
<mi>z</mi>
</math>
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ expression: "x + \\lvert + y + \\rvert + z"
<math>
<mi>x</mi>
<mo>+</mo>
<mo form="prefix" stretchy="false">|</mo>
<mo stretchy="false" form="prefix">|</mo>
<mo lspace="0" rspace="0">+</mo>
<mi>y</mi>
<mo lspace="0" rspace="0">+</mo>
<mo form="postfix" stretchy="false">|</mo>
<mo stretchy="false" form="postfix">|</mo>
<mo>+</mo>
<mi>z</mi>
</math>
4 changes: 2 additions & 2 deletions crates/math-core/tests/snapshots/wiki_test__wiki020.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ expression: "\\Pr j, \\hom l, \\lVert z \\rVert, \\arg z"
<mo lspace="0" rspace="0.1667em">hom</mo>
<mi>l</mi>
<mo>,</mo>
<mo form="prefix" stretchy="false">‖</mo>
<mo stretchy="false" form="prefix">‖</mo>
<mi>z</mi>
<mo form="postfix" stretchy="false">‖</mo>
<mo stretchy="false" form="postfix">‖</mo>
<mo>,</mo>
<mo lspace="0" rspace="0.1667em">arg</mo>
<mi>z</mi>
Expand Down
39 changes: 7 additions & 32 deletions crates/mathml-renderer/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::Serialize;

use crate::attribute::{
FracAttr, HtmlTextStyle, LetterAttr, MathSpacing, Notation, OpAttrs, ParenType, RowAttr, Size,
StretchMode, Style,
Style,
};
use crate::fmt::new_line_and_indent;
use crate::itoa::append_u8_as_hex;
Expand All @@ -31,7 +31,6 @@ pub enum Node<'arena> {
left: Option<MathSpacing>,
right: Option<MathSpacing>,
},
StretchableOp(StretchableOp, StretchMode, OpAttrs),
/// `<mo>...</mo>` for a string.
PseudoOp {
attrs: OpAttrs,
Expand Down Expand Up @@ -198,9 +197,6 @@ impl Node<'_> {
write!(s, "</mrow>")?;
}
}
Node::StretchableOp(op, stretch_mode, attr) => {
emit_stretchy_op(s, *stretch_mode, Some(*op), *attr)?;
}
Node::Operator {
op,
attrs: attr,
Expand Down Expand Up @@ -409,11 +405,11 @@ impl Node<'_> {
None => write!(s, "<mrow>")?,
}
new_line_and_indent(s, child_indent);
emit_stretchy_op(s, StretchMode::Fence, *open, OpAttrs::empty())?;
emit_fence(s, *open, OpAttrs::empty())?;
// TODO: if `content` is an `mrow`, we should flatten it before emitting.
content.emit(s, child_indent)?;
new_line_and_indent(s, child_indent);
emit_stretchy_op(s, StretchMode::Fence, *close, OpAttrs::empty())?;
emit_fence(s, *close, OpAttrs::empty())?;
writeln_indent!(s, base_indent, "</mrow>");
}
Node::SizedParen(size, paren, paren_type) => {
Expand Down Expand Up @@ -760,34 +756,13 @@ fn write_equation_num(
Ok(())
}

fn emit_stretchy_op(
s: &mut String,
stretch_mode: StretchMode,
op: Option<StretchableOp>,
attrs: OpAttrs,
) -> std::fmt::Result {
fn emit_fence(s: &mut String, op: Option<StretchableOp>, attrs: OpAttrs) -> std::fmt::Result {
emit_operator_attributes(s, attrs, None, None)?;
if let Some(op) = op {
match (stretch_mode, op.stretchy) {
(StretchMode::Fence, Stretchy::Never)
| (StretchMode::Middle, Stretchy::PrePostfix | Stretchy::Never) => {
write!(s, " stretchy=\"true\">")?;
}
(
StretchMode::NoStretch,
Stretchy::Always | Stretchy::PrePostfix | Stretchy::AlwaysAsymmetric,
) => {
write!(s, " stretchy=\"false\">")?;
}

(StretchMode::Middle, Stretchy::AlwaysAsymmetric) => {
write!(s, " symmetric=\"true\">")?;
}
_ => {
write!(s, ">")?;
}
if matches!(op.stretchy, Stretchy::Never) {
write!(s, " stretchy=\"true\"")?;
}
write!(s, "{}", char::from(op))?;
write!(s, ">{}", char::from(op))?;
} else {
// An empty `<mo></mo>` produces weird spacing in some browsers.
// Use U+2063 (INVISIBLE SEPARATOR) to work around this. It's in Category K in MathML Core.
Expand Down
31 changes: 19 additions & 12 deletions crates/mathml-renderer/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,27 @@ bitflags! {
const NO_MOVABLE_LIMITS = 1 << 2;
const FORCE_MOVABLE_LIMITS = 1 << 3;
const FORM_PREFIX = 1 << 4;
const FORM_POSTFIX = 1 << 5;
// const FORM_INFIX = 1 << 5;
const FORM_POSTFIX = 1 << 6;
const SYMMETRIC_TRUE = 1 << 7;
}
}

impl OpAttrs {
pub fn write_to(self, s: &mut String) {
debug_assert!(
!(self.contains(OpAttrs::STRETCHY_FALSE) && self.contains(OpAttrs::STRETCHY_TRUE)),
"STRETCHY_FALSE and STRETCHY_TRUE cannot both be set"
);
debug_assert!(
!(self.contains(OpAttrs::NO_MOVABLE_LIMITS)
&& self.contains(OpAttrs::FORCE_MOVABLE_LIMITS)),
"NO_MOVABLE_LIMITS and FORCE_MOVABLE_LIMITS cannot both be set"
);
debug_assert!(
!(self.contains(OpAttrs::FORM_PREFIX) && self.contains(OpAttrs::FORM_POSTFIX)),
"FORM_PREFIX and FORM_POSTFIX cannot both be set"
);
if self.contains(OpAttrs::STRETCHY_FALSE) {
s.push_str(r#" stretchy="false""#);
}
Expand All @@ -57,6 +72,9 @@ impl OpAttrs {
if self.contains(OpAttrs::FORM_POSTFIX) {
s.push_str(r#" form="postfix""#);
}
if self.contains(OpAttrs::SYMMETRIC_TRUE) {
s.push_str(r#" symmetric="true""#);
}
}
}

Expand Down Expand Up @@ -89,17 +107,6 @@ pub enum ParenType {
Close,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum StretchMode {
/// Don't stretch the operator.
NoStretch = 1,
/// Operator is in a fence and should stretch.
Fence,
/// Operator is in the middle of a fenced expression and should stretch.
Middle,
}

/// display style
#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
#[cfg_attr(feature = "serde", derive(Serialize))]
Expand Down
9 changes: 8 additions & 1 deletion crates/mathml-renderer/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl OrdLike {
}
OrdCategory::K => (Stretchy::Never, DelimiterSpacing::Zero),
OrdCategory::KButUsedToBeB => (Stretchy::Never, DelimiterSpacing::NonZero),
_ => {
OrdCategory::D | OrdCategory::E | OrdCategory::I => {
return None;
}
};
Expand Down Expand Up @@ -261,6 +261,13 @@ impl Serialize for StretchableOp {
}
}

impl StretchableOp {
#[inline]
pub const fn as_op(self) -> MathMLOperator {
MathMLOperator(self.char.as_char())
}
}

impl From<StretchableOp> for char {
#[inline]
fn from(op: StretchableOp) -> Self {
Expand Down