diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-07-02 19:59:52 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-07-02 20:07:43 +0200 |
| commit | ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch) | |
| tree | 2bbc24ddb4124c4bb14dec0e536129d4de37b056 /crates/typst-library/src/math/matrix.rs | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'crates/typst-library/src/math/matrix.rs')
| -rw-r--r-- | crates/typst-library/src/math/matrix.rs | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/crates/typst-library/src/math/matrix.rs b/crates/typst-library/src/math/matrix.rs new file mode 100644 index 00000000..aaccc332 --- /dev/null +++ b/crates/typst-library/src/math/matrix.rs @@ -0,0 +1,313 @@ +use super::*; + +const ROW_GAP: Em = Em::new(0.5); +const COL_GAP: Em = Em::new(0.5); +const VERTICAL_PADDING: Ratio = Ratio::new(0.1); + +/// A column vector. +/// +/// Content in the vector's elements can be aligned with the `&` symbol. +/// +/// ## Example { #example } +/// ```example +/// $ vec(a, b, c) dot vec(1, 2, 3) +/// = a + 2b + 3c $ +/// ``` +/// +/// Display: Vector +/// Category: math +#[element(LayoutMath)] +pub struct VecElem { + /// The delimiter to use. + /// + /// ```example + /// #set math.vec(delim: "[") + /// $ vec(1, 2) $ + /// ``` + #[default(Some(Delimiter::Paren))] + pub delim: Option<Delimiter>, + + /// The elements of the vector. + #[variadic] + pub children: Vec<Content>, +} + +impl LayoutMath for VecElem { + #[tracing::instrument(skip(ctx))] + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + let delim = self.delim(ctx.styles()); + let frame = layout_vec_body(ctx, &self.children(), Align::Center)?; + layout_delimiters( + ctx, + frame, + delim.map(Delimiter::open), + delim.map(Delimiter::close), + self.span(), + ) + } +} + +/// A matrix. +/// +/// The elements of a row should be separated by commas, while the rows +/// themselves should be separated by semicolons. The semicolon syntax merges +/// preceding arguments separated by commas into an array. You can also use this +/// special syntax of math function calls to define custom functions that take +/// 2D data. +/// +/// Content in cells that are in the same row can be aligned with the `&` symbol. +/// +/// ## Example { #example } +/// ```example +/// $ mat( +/// 1, 2, ..., 10; +/// 2, 2, ..., 10; +/// dots.v, dots.v, dots.down, dots.v; +/// 10, 10, ..., 10; +/// ) $ +/// ``` +/// +/// Display: Matrix +/// Category: math +#[element(LayoutMath)] +pub struct MatElem { + /// The delimiter to use. + /// + /// ```example + /// #set math.mat(delim: "[") + /// $ mat(1, 2; 3, 4) $ + /// ``` + #[default(Some(Delimiter::Paren))] + pub delim: Option<Delimiter>, + + /// An array of arrays with the rows of the matrix. + /// + /// ```example + /// #let data = ((1, 2, 3), (4, 5, 6)) + /// #let matrix = math.mat(..data) + /// $ v := matrix $ + /// ``` + #[variadic] + #[parse( + let mut rows = vec![]; + let mut width = 0; + + let values = args.all::<Spanned<Value>>()?; + if values.iter().any(|spanned| matches!(spanned.v, Value::Array(_))) { + for Spanned { v, span } in values { + let array = v.cast::<Array>().at(span)?; + let row: Vec<_> = array.into_iter().map(Value::display).collect(); + width = width.max(row.len()); + rows.push(row); + } + } else { + rows = vec![values.into_iter().map(|spanned| spanned.v.display()).collect()]; + } + + for row in &mut rows { + if row.len() < width { + row.resize(width, Content::empty()); + } + } + + rows + )] + pub rows: Vec<Vec<Content>>, +} + +impl LayoutMath for MatElem { + #[tracing::instrument(skip(ctx))] + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + let delim = self.delim(ctx.styles()); + let frame = layout_mat_body(ctx, &self.rows())?; + layout_delimiters( + ctx, + frame, + delim.map(Delimiter::open), + delim.map(Delimiter::close), + self.span(), + ) + } +} + +/// A case distinction. +/// +/// Content across different branches can be aligned with the `&` symbol. +/// +/// ## Example { #example } +/// ```example +/// $ f(x, y) := cases( +/// 1 "if" (x dot y)/2 <= 0, +/// 2 "if" x "is even", +/// 3 "if" x in NN, +/// 4 "else", +/// ) $ +/// ``` +/// +/// Display: Cases +/// Category: math +#[element(LayoutMath)] +pub struct CasesElem { + /// The delimiter to use. + /// + /// ```example + /// #set math.cases(delim: "[") + /// $ x = cases(1, 2) $ + /// ``` + #[default(Delimiter::Brace)] + pub delim: Delimiter, + + /// The branches of the case distinction. + #[variadic] + pub children: Vec<Content>, +} + +impl LayoutMath for CasesElem { + #[tracing::instrument(skip(ctx))] + fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { + let delim = self.delim(ctx.styles()); + let frame = layout_vec_body(ctx, &self.children(), Align::Left)?; + layout_delimiters(ctx, frame, Some(delim.open()), None, self.span()) + } +} + +/// A vector / matrix delimiter. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] +pub enum Delimiter { + /// Delimit with parentheses. + #[string("(")] + Paren, + /// Delimit with brackets. + #[string("[")] + Bracket, + /// Delimit with curly braces. + #[string("{")] + Brace, + /// Delimit with vertical bars. + #[string("|")] + Bar, + /// Delimit with double vertical bars. + #[string("||")] + 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 => '‖', + } + } +} + +/// Layout the inner contents of a vector. +fn layout_vec_body( + ctx: &mut MathContext, + column: &[Content], + align: Align, +) -> SourceResult<Frame> { + let gap = ROW_GAP.scaled(ctx); + ctx.style(ctx.style.for_denominator()); + let mut flat = vec![]; + for child in column { + flat.push(ctx.layout_row(child)?); + } + ctx.unstyle(); + Ok(stack(ctx, flat, align, gap, 0)) +} + +/// Layout the inner contents of a matrix. +fn layout_mat_body(ctx: &mut MathContext, rows: &[Vec<Content>]) -> SourceResult<Frame> { + let row_gap = ROW_GAP.scaled(ctx); + let col_gap = COL_GAP.scaled(ctx); + + let ncols = rows.first().map_or(0, |row| row.len()); + let nrows = rows.len(); + if ncols == 0 || nrows == 0 { + return Ok(Frame::new(Size::zero())); + } + + let mut heights = vec![(Abs::zero(), Abs::zero()); nrows]; + + ctx.style(ctx.style.for_denominator()); + let mut cols = vec![vec![]; ncols]; + for (row, (ascent, descent)) in rows.iter().zip(&mut heights) { + for (cell, col) in row.iter().zip(&mut cols) { + let cell = ctx.layout_row(cell)?; + ascent.set_max(cell.ascent()); + descent.set_max(cell.descent()); + col.push(cell); + } + } + ctx.unstyle(); + + let mut frame = Frame::new(Size::new( + Abs::zero(), + heights.iter().map(|&(a, b)| a + b).sum::<Abs>() + row_gap * (nrows - 1) as f64, + )); + let mut x = Abs::zero(); + for col in cols { + let AlignmentResult { points, width: rcol } = alignments(&col); + let mut y = Abs::zero(); + for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) { + let cell = cell.into_aligned_frame(ctx, &points, Align::Center); + let pos = Point::new( + if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x }, + y + ascent - cell.ascent(), + ); + frame.push_frame(pos, cell); + y += ascent + descent + row_gap; + } + x += rcol + col_gap; + } + frame.size_mut().x = x - col_gap; + + Ok(frame) +} + +/// Layout the outer wrapper around a vector's or matrices' body. +fn layout_delimiters( + ctx: &mut MathContext, + mut frame: Frame, + left: Option<char>, + right: Option<char>, + span: Span, +) -> SourceResult<()> { + let axis = scaled!(ctx, axis_height); + let short_fall = DELIM_SHORT_FALL.scaled(ctx); + let height = frame.height(); + let target = height + VERTICAL_PADDING.of(height); + frame.set_baseline(height / 2.0 + axis); + + if let Some(left) = left { + ctx.push( + GlyphFragment::new(ctx, left, span).stretch_vertical(ctx, target, short_fall), + ); + } + + ctx.push(FrameFragment::new(ctx, frame)); + + if let Some(right) = right { + ctx.push( + GlyphFragment::new(ctx, right, span) + .stretch_vertical(ctx, target, short_fall), + ); + } + + Ok(()) +} |
