summaryrefslogtreecommitdiff
path: root/crates/typst-layout/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-layout/src')
-rw-r--r--crates/typst-layout/src/math/mat.rs167
-rw-r--r--crates/typst-layout/src/math/shared.rs16
-rw-r--r--crates/typst-layout/src/math/underover.rs10
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(())