diff --git a/crates/typst/src/layout/align.rs b/crates/typst/src/layout/align.rs index 5f516ebec..3e462d33b 100644 --- a/crates/typst/src/layout/align.rs +++ b/crates/typst/src/layout/align.rs @@ -126,7 +126,7 @@ impl Alignment { pub fn fix(self, text_dir: Dir) -> Axes { Axes::new( self.x().unwrap_or_default().fix(text_dir), - self.y().unwrap_or_default().fix(), + self.y().unwrap_or_default().fix(text_dir), ) } } @@ -244,6 +244,12 @@ impl From for Alignment { } } +/// Alignment on this axis can be fixed to an absolute direction. +pub trait FixAlignment { + /// Resolve to the absolute alignment. + fn fix(self, dir: Dir) -> FixedAlignment; +} + /// Where to align something horizontally. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] pub enum HAlignment { @@ -266,9 +272,10 @@ impl HAlignment { Self::End => Self::Start, } } +} - /// Resolve the axis alignment based on the horizontal direction. - pub const fn fix(self, dir: Dir) -> FixedAlignment { +impl FixAlignment for HAlignment { + fn fix(self, dir: Dir) -> FixedAlignment { match (self, dir.is_positive()) { (Self::Start, true) | (Self::End, false) => FixedAlignment::Start, (Self::Left, _) => FixedAlignment::Start, @@ -344,9 +351,8 @@ pub enum OuterHAlignment { End, } -impl OuterHAlignment { - /// Resolve the axis alignment based on the horizontal direction. - pub const fn fix(self, dir: Dir) -> FixedAlignment { +impl FixAlignment for OuterHAlignment { + fn fix(self, dir: Dir) -> FixedAlignment { match (self, dir.is_positive()) { (Self::Start, true) | (Self::End, false) => FixedAlignment::Start, (Self::Left, _) => FixedAlignment::Start, @@ -413,9 +419,11 @@ impl VAlignment { Self::Bottom => Self::Top, } } +} - /// Turns into a fixed alignment. - pub const fn fix(self) -> FixedAlignment { +impl FixAlignment for VAlignment { + fn fix(self, _: Dir) -> FixedAlignment { + // The vertical alignment does not depend on text direction. match self { Self::Top => FixedAlignment::Start, Self::Horizon => FixedAlignment::Center, @@ -442,6 +450,14 @@ impl Add for VAlignment { } } +impl Resolve for VAlignment { + type Output = FixedAlignment; + + fn resolve(self, _: StyleChain) -> Self::Output { + self.fix(Dir::TTB) + } +} + impl From for Alignment { fn from(align: VAlignment) -> Self { Self::V(align) @@ -474,9 +490,9 @@ pub enum OuterVAlignment { Bottom, } -impl OuterVAlignment { - /// Resolve the axis alignment based on the vertical direction. - pub const fn fix(self) -> FixedAlignment { +impl FixAlignment for OuterVAlignment { + fn fix(self, _: Dir) -> FixedAlignment { + // The vertical alignment does not depend on text direction. match self { Self::Top => FixedAlignment::Start, Self::Bottom => FixedAlignment::End, @@ -526,8 +542,8 @@ pub enum SpecificAlignment { impl SpecificAlignment where - H: Copy, - V: Copy, + H: Default + Copy + FixAlignment, + V: Default + Copy + FixAlignment, { /// The horizontal component. pub const fn x(self) -> Option { @@ -544,6 +560,26 @@ where Self::H(_) => None, } } + + /// Normalize the alignment to a LTR-TTB space. + pub fn fix(self, text_dir: Dir) -> Axes { + Axes::new( + self.x().unwrap_or_default().fix(text_dir), + self.y().unwrap_or_default().fix(text_dir), + ) + } +} + +impl Resolve for SpecificAlignment +where + H: Default + Copy + FixAlignment, + V: Default + Copy + FixAlignment, +{ + type Output = Axes; + + fn resolve(self, styles: StyleChain) -> Self::Output { + self.fix(TextElem::dir_in(styles)) + } } impl From> for Alignment @@ -631,6 +667,15 @@ impl FixedAlignment { Self::End => extent, } } + + /// The inverse alignment. + pub const fn inv(self) -> Self { + match self { + Self::Start => Self::End, + Self::Center => Self::Center, + Self::End => Self::Start, + } + } } impl From for FixedAlignment { diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs index d35b39cc5..003c9db7d 100644 --- a/crates/typst/src/layout/flow.rs +++ b/crates/typst/src/layout/flow.rs @@ -9,7 +9,7 @@ use crate::introspection::{Meta, MetaElem}; use crate::layout::{ Abs, AlignElem, Axes, BlockElem, ColbreakElem, ColumnsElem, FixedAlignment, Fr, Fragment, Frame, FrameItem, LayoutMultiple, LayoutSingle, PlaceElem, Point, Regions, - Rel, Size, Spacing, VAlignment, VElem, + Rel, Size, Spacing, VElem, }; use crate::model::{FootnoteElem, FootnoteEntry, ParElem}; use crate::util::Numeric; @@ -311,7 +311,7 @@ impl<'a> FlowLayouter<'a> { let x_align = alignment.map_or(FixedAlignment::Center, |align| { align.x().unwrap_or_default().resolve(styles) }); - let y_align = alignment.map(|align| align.y().map(VAlignment::fix)); + let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles))); let mut frame = placed.layout(engine, styles, self.regions)?.into_frame(); frame.meta(styles, false); let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance }; diff --git a/crates/typst/src/layout/frame.rs b/crates/typst/src/layout/frame.rs index 3a21078e4..09e36f8f4 100644 --- a/crates/typst/src/layout/frame.rs +++ b/crates/typst/src/layout/frame.rs @@ -255,14 +255,18 @@ impl Frame { } } - /// Resize the frame to a new size, distributing new space according to the - /// given alignments. - pub fn resize(&mut self, target: Size, align: Axes) { - if self.size != target { - let offset = align.zip_map(target - self.size, FixedAlignment::position); - self.size = target; - self.translate(offset.to_point()); + /// Adjust the frame's size, translate the original content by an offset + /// computed according to the given alignments, and return the amount of + /// offset. + pub fn resize(&mut self, target: Size, align: Axes) -> Point { + if self.size == target { + return Point::zero(); } + let offset = + align.zip_map(target - self.size, FixedAlignment::position).to_point(); + self.size = target; + self.translate(offset); + offset } /// Move the baseline and contents of the frame by an offset. diff --git a/crates/typst/src/math/align.rs b/crates/typst/src/math/align.rs index 0674f49ea..467f7ac97 100644 --- a/crates/typst/src/math/align.rs +++ b/crates/typst/src/math/align.rs @@ -1,7 +1,7 @@ use crate::diag::SourceResult; use crate::foundations::{elem, Packed, StyleChain}; use crate::layout::Abs; -use crate::math::{LayoutMath, MathContext, MathFragment, MathRow}; +use crate::math::{LayoutMath, MathContext, MathFragment, MathRun}; /// A math alignment point: `&`, `&&`. #[elem(title = "Alignment Point", LayoutMath)] @@ -20,7 +20,7 @@ pub(super) struct AlignmentResult { } /// Determine the positions of the alignment points, according to the input rows combined. -pub(super) fn alignments(rows: &[MathRow]) -> AlignmentResult { +pub(super) fn alignments(rows: &[MathRun]) -> AlignmentResult { let mut widths = Vec::::new(); let mut pending_width = Abs::zero(); diff --git a/crates/typst/src/math/ctx.rs b/crates/typst/src/math/ctx.rs index 9acf07359..70de8a8e5 100644 --- a/crates/typst/src/math/ctx.rs +++ b/crates/typst/src/math/ctx.rs @@ -15,7 +15,7 @@ use crate::foundations::{Content, Packed, Smart, StyleChain}; use crate::layout::{Abs, Axes, BoxElem, Em, Frame, LayoutMultiple, Regions, Size}; use crate::math::{ scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment, - LayoutMath, MathFragment, MathRow, MathSize, THICK, + LayoutMath, MathFragment, MathRun, MathSize, THICK, }; use crate::model::ParElem; use crate::syntax::{is_newline, Span}; @@ -122,7 +122,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { self.fragments.extend(fragments); } - /// Layout the given element and return the resulting `MathFragment`s. + /// Layout the given element and return the resulting [`MathFragment`]s. pub fn layout_into_fragments( &mut self, elem: &dyn LayoutMath, @@ -137,27 +137,26 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { Ok(std::mem::replace(&mut self.fragments, prev)) } - /// Layout the given element and return the result as a `MathRow`. - pub fn layout_into_row( + /// Layout the given element and return the result as a [`MathRun`]. + pub fn layout_into_run( &mut self, elem: &dyn LayoutMath, styles: StyleChain, - ) -> SourceResult { - let row = self.layout_into_fragments(elem, styles)?; - Ok(MathRow::new(row)) + ) -> SourceResult { + Ok(MathRun::new(self.layout_into_fragments(elem, styles)?)) } /// Layout the given element and return the result as a - /// unified `MathFragment`. + /// unified [`MathFragment`]. pub fn layout_into_fragment( &mut self, elem: &dyn LayoutMath, styles: StyleChain, ) -> SourceResult { - Ok(self.layout_into_row(elem, styles)?.into_fragment(self, styles)) + Ok(self.layout_into_run(elem, styles)?.into_fragment(self, styles)) } - /// Layout the given element and return the result as a `Frame`. + /// Layout the given element and return the result as a [`Frame`]. pub fn layout_into_frame( &mut self, elem: &dyn LayoutMath, @@ -166,7 +165,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { Ok(self.layout_into_fragment(elem, styles)?.into_frame()) } - /// Layout the given `BoxElem` into a `Frame`. + /// Layout the given [`BoxElem`] into a [`Frame`]. pub fn layout_box( &mut self, boxed: &Packed, @@ -177,7 +176,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { boxed.layout(self.engine, styles.chain(&local), self.regions) } - /// Layout the given `Content` into a `Frame`. + /// Layout the given [`Content`] into a [`Frame`]. pub fn layout_content( &mut self, content: &Content, @@ -190,7 +189,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { .into_frame()) } - /// Layout the given `TextElem` into a `MathFragment`. + /// Layout the given [`TextElem`] into a [`MathFragment`]. pub fn layout_text( &mut self, elem: &Packed, @@ -238,7 +237,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { let c = styled_char(styles, c); fragments.push(GlyphFragment::new(self, styles, c, span).into()); } - let frame = MathRow::new(fragments).into_frame(self, styles); + let frame = MathRun::new(fragments).into_frame(self, styles); FrameFragment::new(self, styles, frame).with_text_like(true).into() } else { let local = [ @@ -263,7 +262,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { .push(self.layout_complex_text(piece, span, styles)?.into()); } } - let mut frame = MathRow::new(fragments).into_frame(self, styles); + let mut frame = MathRun::new(fragments).into_frame(self, styles); let axis = scaled!(self, styles, axis_height); frame.set_baseline(frame.height() / 2.0 + axis); FrameFragment::new(self, styles, frame).into() @@ -274,7 +273,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> { Ok(fragment) } - /// Layout the given text string into a `FrameFragment`. + /// Layout the given text string into a [`FrameFragment`]. fn layout_complex_text( &mut self, text: &str, diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs index a272622e8..ff34f40ee 100644 --- a/crates/typst/src/math/equation.rs +++ b/crates/typst/src/math/equation.rs @@ -11,9 +11,11 @@ use crate::foundations::{ use crate::introspection::{Count, Counter, CounterUpdate, Locatable}; use crate::layout::{ Abs, AlignElem, Alignment, Axes, Em, FixedAlignment, Frame, LayoutMultiple, - LayoutSingle, OuterHAlignment, Point, Regions, Size, + LayoutSingle, OuterHAlignment, Point, Regions, Size, SpecificAlignment, VAlignment, +}; +use crate::math::{ + scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant, }; -use crate::math::{scaled_font_size, LayoutMath, MathContext, MathSize, MathVariant}; use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement}; use crate::syntax::Span; use crate::text::{ @@ -78,18 +80,20 @@ pub struct EquationElem { /// The alignment of the equation numbering. /// - /// By default, the number is put at the `{end}` of the equation block. Both - /// `{start}` and `{end}` respects text direction; for absolute positioning, - /// use `{left}` or `{right}`. + /// By default, the alignment is `{end} + {horizon}`. For the horizontal + /// component, you can use `{right}`, `{left}`, or `{start}` and `{end}` + /// of the text direction; for the vertical component, you can use + /// `{top}`, `{horizon}`, or `{bottom}`. /// /// ```example - /// #set math.equation(numbering: "(1)", number-align: start) + /// #set math.equation(numbering: "(1)", number-align: bottom) /// - /// With natural units, we know: - /// $ E^2 = m^2 + p^2 $ + /// We can calculate: + /// $ E &= sqrt(m_0^2 + p^2) \ + /// &approx 125 "GeV" $ /// ``` - #[default(OuterHAlignment::End)] - pub number_align: OuterHAlignment, + #[default(SpecificAlignment::Both(OuterHAlignment::End, VAlignment::Horizon))] + pub number_align: SpecificAlignment, /// A supplement for the equation. /// @@ -210,12 +214,12 @@ impl Packed { let font = find_math_font(engine, styles, self.span())?; let mut ctx = MathContext::new(engine, styles, regions, &font); - let rows = ctx.layout_into_row(self, styles)?; + let run = ctx.layout_into_run(self, styles)?; - let mut items = if rows.row_count() == 1 { - rows.into_par_items() + let mut items = if run.row_count() == 1 { + run.into_par_items() } else { - vec![MathParItem::Frame(rows.into_fragment(&ctx, styles).into_frame())] + vec![MathParItem::Frame(run.into_fragment(&ctx, styles).into_frame())] }; for item in &mut items { @@ -251,29 +255,39 @@ impl LayoutSingle for Packed { let font = find_math_font(engine, styles, span)?; let mut ctx = MathContext::new(engine, styles, regions, &font); - let mut frame = ctx.layout_into_frame(self, styles)?; + let equation_builder = ctx + .layout_into_run(self, styles)? + .multiline_frame_builder(&ctx, styles); - if let Some(numbering) = (**self).numbering(styles) { - let pod = Regions::one(regions.base(), Axes::splat(false)); - let number = Counter::of(EquationElem::elem()) - .at(engine, self.location().unwrap())? - .display(engine, numbering)? - .spanned(span) - .layout(engine, styles, pod)? - .into_frame(); + let Some(numbering) = (**self).numbering(styles) else { + return Ok(equation_builder.build()); + }; - static NUMBER_GUTTER: Em = Em::new(0.5); - let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles); + let pod = Regions::one(regions.base(), Axes::splat(false)); + let number = Counter::of(EquationElem::elem()) + .at(engine, self.location().unwrap())? + .display(engine, numbering)? + .spanned(span) + .layout(engine, styles, pod)? + .into_frame(); - add_equation_number( - &mut frame, - number, - self.number_align(styles).resolve(styles), - AlignElem::alignment_in(styles).resolve(styles).x, - regions.size.x, - full_number_width, - ); - } + static NUMBER_GUTTER: Em = Em::new(0.5); + let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles); + + let number_align = match self.number_align(styles) { + SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon), + SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v), + SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v), + }; + + let frame = add_equation_number( + equation_builder, + number, + number_align.resolve(styles), + AlignElem::alignment_in(styles).resolve(styles).x, + regions.size.x, + full_number_width, + ); Ok(frame) } @@ -396,35 +410,74 @@ fn find_math_font( } fn add_equation_number( - equation: &mut Frame, + equation_builder: MathRunFrameBuilder, number: Frame, - number_align: FixedAlignment, + number_align: Axes, equation_align: FixedAlignment, region_size_x: Abs, full_number_width: Abs, -) { +) -> Frame { + let first = equation_builder + .frames + .first() + .map_or((equation_builder.size, Point::zero()), |(frame, point)| { + (frame.size(), *point) + }); + let last = equation_builder + .frames + .last() + .map_or((equation_builder.size, Point::zero()), |(frame, point)| { + (frame.size(), *point) + }); + let mut equation = equation_builder.build(); + let width = if region_size_x.is_finite() { region_size_x } else { equation.width() + 2.0 * full_number_width }; - - let height = equation.height().max(number.height()); - equation.resize(Size::new(width, height), Axes::splat(equation_align)); - - let offset = match (equation_align, number_align) { + let height = match number_align.y { + FixedAlignment::Start => { + let (size, point) = first; + let excess_above = (number.height() - size.y) / 2.0 - point.y; + equation.height() + Abs::zero().max(excess_above) + } + FixedAlignment::Center => equation.height().max(number.height()), + FixedAlignment::End => { + let (size, point) = last; + let excess_below = + (number.height() + size.y) / 2.0 - equation.height() + point.y; + equation.height() + Abs::zero().max(excess_below) + } + }; + let resizing_offset = equation.resize( + Size::new(width, height), + Axes::::new(equation_align, number_align.y.inv()), + ); + equation.translate(Point::with_x(match (equation_align, number_align.x) { (FixedAlignment::Start, FixedAlignment::Start) => full_number_width, (FixedAlignment::End, FixedAlignment::End) => -full_number_width, _ => Abs::zero(), - }; - equation.translate(Point::with_x(offset)); + })); - let x = match number_align { + let x = match number_align.x { FixedAlignment::Start => Abs::zero(), FixedAlignment::End => equation.width() - number.width(), _ => unreachable!(), }; - let y = (equation.height() - number.height()) / 2.0; + let dh = |h1: Abs, h2: Abs| (h1 - h2) / 2.0; + let y = match number_align.y { + FixedAlignment::Start => { + let (size, point) = first; + resizing_offset.y + point.y + dh(size.y, number.height()) + } + FixedAlignment::Center => dh(equation.height(), number.height()), + FixedAlignment::End => { + let (size, point) = last; + resizing_offset.y + point.y + dh(size.y, number.height()) + } + }; equation.push_frame(Point::new(x, y), number); + equation } diff --git a/crates/typst/src/math/matrix.rs b/crates/typst/src/math/matrix.rs index b4d34a910..83d50dd6c 100644 --- a/crates/typst/src/math/matrix.rs +++ b/crates/typst/src/math/matrix.rs @@ -393,7 +393,7 @@ fn layout_vec_body( let denom_style = style_for_denominator(styles); let mut flat = vec![]; for child in column { - flat.push(ctx.layout_into_row(child, styles.chain(&denom_style))?); + flat.push(ctx.layout_into_run(child, styles.chain(&denom_style))?); } Ok(stack(flat, align, gap, 0)) @@ -455,7 +455,7 @@ fn layout_mat_body( let denom_style = style_for_denominator(styles); for (row, (ascent, descent)) in rows.iter().zip(&mut heights) { for (cell, col) in row.iter().zip(&mut cols) { - let cell = ctx.layout_into_row(cell, styles.chain(&denom_style))?; + let cell = ctx.layout_into_run(cell, styles.chain(&denom_style))?; ascent.set_max(cell.ascent()); descent.set_max(cell.descent()); diff --git a/crates/typst/src/math/row.rs b/crates/typst/src/math/row.rs index e49c91423..afed82316 100644 --- a/crates/typst/src/math/row.rs +++ b/crates/typst/src/math/row.rs @@ -14,10 +14,12 @@ use super::fragment::SpacingFragment; pub const TIGHT_LEADING: Em = Em::new(0.25); +/// A linear collection of [`MathFragment`]s. #[derive(Debug, Default, Clone)] -pub struct MathRow(Vec); +pub struct MathRun(Vec); -impl MathRow { +impl MathRun { + /// Takes the given [`MathFragment`]s and do some basic processing. pub fn new(fragments: Vec) -> Self { let iter = fragments.into_iter().peekable(); let mut last: Option = None; @@ -93,11 +95,7 @@ impl MathRow { self.0.iter() } - /// Extract the sublines of the row. - /// - /// It is very unintuitive, but in current state of things, a `MathRow` can - /// contain several actual rows. That function deconstructs it to "single" - /// rows. Hopefully this is only a temporary hack. + /// Split by linebreaks, and copy [`MathFragment`]s into rows. pub fn rows(&self) -> Vec { self.0 .split(|frag| matches!(frag, MathFragment::Linebreak)) @@ -141,11 +139,10 @@ impl MathRow { } pub fn into_frame(self, ctx: &MathContext, styles: StyleChain) -> Frame { - let align = AlignElem::alignment_in(styles).resolve(styles).x; if !self.is_multiline() { - self.into_line_frame(&[], align) + self.into_line_frame(&[], AlignElem::alignment_in(styles).resolve(styles).x) } else { - self.multiline_frame_builder(ctx, styles, align).build() + self.multiline_frame_builder(ctx, styles).build() } } @@ -157,14 +154,14 @@ impl MathRow { } } - /// Returns a builder that lays out `MathFragment`s into a multi-row frame. The set - /// of alignment points are computed from those rows combined. + /// Returns a builder that lays out the [`MathFragment`]s into a possibly + /// multi-row [`Frame`]. The rows are aligned using the same set of alignment + /// points computed from them as a whole. pub fn multiline_frame_builder( self, ctx: &MathContext, styles: StyleChain, - align: FixedAlignment, - ) -> MathRowFrameBuilder { + ) -> MathRunFrameBuilder { let rows: Vec<_> = self.rows(); let row_count = rows.len(); let alignments = alignments(&rows); @@ -176,6 +173,7 @@ impl MathRow { TIGHT_LEADING.at(font_size) }; + let align = AlignElem::alignment_in(styles).resolve(styles).x; let mut frames: Vec<(Frame, Point)> = vec![]; let mut size = Size::zero(); for (i, row) in rows.into_iter().enumerate() { @@ -197,10 +195,11 @@ impl MathRow { frames.push((sub, pos)); } - MathRowFrameBuilder { size, frames } + MathRunFrameBuilder { size, frames } } - /// Lay out `MathFragment`s into a one-row frame, with alignment points respected. + /// Lay out [`MathFragment`]s into a one-row [`Frame`], using the + /// caller-provided alignment points. pub fn into_line_frame(self, points: &[Abs], align: FixedAlignment) -> Frame { let ascent = self.ascent(); let mut frame = Frame::soft(Size::new(Abs::zero(), ascent + self.descent())); @@ -347,7 +346,7 @@ impl MathRow { } } -impl> From for MathRow { +impl> From for MathRun { fn from(fragment: T) -> Self { Self(vec![fragment.into()]) } @@ -372,17 +371,17 @@ impl Iterator for LeftRightAlternator { } } -/// How the rows should be aligned and merged into a Frame. -pub struct MathRowFrameBuilder { +/// How the rows from the [`MathRun`] should be aligned and merged into a [`Frame`]. +pub struct MathRunFrameBuilder { /// The size of the resulting frame. - size: Size, - /// Sub frames, and the positions where they should be pushed into + pub size: Size, + /// Sub frames for each row, and the positions where they should be pushed into /// the resulting frame. - frames: Vec<(Frame, Point)>, + pub frames: Vec<(Frame, Point)>, } -impl MathRowFrameBuilder { - /// Consumes the builder and returns a `Frame`. +impl MathRunFrameBuilder { + /// Consumes the builder and returns a [`Frame`]. pub fn build(self) -> Frame { let mut frame = Frame::soft(self.size); for (sub, pos) in self.frames.into_iter() { diff --git a/crates/typst/src/math/underover.rs b/crates/typst/src/math/underover.rs index 1e5fe861c..4117342bd 100644 --- a/crates/typst/src/math/underover.rs +++ b/crates/typst/src/math/underover.rs @@ -3,7 +3,7 @@ use crate::foundations::{elem, Content, Packed, StyleChain}; use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size}; use crate::math::{ alignments, scaled_font_size, style_cramped, style_for_subscript, AlignmentResult, - FrameFragment, GlyphFragment, LayoutMath, MathContext, MathRow, Scaled, + FrameFragment, GlyphFragment, LayoutMath, MathContext, MathRun, Scaled, }; use crate::syntax::Span; use crate::text::TextElem; @@ -260,13 +260,13 @@ fn layout_underoverspreader( ) -> SourceResult<()> { let font_size = scaled_font_size(ctx, styles); let gap = gap.at(font_size); - let body = ctx.layout_into_row(body, styles)?; + let body = ctx.layout_into_run(body, styles)?; let body_class = body.class(); let body = body.into_fragment(ctx, styles); let glyph = GlyphFragment::new(ctx, styles, c, span); let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero()); - let mut rows = vec![MathRow::new(vec![body]), stretched.into()]; + let mut rows = vec![MathRun::new(vec![body]), stretched.into()]; let (sup_style, sub_style); let row_styles = if reverse { @@ -280,7 +280,7 @@ fn layout_underoverspreader( rows.extend( annotation .as_ref() - .map(|annotation| ctx.layout_into_row(annotation, row_styles)) + .map(|annotation| ctx.layout_into_run(annotation, row_styles)) .transpose()?, ); @@ -301,7 +301,7 @@ fn layout_underoverspreader( /// Add a `gap` between each row and uses the baseline of the `baseline`th /// row for the whole frame. pub(super) fn stack( - rows: Vec, + rows: Vec, align: FixedAlignment, gap: Abs, baseline: usize, diff --git a/tests/ref/math/equation-number.png b/tests/ref/math/equation-number.png index 96a1f21ae..8ba915902 100644 Binary files a/tests/ref/math/equation-number.png and b/tests/ref/math/equation-number.png differ diff --git a/tests/typ/math/equation-number.typ b/tests/typ/math/equation-number.typ index aefa308a9..3de611b3f 100644 --- a/tests/typ/math/equation-number.typ +++ b/tests/typ/math/equation-number.typ @@ -94,3 +94,52 @@ $ a + b = c $ --- // Error: 52-58 expected `start`, `left`, `right`, or `end`, found center #set math.equation(numbering: "(1)", number-align: center) + +--- +// Error: 52-67 expected `start`, `left`, `right`, or `end`, found center +#set math.equation(numbering: "(1)", number-align: center + bottom) + +--- +#set math.equation(numbering: "(1)") + +$ p &= ln a b \ + &= ln a + ln b $ + +--- +#set math.equation(numbering: "(1)", number-align: top+start) + +$ p &= ln a b \ + &= ln a + ln b $ + +--- +#show math.equation: set align(left) +#set math.equation(numbering: "(1)", number-align: bottom) + +$ q &= ln sqrt(a b) \ + &= 1/2 (ln a + ln b) $ + +--- +// Tests that if the numbering's layout box vertically exceeds the box of +// the equation frame's boundary, the latter's frame is resized correctly +// to encompass the numbering. #box() below delineates the resized frame. +// +// A row with "-" only has a height that's smaller than the height of the +// numbering's layout box. Note we use pattern "1" here, not "(1)", since +// the parenthesis exceeds the numbering's layout box, due to the default +// settings of top-edge and bottom-edge of the TextElem that laid it out. +#set math.equation(numbering: "1", number-align: top) +#box( +$ - &- - \ + a &= b $, +fill: silver) + +#set math.equation(numbering: "1", number-align: horizon) +#box( +$ - - - $, +fill: silver) + +#set math.equation(numbering: "1", number-align: bottom) +#box( +$ a &= b \ + - &- - $, +fill: silver)