diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index 1a84d6116..aa21e9cf7 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -1,5 +1,8 @@ use super::*; +const ROW_GAP: Em = Em::new(0.5); +const VERTICAL_PADDING: Ratio = Ratio::new(0.1); + /// # Vector /// A column vector. /// @@ -18,7 +21,7 @@ use super::*; /// ## Category /// math #[func] -#[capable(Texify)] +#[capable(LayoutMath)] #[derive(Debug, Hash)] pub struct VecNode(Vec); @@ -47,52 +50,13 @@ impl VecNode { } } -impl Texify for VecNode { - fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - let kind = match t.styles.get(Self::DELIM) { - Delimiter::Paren => "pmatrix", - Delimiter::Bracket => "bmatrix", - Delimiter::Brace => "Bmatrix", - Delimiter::Bar => "vmatrix", - }; - - t.push_str("\\begin{"); - t.push_str(kind); - t.push_str("}"); - - for component in &self.0 { - component.texify(t)?; - t.push_str("\\\\"); - } - t.push_str("\\end{"); - t.push_str(kind); - t.push_str("}"); - - Ok(()) +impl LayoutMath for VecNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + let delim = ctx.outer.get(Self::DELIM); + layout(ctx, &self.0, Align::Center, Some(delim.open()), Some(delim.close())) } } -/// A vector / matrix delimiter. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Delimiter { - Paren, - Bracket, - Brace, - Bar, -} - -castable! { - Delimiter, - /// Delimit the vector with parentheses. - "(" => Self::Paren, - /// Delimit the vector with brackets. - "[" => Self::Bracket, - /// Delimit the vector with curly braces. - "{" => Self::Brace, - /// Delimit the vector with vertical bars. - "|" => Self::Bar, -} - /// # Cases /// A case distinction. /// @@ -113,7 +77,7 @@ castable! { /// ## Category /// math #[func] -#[capable(Texify)] +#[capable(LayoutMath)] #[derive(Debug, Hash)] pub struct CasesNode(Vec); @@ -133,14 +97,132 @@ impl CasesNode { } } -impl Texify for CasesNode { - fn texify(&self, t: &mut Texifier) -> SourceResult<()> { - t.push_str("\\begin{cases}"); - for component in &self.0 { - component.texify(t)?; - t.push_str("\\\\"); - } - t.push_str("\\end{cases}"); - Ok(()) +impl LayoutMath for CasesNode { + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + layout(ctx, &self.0, Align::Left, Some('{'), None) } } + +/// A vector / matrix delimiter. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Delimiter { + Paren, + Bracket, + Brace, + Bar, + DoubleBar, +} + +impl Delimiter { + /// The delimiter's opening character. + fn open(self) -> char { + match self { + Self::Paren => '(', + Self::Bracket => '[', + Self::Brace => '{', + Self::Bar => '|', + Self::DoubleBar => '‖', + } + } + + /// The delimiter's closing character. + fn close(self) -> char { + match self { + Self::Paren => ')', + Self::Bracket => ']', + Self::Brace => '}', + Self::Bar => '|', + Self::DoubleBar => '‖', + } + } +} + +castable! { + Delimiter, + /// Delimit the vector with parentheses. + "(" => Self::Paren, + /// Delimit the vector with brackets. + "[" => Self::Bracket, + /// Delimit the vector with curly braces. + "{" => Self::Brace, + /// Delimit the vector with vertical bars. + "|" => Self::Bar, + /// Delimit the vector with double vertical bars. + "||" => Self::DoubleBar, +} + +/// Layout a matrix. +fn layout( + ctx: &mut MathContext, + elements: &[Content], + align: Align, + left: Option, + right: Option, +) -> SourceResult<()> { + let axis = scaled!(ctx, axis_height); + let gap = ROW_GAP.scaled(ctx); + + ctx.style(ctx.style.for_denominator()); + let mut rows = vec![]; + for element in elements { + rows.push(ctx.layout_row(element)?); + } + ctx.unstyle(); + + if let Some(left) = left { + ctx.push(GlyphFragment::new(ctx, left)); + } + + let mut frame = stack(ctx, rows, align, gap, 0); + frame.set_baseline(frame.height() / 2.0 + axis); + + ctx.push(frame); + + if let Some(right) = right { + ctx.push(GlyphFragment::new(ctx, right)); + } + + Ok(()) +} + +/// Stack rows on top of each other. +/// +/// Add a `gap` between each row and uses the baseline of the `baseline`th +/// row for the whole frame. +pub(super) fn stack( + ctx: &MathContext, + rows: Vec, + align: Align, + gap: Abs, + baseline: usize, +) -> Frame { + let mut width = Abs::zero(); + let mut height = rows.len().saturating_sub(1) as f64 * gap; + + let points = alignments(&rows); + let rows: Vec<_> = + rows.into_iter().map(|row| row.to_line_frame(ctx, &points)).collect(); + + for row in &rows { + height += row.height(); + width.set_max(row.width()); + } + + let extra = VERTICAL_PADDING.of(height); + height += extra; + + let mut y = extra / 2.0; + let mut frame = Frame::new(Size::new(width, height)); + + for (i, row) in rows.into_iter().enumerate() { + let x = align.position(width - row.width()); + let pos = Point::new(x, y); + if i == baseline { + frame.set_baseline(y + row.baseline()); + } + y += row.height() + gap; + frame.push_frame(pos, row); + } + + frame +}