diff --git a/math-core/src/lexer.rs b/math-core/src/lexer.rs index c5a06d2b..c97bae8a 100644 --- a/math-core/src/lexer.rs +++ b/math-core/src/lexer.rs @@ -83,7 +83,7 @@ impl<'source> Lexer<'source> { while self.peek.1.is_ascii_alphanumeric() || self.peek.1.is_ascii_whitespace() - || matches!(self.peek.1, '|' | '.' | '-' | ',' | '*') + || matches!(self.peek.1, '|' | '.' | '-' | ',' | '*' | ':') { self.read_char(); } diff --git a/math-core/src/lib.rs b/math-core/src/lib.rs index f07e20a2..08cb1232 100644 --- a/math-core/src/lib.rs +++ b/math-core/src/lib.rs @@ -354,8 +354,16 @@ mod tests { // ("hspace_whitespace_in_between", r"\hspace{ 4 em }"), ("array_simple", r"\begin{array}{lcr} 0 & 1 & 2 \end{array}"), ( - "array_center_lines", - r"\begin{array}{ |c| |cc| } 1 & 2 & 3\\ 4 & 5 & 6 \end{array}", + "array_lines", + r"\begin{array}{ |l| |rc| } 10 & 20 & 30\\ 4 & 5 & 6 \end{array}", + ), + ( + "array_many_lines", + r"\begin{array}{ ||::|l } 10\\ 2 \end{array}", + ), + ( + "subarray", + r"\sum_{\begin{subarray}{c} 0 \le i \le m\\ 0 < j < n \end{subarray}}", ), ]; diff --git a/math-core/src/parse.rs b/math-core/src/parse.rs index 9ea09c2a..c5a66924 100644 --- a/math-core/src/parse.rs +++ b/math-core/src/parse.rs @@ -621,7 +621,7 @@ where Token::Begin => { // Read the environment name. let env_name = self.parse_ascii_text_group()?.1; - let array_spec = if env_name == "array" { + let array_spec = if env_name == "array" || env_name == "subarray" { // Parse the array options. let (loc, options) = self.parse_ascii_text_group()?; Some( @@ -660,10 +660,19 @@ where align: Alignment::Centered, attr: None, }, - "array" => { - // SAFETY: `array_spec` is guaranteed to be Some because we checked for "array" above. - let spec = unsafe { array_spec.unwrap_unchecked() }; + array_variant @ ("array" | "subarray") => { + // SAFETY: `array_spec` is guaranteed to be Some because we checked for + // "array" and "subarray" above. + // TODO: Refactor this to avoid using `unsafe`. + let mut spec = unsafe { array_spec.unwrap_unchecked() }; + let style = if array_variant == "subarray" { + spec.is_sub = true; + Some(Style::ScriptStyle) + } else { + None + }; Node::Array { + style, content, array_spec: self.arena.alloc_array_spec(spec), } diff --git a/math-core/src/snapshots/math_core__tests__array_center_lines.snap b/math-core/src/snapshots/math_core__tests__array_center_lines.snap deleted file mode 100644 index d8ede9a3..00000000 --- a/math-core/src/snapshots/math_core__tests__array_center_lines.snap +++ /dev/null @@ -1,30 +0,0 @@ ---- -source: math-core/src/lib.rs -expression: "\\begin{array}{ |c| |cc| } 1 & 2 & 3\\\\ 4 & 5 & 6 \\end{array}" ---- - - - - - 1 - - - 2 - - - 3 - - - - - 4 - - - 5 - - - 6 - - - - diff --git a/math-core/src/snapshots/math_core__tests__array_lines.snap b/math-core/src/snapshots/math_core__tests__array_lines.snap new file mode 100644 index 00000000..31a7f560 --- /dev/null +++ b/math-core/src/snapshots/math_core__tests__array_lines.snap @@ -0,0 +1,32 @@ +--- +source: math-core/src/lib.rs +expression: "\\begin{array}{ |l| |rc| } 10 & 20 & 30\\\\ 4 & 5 & 6 \\end{array}" +--- + + + + + 10 + + + + 20 + + + 30 + + + + + 4 + + + + 5 + + + 6 + + + + diff --git a/math-core/src/snapshots/math_core__tests__array_many_lines.snap b/math-core/src/snapshots/math_core__tests__array_many_lines.snap new file mode 100644 index 00000000..533f8512 --- /dev/null +++ b/math-core/src/snapshots/math_core__tests__array_many_lines.snap @@ -0,0 +1,26 @@ +--- +source: math-core/src/lib.rs +expression: "\\begin{array}{ ||::|l } 10\\\\ 2 \\end{array}" +--- + + + + + + + + + 10 + + + + + + + + + 2 + + + + diff --git a/math-core/src/snapshots/math_core__tests__subarray.snap b/math-core/src/snapshots/math_core__tests__subarray.snap new file mode 100644 index 00000000..444f19ba --- /dev/null +++ b/math-core/src/snapshots/math_core__tests__subarray.snap @@ -0,0 +1,29 @@ +--- +source: math-core/src/lib.rs +expression: "\\sum_{\\begin{subarray}{c} 0 \\le i \\le m\\\\ 0 < j < n \\end{subarray}}" +--- + + + + + + + 0 + + i + + m + + + + + 0 + < + j + < + n + + + + + diff --git a/math-core/src/specifications.rs b/math-core/src/specifications.rs index a714669c..c6fca83d 100644 --- a/math-core/src/specifications.rs +++ b/math-core/src/specifications.rs @@ -122,12 +122,13 @@ pub fn parse_column_specification<'arena>( arena: &'arena Arena, ) -> Option> { let mut column_spec = Vec::new(); - let mut begins_with_line = false; + let mut beginning_line: Option = None; let mut has_content_column = false; // We will work with bytes to avoid UTF-8 checks. // This is possible because we only match on ASCII characters. for ch in s.as_bytes() { + let ch = *ch; match ch { b'l' | b'c' | b'r' => { let alignment = match ch { @@ -140,22 +141,27 @@ pub fn parse_column_specification<'arena>( column_spec.push(ColumnSpec::WithContent(alignment, None)); has_content_column = true; } - b'|' => { + b'|' | b':' => { + let line_type = match ch { + b'|' => LineType::Solid, + b':' => LineType::Dashed, + _ => unreachable!(), + }; if let Some(last) = column_spec.last_mut() { // If the last column was a content column, we need to add a line type. // If it is already set, we add a new element to the column spec vector. - if let ColumnSpec::WithContent(_, line_type @ None) = last { - *line_type = Some(LineType::Solid); + if let ColumnSpec::WithContent(_, last_line_type @ None) = last { + *last_line_type = Some(line_type); } else { - column_spec.push(ColumnSpec::OnlyLine(LineType::Solid)) + column_spec.push(ColumnSpec::OnlyLine(line_type)) } } else { // Nothing has been added yet, so this is the first column. - if begins_with_line { - // If `begins_with_line` is already true, we have a double line. - column_spec.push(ColumnSpec::OnlyLine(LineType::Solid)) + if beginning_line.is_none() { + beginning_line = Some(line_type); } else { - begins_with_line = true; + // If there already is a `beginning_line`, we have a double line. + column_spec.push(ColumnSpec::OnlyLine(line_type)) } } } @@ -174,7 +180,8 @@ pub fn parse_column_specification<'arena>( } Some(ArraySpec { - begins_with_line, + beginning_line, + is_sub: false, column_spec: arena.alloc_column_specs(column_spec.as_slice()), }) } @@ -201,7 +208,7 @@ mod tests { fn column_parse_simple() { let arena = Arena::new(); let spec = parse_column_specification("l|c|r", &arena).unwrap(); - assert_eq!(spec.begins_with_line, false); + assert!(matches!(spec.beginning_line, None)); assert_eq!(spec.column_spec.len(), 3); assert!(matches!( spec.column_spec[0], @@ -221,7 +228,7 @@ mod tests { fn column_parse_line_at_beginning() { let arena = Arena::new(); let spec = parse_column_specification("|ccc", &arena).unwrap(); - assert_eq!(spec.begins_with_line, true); + assert!(matches!(spec.beginning_line, Some(LineType::Solid))); assert_eq!(spec.column_spec.len(), 3); assert!(matches!( spec.column_spec[0], @@ -241,7 +248,7 @@ mod tests { fn column_parse_multiple_line_at_beginning() { let arena = Arena::new(); let spec = parse_column_specification(" | ||c", &arena).unwrap(); - assert_eq!(spec.begins_with_line, true); + assert!(matches!(spec.beginning_line, Some(LineType::Solid))); assert_eq!(spec.column_spec.len(), 3); assert!(matches!( spec.column_spec[0], @@ -260,12 +267,12 @@ mod tests { #[test] fn column_parse_with_spaces() { let arena = Arena::new(); - let spec = parse_column_specification(" c | | c| | | c ", &arena).unwrap(); - assert_eq!(spec.begins_with_line, false); + let spec = parse_column_specification(" c : | c| : | c ", &arena).unwrap(); + assert!(matches!(spec.beginning_line, None)); assert_eq!(spec.column_spec.len(), 6); assert!(matches!( spec.column_spec[0], - ColumnSpec::WithContent(ColumnAlignment::Centered, Some(LineType::Solid)) + ColumnSpec::WithContent(ColumnAlignment::Centered, Some(LineType::Dashed)) )); assert!(matches!( spec.column_spec[1], @@ -277,7 +284,7 @@ mod tests { )); assert!(matches!( spec.column_spec[3], - ColumnSpec::OnlyLine(LineType::Solid) + ColumnSpec::OnlyLine(LineType::Dashed) )); assert!(matches!( spec.column_spec[4], diff --git a/mathml_renderer/src/ast.rs b/mathml_renderer/src/ast.rs index 5d512b29..76b05157 100644 --- a/mathml_renderer/src/ast.rs +++ b/mathml_renderer/src/ast.rs @@ -8,10 +8,11 @@ use crate::attribute::{ FracAttr, MathSpacing, MathVariant, OpAttr, RowAttr, Size, StretchMode, Stretchy, Style, TextTransform, }; +use crate::fmt::new_line_and_indent; use crate::itoa::append_u8_as_hex; use crate::length::{Length, LengthUnit, LengthValue}; use crate::symbol::{Fence, Op}; -use crate::table::{Alignment, ArraySpec, ColumnGenerator, SIMPLE_CENTERED}; +use crate::table::{Alignment, ArraySpec, ColumnGenerator, LineType}; /// AST node #[derive(Debug)] @@ -90,6 +91,7 @@ pub enum Node<'arena> { attr: Option, }, Array { + style: Option