diff options
| author | Max <me@mkor.je> | 2025-03-31 09:38:04 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-31 09:38:04 +0000 |
| commit | 012e14d40cb44997630cf6469a446f217f2e9057 (patch) | |
| tree | 71f60052b2e8eae87c0c9ac2c20faebc293a1f39 /crates/typst-layout | |
| parent | 4f0fbfb7e003f6ae88c1b210fdb7b38f795fc9e4 (diff) | |
Unify layout of `vec` and `cases` with `mat` (#5934)
Diffstat (limited to 'crates/typst-layout')
| -rw-r--r-- | crates/typst-layout/src/math/mat.rs | 167 | ||||
| -rw-r--r-- | crates/typst-layout/src/math/shared.rs | 16 | ||||
| -rw-r--r-- | crates/typst-layout/src/math/underover.rs | 10 |
3 files changed, 91 insertions, 102 deletions
diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index bf492902..d678f865 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -1,4 +1,4 @@ -use typst_library::diag::{bail, SourceResult}; +use typst_library::diag::{bail, warning, SourceResult}; use typst_library::foundations::{Content, Packed, Resolve, StyleChain}; use typst_library::layout::{ Abs, Axes, Em, FixedAlignment, Frame, FrameItem, Point, Ratio, Rel, Size, @@ -9,7 +9,7 @@ use typst_library::visualize::{FillRule, FixedStroke, Geometry, LineCap, Shape}; use typst_syntax::Span; use super::{ - alignments, delimiter_alignment, stack, style_for_denominator, AlignmentResult, + alignments, delimiter_alignment, style_for_denominator, AlignmentResult, FrameFragment, GlyphFragment, LeftRightAlternator, MathContext, DELIM_SHORT_FALL, }; @@ -23,17 +23,51 @@ pub fn layout_vec( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let delim = elem.delim(styles); - let frame = layout_vec_body( + let span = elem.span(); + + let column: Vec<&Content> = elem.children.iter().collect(); + let frame = layout_body( ctx, styles, - &elem.children, + &[column], elem.align(styles), - elem.gap(styles), LeftRightAlternator::Right, + None, + Axes::with_y(elem.gap(styles)), + span, + "elements", )?; - layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), elem.span()) + let delim = elem.delim(styles); + layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), span) +} + +/// Lays out a [`CasesElem`]. +#[typst_macros::time(name = "math.cases", span = elem.span())] +pub fn layout_cases( + elem: &Packed<CasesElem>, + ctx: &mut MathContext, + styles: StyleChain, +) -> SourceResult<()> { + let span = elem.span(); + + let column: Vec<&Content> = elem.children.iter().collect(); + let frame = layout_body( + ctx, + styles, + &[column], + FixedAlignment::Start, + LeftRightAlternator::None, + None, + Axes::with_y(elem.gap(styles)), + span, + "branches", + )?; + + let delim = elem.delim(styles); + let (open, close) = + if elem.reverse(styles) { (None, delim.close()) } else { (delim.open(), None) }; + layout_delimiters(ctx, styles, frame, open, close, span) } /// Lays out a [`MatElem`]. @@ -43,14 +77,16 @@ pub fn layout_mat( ctx: &mut MathContext, styles: StyleChain, ) -> SourceResult<()> { - let augment = elem.augment(styles); + let span = elem.span(); let rows = &elem.rows; + let ncols = rows.first().map_or(0, |row| row.len()); + let augment = elem.augment(styles); if let Some(aug) = &augment { for &offset in &aug.hline.0 { if offset == 0 || offset.unsigned_abs() >= rows.len() { bail!( - elem.span(), + span, "cannot draw a horizontal line after row {} of a matrix with {} rows", if offset < 0 { rows.len() as isize + offset } else { offset }, rows.len() @@ -58,95 +94,55 @@ pub fn layout_mat( } } - let ncols = rows.first().map_or(0, |row| row.len()); - for &offset in &aug.vline.0 { if offset == 0 || offset.unsigned_abs() >= ncols { bail!( - elem.span(), - "cannot draw a vertical line after column {} of a matrix with {} columns", - if offset < 0 { ncols as isize + offset } else { offset }, - ncols - ); + span, + "cannot draw a vertical line after column {} of a matrix with {} columns", + if offset < 0 { ncols as isize + offset } else { offset }, + ncols + ); } } } - let delim = elem.delim(styles); - let frame = layout_mat_body( + // Transpose rows of the matrix into columns. + let mut row_iters: Vec<_> = rows.iter().map(|i| i.iter()).collect(); + let columns: Vec<Vec<_>> = (0..ncols) + .map(|_| row_iters.iter_mut().map(|i| i.next().unwrap()).collect()) + .collect(); + + let frame = layout_body( ctx, styles, - rows, + &columns, elem.align(styles), + LeftRightAlternator::Right, augment, Axes::new(elem.column_gap(styles), elem.row_gap(styles)), - elem.span(), + span, + "cells", )?; - layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), elem.span()) -} - -/// Lays out a [`CasesElem`]. -#[typst_macros::time(name = "math.cases", span = elem.span())] -pub fn layout_cases( - elem: &Packed<CasesElem>, - ctx: &mut MathContext, - styles: StyleChain, -) -> SourceResult<()> { let delim = elem.delim(styles); - let frame = layout_vec_body( - ctx, - styles, - &elem.children, - FixedAlignment::Start, - elem.gap(styles), - LeftRightAlternator::None, - )?; - - let (open, close) = - if elem.reverse(styles) { (None, delim.close()) } else { (delim.open(), None) }; - - layout_delimiters(ctx, styles, frame, open, close, elem.span()) + layout_delimiters(ctx, styles, frame, delim.open(), delim.close(), span) } -/// Layout the inner contents of a vector. -fn layout_vec_body( +/// Layout the inner contents of a matrix, vector, or cases. +#[allow(clippy::too_many_arguments)] +fn layout_body( ctx: &mut MathContext, styles: StyleChain, - column: &[Content], + columns: &[Vec<&Content>], align: FixedAlignment, - row_gap: Rel<Abs>, alternator: LeftRightAlternator, -) -> SourceResult<Frame> { - let gap = row_gap.relative_to(ctx.region.size.y); - - let denom_style = style_for_denominator(styles); - let mut flat = vec![]; - for child in column { - // We allow linebreaks in cases and vectors, which are functionally - // identical to commas. - flat.extend(ctx.layout_into_run(child, styles.chain(&denom_style))?.rows()); - } - // We pad ascent and descent with the ascent and descent of the paren - // to ensure that normal vectors are aligned with others unless they are - // way too big. - let paren = - GlyphFragment::new(ctx, styles.chain(&denom_style), '(', Span::detached()); - Ok(stack(flat, align, gap, 0, alternator, Some((paren.ascent, paren.descent)))) -} - -/// Layout the inner contents of a matrix. -fn layout_mat_body( - ctx: &mut MathContext, - styles: StyleChain, - rows: &[Vec<Content>], - align: FixedAlignment, augment: Option<Augment<Abs>>, gap: Axes<Rel<Abs>>, span: Span, + children: &str, ) -> SourceResult<Frame> { - let ncols = rows.first().map_or(0, |row| row.len()); - let nrows = rows.len(); + let nrows = columns.first().map_or(0, |col| col.len()); + let ncols = columns.len(); if ncols == 0 || nrows == 0 { return Ok(Frame::soft(Size::zero())); } @@ -178,16 +174,11 @@ fn layout_mat_body( // Before the full matrix body can be laid out, the // individual cells must first be independently laid out // so we can ensure alignment across rows and columns. + let mut cols = vec![vec![]; ncols]; // This variable stores the maximum ascent and descent for each row. let mut heights = vec![(Abs::zero(), Abs::zero()); nrows]; - // We want to transpose our data layout to columns - // before final layout. For efficiency, the columns - // variable is set up here and newly generated - // individual cells are then added to it. - let mut cols = vec![vec![]; ncols]; - let denom_style = style_for_denominator(styles); // We pad ascent and descent with the ascent and descent of the paren // to ensure that normal matrices are aligned with others unless they are @@ -195,10 +186,22 @@ fn layout_mat_body( let paren = GlyphFragment::new(ctx, styles.chain(&denom_style), '(', Span::detached()); - for (row, (ascent, descent)) in rows.iter().zip(&mut heights) { - for (cell, col) in row.iter().zip(&mut cols) { + for (column, col) in columns.iter().zip(&mut cols) { + for (cell, (ascent, descent)) in column.iter().zip(&mut heights) { + let cell_span = cell.span(); let cell = ctx.layout_into_run(cell, styles.chain(&denom_style))?; + // We ignore linebreaks in the cells as we can't differentiate + // alignment points for the whole body from ones for a specific + // cell, and multiline cells don't quite make sense at the moment. + if cell.is_multiline() { + ctx.engine.sink.warn(warning!( + cell_span, + "linebreaks are ignored in {}", children; + hint: "use commas instead to separate each line" + )); + } + ascent.set_max(cell.ascent().max(paren.ascent)); descent.set_max(cell.descent().max(paren.descent)); @@ -222,7 +225,7 @@ fn layout_mat_body( let mut y = Abs::zero(); for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) { - let cell = cell.into_line_frame(&points, LeftRightAlternator::Right); + let cell = cell.into_line_frame(&points, alternator); let pos = Point::new( if points.is_empty() { x + align.position(rcol - cell.width()) diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 5aebdaca..600c130d 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -117,7 +117,6 @@ pub fn stack( gap: Abs, baseline: usize, alternator: LeftRightAlternator, - minimum_ascent_descent: Option<(Abs, Abs)>, ) -> Frame { let AlignmentResult { points, width } = alignments(&rows); let rows: Vec<_> = rows @@ -125,13 +124,9 @@ pub fn stack( .map(|row| row.into_line_frame(&points, alternator)) .collect(); - let padded_height = |height: Abs| { - height.max(minimum_ascent_descent.map_or(Abs::zero(), |(a, d)| a + d)) - }; - let mut frame = Frame::soft(Size::new( width, - rows.iter().map(|row| padded_height(row.height())).sum::<Abs>() + rows.iter().map(|row| row.height()).sum::<Abs>() + rows.len().saturating_sub(1) as f64 * gap, )); @@ -142,14 +137,11 @@ pub fn stack( } else { Abs::zero() }; - let ascent_padded_part = minimum_ascent_descent - .map_or(Abs::zero(), |(a, _)| (a - row.ascent())) - .max(Abs::zero()); - let pos = Point::new(x, y + ascent_padded_part); + let pos = Point::new(x, y); if i == baseline { - frame.set_baseline(y + row.baseline() + ascent_padded_part); + frame.set_baseline(y + row.baseline()); } - y += padded_height(row.height()) + gap; + y += row.height() + gap; frame.push_frame(pos, row); } diff --git a/crates/typst-layout/src/math/underover.rs b/crates/typst-layout/src/math/underover.rs index 7b3617c3..5b6bd40e 100644 --- a/crates/typst-layout/src/math/underover.rs +++ b/crates/typst-layout/src/math/underover.rs @@ -312,14 +312,8 @@ fn layout_underoverspreader( } }; - let frame = stack( - rows, - FixedAlignment::Center, - gap, - baseline, - LeftRightAlternator::Right, - None, - ); + let frame = + stack(rows, FixedAlignment::Center, gap, baseline, LeftRightAlternator::Right); ctx.push(FrameFragment::new(styles, frame).with_class(body_class)); Ok(()) |
