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}"
----
-
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}"
+---
+
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}"
+---
+
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}}"
+---
+
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