diff --git a/crates/typst-library/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs index 91310ed3a..333a57066 100644 --- a/crates/typst-library/src/math/matrix.rs +++ b/crates/typst-library/src/math/matrix.rs @@ -97,7 +97,7 @@ pub struct MatElem { /// /// - `{none}`: No lines are drawn. /// - A single number: A vertical augmentation line is drawn - /// after the specified column number. + /// after the specified column number. Negative numbers start from the end. /// - A dictionary: With a dictionary, multiple augmentation lines can be /// drawn both horizontally and vertically. Additionally, the style of the /// lines can be set. The dictionary can contain the following keys: @@ -105,17 +105,19 @@ pub struct MatElem { /// For example, an offset of `2` would result in a horizontal line /// being drawn after the second row of the matrix. Accepts either an /// integer for a single line, or an array of integers - /// for multiple lines. + /// for multiple lines. Like for a single number, negative numbers start from the end. /// - `vline`: The offsets at which vertical lines should be drawn. /// For example, an offset of `2` would result in a vertical line being /// drawn after the second column of the matrix. Accepts either an /// integer for a single line, or an array of integers - /// for multiple lines. + /// for multiple lines. Like for a single number, negative numbers start from the end. /// - `stroke`: How to [stroke]($stroke) the line. If set to `{auto}`, /// takes on a thickness of 0.05em and square line caps. /// /// ```example /// $ mat(1, 0, 1; 0, 1, 2; augment: #2) $ + /// // Equivalent to: + /// $ mat(1, 0, 1; 0, 1, 2; augment: #(-1)) $ /// ``` /// /// ```example @@ -200,15 +202,16 @@ impl LayoutMath for MatElem { // validate inputs let augment = self.augment(ctx.styles()); + let rows = self.rows(); if let Some(aug) = &augment { for &offset in &aug.hline.0 { - if offset == 0 || offset >= self.rows().len() { + if offset == 0 || offset.unsigned_abs() >= rows.len() { bail!( self.span(), "cannot draw a horizontal line after row {} of a matrix with {} rows", - offset, - self.rows().len() + if offset < 0 { rows.len() as isize + offset } else { offset }, + rows.len() ); } } @@ -216,11 +219,11 @@ impl LayoutMath for MatElem { let ncols = self.rows().first().map_or(0, |row| row.len()); for &offset in &aug.vline.0 { - if offset == 0 || offset >= ncols { + if offset == 0 || offset.unsigned_abs() >= ncols { bail!( self.span(), "cannot draw a vertical line after column {} of a matrix with {} columns", - offset, + if offset < 0 { ncols as isize + offset } else { offset }, ncols ); } @@ -230,7 +233,7 @@ impl LayoutMath for MatElem { let delim = self.delim(ctx.styles()); let frame = layout_mat_body( ctx, - &self.rows(), + &rows, augment, Axes::new(self.column_gap(ctx.styles()), self.row_gap(ctx.styles())), self.span(), @@ -472,7 +475,9 @@ fn layout_mat_body( x += rcol; // If a vertical line should be inserted after this column - if vline.0.contains(&(index + 1)) { + if vline.0.contains(&(index as isize + 1)) + || vline.0.contains(&(1 - ((ncols - index) as isize))) + { frame.push( Point::with_x(x + half_gap.x), line_item(total_height, true, stroke.clone(), span), @@ -488,8 +493,10 @@ fn layout_mat_body( // This allows the horizontal lines to be laid out for line in hline.0 { - let offset = (heights[0..line].iter().map(|&(a, b)| a + b).sum::() - + gap.y * (line - 1) as f64) + let real_line = + if line < 0 { nrows - line.unsigned_abs() } else { line as usize }; + let offset = (heights[0..real_line].iter().map(|&(a, b)| a + b).sum::() + + gap.y * (real_line - 1) as f64) + half_gap.y; frame.push( @@ -605,7 +612,7 @@ cast! { d.into_value() }, - v: usize => Augment { + v: isize => Augment { hline: Offsets::default(), vline: Offsets(vec![v]), stroke: Smart::Auto, @@ -632,11 +639,11 @@ cast! { /// The offsets at which augmentation lines /// should be drawn on a matrix. #[derive(Debug, Default, Clone, Hash)] -pub struct Offsets(Vec); +pub struct Offsets(Vec); cast! { Offsets, self => self.0.into_value(), - v: usize => Self(vec![v]), + v: isize => Self(vec![v]), v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } diff --git a/tests/ref/math/matrix.png b/tests/ref/math/matrix.png index 530c2b5a5..32181c69c 100644 Binary files a/tests/ref/math/matrix.png and b/tests/ref/math/matrix.png differ diff --git a/tests/typ/math/matrix.typ b/tests/typ/math/matrix.typ index efcde1eb1..57ecd85fd 100644 --- a/tests/typ/math/matrix.typ +++ b/tests/typ/math/matrix.typ @@ -72,9 +72,13 @@ $ mat(#1, #(foo: "bar")) $ gutter: 10pt, $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #3) $, + $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #(-1)) $, $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 2)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -1)) $, $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 1, vline: 1)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -2, vline: -2)) $, $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: 2, stroke: 1pt + blue)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: -1, stroke: 1pt + blue)) $, ) ---