From e9ee00a7c0df083663ff5ccca162238b88525e14 Mon Sep 17 00:00:00 2001 From: Leedehai <18319900+Leedehai@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:57:21 -0500 Subject: [PATCH] Add vertical alignment to equation number [Better Equation Numbering Pt.3] (#3482) --- crates/typst/src/layout/align.rs | 71 +++++++++++--- crates/typst/src/layout/flow.rs | 4 +- crates/typst/src/layout/frame.rs | 18 ++-- crates/typst/src/math/align.rs | 4 +- crates/typst/src/math/ctx.rs | 31 +++--- crates/typst/src/math/equation.rs | 145 ++++++++++++++++++++--------- crates/typst/src/math/matrix.rs | 4 +- crates/typst/src/math/row.rs | 47 +++++----- crates/typst/src/math/underover.rs | 10 +- tests/ref/math/equation-number.png | Bin 18851 -> 23214 bytes tests/typ/math/equation-number.typ | 49 ++++++++++ 11 files changed, 266 insertions(+), 117 deletions(-) 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 96a1f21ae637e8852e8432a2e3c9506348dea2bc..8ba915902fa593fd9f0abf8cf8dc29d3b8042c24 100644 GIT binary patch literal 23214 zcmeFZRZv`Sx9;0`L$D468kYuwTX5G#LvVL@2*F*N5L^-@5FCQLd*dDm1ozy^^Wm8zh`u~vXTrI1~CQz0Kk%yl~M%&5Rd@?G&@%s7ZbY*dt@${Bnp2>PRnek4gZfasaNB6#+hb9|X z^+t|4f3!fJLE~Oi!A{OByzeetRRKf=Lk7uz;iigzZ=SYYalQO$WMuHW9pkW~z;Qpt z^>k+^7>%j+6aa`p8cq;MTp;EXYDP)lNzkI>;%Xc{%b&;v_cq%p&pPE-g z<8vqNMG?8&sEn45(~HV;DX12z;iwRMfxs%}x1wiLmCAq>@?XvL_+&@(hR7?lV8Lub zMSlzraROh9Y+{7qMF)|t1J&J)0+497Hvz)7)IRhJxq$gi({U}et2O)YvU`=!ya;zjsG$5msPOJ`oi}@r^R|z>hN8#tN!`r(9DF2EDHz4)CY(K zmM-J43jQZ~ks!S0Hz~=6`JlMOAlmkWm!rR{r=w0B-xzn`e`d7*S~`qt(G-^te5NlzgO$#plmpsjPW615`fzNEFT5Ut}g$4t7xRIN{HvORrgq z8qm+igsIv99%9wv8>A<^PnGbe`6fu|&1@=&{QAoE)$*L}2G%(!qZxaj-5~a7p~$jf zxn8BL{C6pHmF#p^f2(6k5`nQ)&?-Y*S-&r`=g@h+l}V9E(14}lj{{+^E=%(%CmpKb zdOSh~*?Y9WA~lMI73KVOH^s{~;{Xt=w=uOV<`rQO+=;_wiDi-;QfU<1dAw^n&kQ}H z4*o(({RQ~nzq&;EyE?H-2CQl>!S?Nr_n-+&z8T7ZTXL~0N_3q&xE>F9%>;waw&iO@ za61CuRG!_a2QN~OM_ngSE!pg#y*BCL+Z-R!O^r0CqO10+6R)gMinV)bkLZV5+t-0J zaq0u^rLS=H#^ynP8!~>!tnT{yTz<^hzM3}pP&p75hS0o&7zkBmhWnUIN3+-MX6Tex z*h&|w9oDROFOGj(EHE2?7T7ip(k{RZoLrU@lV-p+o-i+grmT{paqn46c~WBi!OIp5 z`TN)I4VG>oW08BnN!>v`dg<6ZP{ONfx3W^AwbE`Ju_=|p>aXT_TMUup(t)fPIr)j! z;G=JtdRwO9p%kZz{WFf#3-_P7As5U~fO2_+LOTB8p>G;VQ}nHjNDKLZkrsv~GmaJi zKwFEscl1h0)t1QHTX04OrFD1D4nlVQ=3we=`EQ-V>I-?9MjNE-gL`W{A7iR!t&PQm z5jjUQ&3>R0ZHkD$nQBfHcT#7{X9ox;r7RI?xUP#*(U7ki(=tJ`j*!26Bvv9Ef$s32 z5b$TnNanes4!E|2qww&GpRVTW1@@a=T1`h)R{1{+22sQ=H^F^jE5s{HS9>a4(Py3s zq+Il`6<{5~%yqvhDKZBQ-Qq@FYAlgsyedXKxwhYq$WZ?A@nW`K*j^`#3$?J721Lc< zAI3Eu)Jq^cyqi&uvt$M|o)`Q@5YtU$PK%6_4DudVjxFk4nO!lrInq=#Z}74W0CC+f3*M(Aymr&EKSg_>~q8@!av)NZfAsfMDA#4n#LAc*@_Ba_`jGaW4X!y0Gj` zmm=BK?IQeIVYhg0Mh}pS{F?wkWwW0lMBiR`@^U9Y(5ELY7I$H}U{tXd#qIn4yB$Id z{a`Z;_=iVM;}}QQ>Ug7VZp1~D*Ow`=fNRh8St$(38Q}p${bnv7yH^pqGZ-6Y=UZvB z#bo4#8F^XFC)tl!8uQDY-|W6TO7k6thAWW2RNHhpeBe>M=n5QUbOAn)d;RYV?tnw$ ztm+Wdnv(C0S--^81-kN>^c#Nly#`Ce9W#!LvYlR97T#mBBxl%@U6hqN@oH6bB?o`) zjp7Zz&g&#qYV$ynu>u3FoD2U>SlgT3w%XYyz3VAfL!dS6lDHjgcgO|79z32d#eMhb8z77 zYE(t9l);$!vU*0v-l-Bxy`CMx+{I`XSTfcz#@jnxFv85Ro7s$Jx3R-jqrG3L0`z}4_5HRQ0?c% z4s`y)`D6~Cn2)QHxj-sf2aYak2E^BqEYt(`uxGhzRohzi z4~=dGeG+0b9z?RFf*6OSz$Ar=%(q^lR>V-H4%9-Jq83bX$x7U4BPQPz8Y}$*R5TqK0ngL zW3RJJeSDu}dC&=Ke_#A4ExUh-YgU6;)d3W8o-teP!1jp}OC5wNru7aO{L2Xo8Pquh zQdQ)+A~JCUpn6~X%mT;UqhG&b>YC<dJ)$Vu0bPG_} zQuB#KL}eYGF)>v$ap>yqME3eI>Xx-s%EQ?f6?wL`Ldv#?yMiGE^T$goY#sch&t5!~ zjo-LYFF8pum2_k+Wv%LhVzOzfym7GN;0#s{)inA>er@PU4&l0xaWC$gjuPay%=LV> z!qDFqAQ~?q-h$8&UZeVj_tL9EJ;713(r@mMzn>&_Hwi%tPXw!b*jM{ax8vRJQ-j@Q zb%B4#gZCCCbe^Y&UPqW#xDq7fp{Fto=ZQ&hc||F6!mNJDuE$?qXJ=u#G~#&VyA{T~ zPj<5{W?oQHWGV?Ww&CBRl`p?NGxpu330yv=be5h$*I8k}uYLZ5!n142MIN;@j|d^9 zR>^yR&T->J7ZK8Yd`>G7y#)B(oCGIYQZ{Fed?g~mW_&_2(q_pcL4o6`>klX{jo!!N zvCQGCSU@>hwIzqtK7zznPB<%78i?iu#qn!ZSXRY$$z9?0FfWI-^VEjlSrzrjjdRET zfR7AXbCQ-_3=Q(fV_TK$ zIX;yZkQjpVMV0FGt~A^jEL~BPnGC!!@1&^pNJn8m!Y3H$>mg<+rzY=cvDWl=EN{*nS@c8TpXBC`Px{#OB>gfejgz)J6Tg?PtfjOI_Jz$1*QL19 zZU43}KmK5WI_qHJvU)Z89cnQlOD`>fTW+`1Prka_NjWq5fm36|H|K_A=?i%h`TIg< z`WH-LsVxA+e3xyLCN<{jdIgT@ZhjJf`lLM2+I!wCHHi9kPLz381j9R|90R47(-Ut2 z5Bbw#rChIdR*Jy<5I^h-`zXeoh;t)DmLq1n@V`H6L50isSRoO;s&QxjF&x@wfQkI; z0l%1!K^JH{!#*L5w7YD{%kl~NxxhF_xSI`_c-cB|6iV*_rj!qlV|k>gKJo{jy2^&t zOe|mE)--f?1t8VreJyO(;&Q>4b`SsHz$+1A6sK{7NF#stCe;BxfcVz9RdQmh%o2jE z5m>v|Ha=mF44*@(2WKC93_LJe!o_WHsu|z1zg81beD?E@@yqPDgb4XV9tKyhY^>)D znZ6g4^jXZB!bX?kgdeS?Y^4sX2FkUj&6pSME=1bN+z4twjhxedUa0ff?@2ekaP2uH zY&Y%A`I+u~P_wnUGq#)6jw!f5fE$Pu>%MH=IzHr5)6{nV$sRj&uC6sf?^E>_sqY5* zQow@zwJ~41spr?|6rRZVmVc!XfWegi@kp@#HLLOT_-74@!PKRJc`3Nlo6J-+Iq(cieOi#q{tTEJ zvvfMiR6{OA(lc9dp(W6WAf2DpC0g#}=_2S(u^;gt)M(`t=kY`e3GA>p5i57u^yQ?}Sv< z4a|Fv3G(mw#~S-DY5GtGoZ&Ug%6#uH&}gC&|HawPSU}7@ihK^i=OX+ zrXQSh$u>}~lndsq`oo&C8Ja%yXFa?9sxpOX?t5AmI`UTE z1!xie&%P>T{YD)6H#b!K?>{Tk zUSPgbFk_AXH7Wk1Zm_3!Uh$63$8k40vl=f*|JtU;mB>-~DmA znX4mqX~JJF$FOT3gzkJMdY5u3qD9HN_jhx9AVxB&%U(}Ppw9N3Hf75{h|@N12-jSc zX(CSCj>*m3`BXZ9L#cZ=cn-Mq6HeWKZvExkFTi(4-ZF5f13~qYhM%u5oqKBJL83Er z)jz3ICg|)TV%z}@gFAxMpDxb+RxL}R)N{No2<~2+5JBzX`6>25S1PEwI1hm+pqLXt zFtXY^s;`E>jlRO%_pN;m$xt6#nxkuyXD(${U9mr zQRMmerbqB5sL`pqY7BUOWxP=%dV9ac>;K6rKIUZIB@h-0DR@ALn|>GH!D83MnnN$+ z`3HMdVtXCkhfD^6l*sBg!+S+ftt@((lQXoP`BkcmI3+}1+}{7)Kz}Ypy1>2-@dMJy z%Hhg3gz(g=06_`)(_3l&uD+uIPSYUE{I3X(tw|w8b(t~CHS3?DFu&$oqa$Fa3gVv7hz5@80@6jHbtC-)DC%=qBGY)(EOouWP3+u>xA)1nY!NSA z%}cvpkiD3(*vClmd3aXq=WtTCEXyg`cpfY9)($pO!?@`q#P_5LU+f6tyYKo0Unnmm zXVJY$W-~IN*9-X@hmY|={z^hJm$CldnAs0;Zd2Vj_YcuaBR2lAMZWlTI>GVEU0anJ zsa|$iW%P_4>`f%?KB!brl5L3#r%**QPF5$;S+D?q-$O2)GI*t9$FWO9ob|$SJLHy; zpmuWfxG8s>yg#L`Xq3Gi2xdl?8C!4euWFq*NP=kFg!Iy-)soU?Ji29;`v@TVC85k8 z577KduG+)xKWHiDzeS84{{o*x&R|;iCYUux7HJLK%O+3k$If7Sybt&zMAh>~&0~Ik zO}TgdHC?OHf(h)8_%%nhDT_NeC{Gx?6YxKsV zXoM?3KcgkNPbIR5gIxRX@mn3-lKchBw~(TNZ9_Of@Qh^a&u5++6Pe?0LQv}#g5Fa$ z6$s!MS!I=0t6rSRj1@ zmGRd@SZMPNoxU*O67~SYR^;BPWJrIOE^B&-QCPtyN0-9ect21ul|;wO%+Ka3Lpk{y zX9`EDT9x)&FG;Uz7;NpU+Za=xE}#iwcPd90Pn4PRe0 z>R=&t{_gGT>@KQ66RcETCqo6TirY7VZLeRMYe`mof0k^*uI}%odJU)#D=;@fl$e}w0hRq;n;e39N1!}9MJ9DR zZ-X(vTpW88FrU9)x0Vu(d!?x_b(wFSIxU<(Bdwl2fA zPVv(bar4mFaN&0c&eiyQugq|K^5;yDr*F-&JOToRp_HTMke{Qb8?67*%>56e_rVO3 zmtr5`0uwe^HX3@>v|Zj$Y3v4yiK~}v2q%o6e}^#ZB9s=E#tQgUPmm8>F*eP`AC63^ z$H{6c*CsLu!w=+PkfxU)QuNhKCj?F6>qLH7cC6zfnG;yaA<0f6VKY7BG%xd{t8r(VS+ytR{>W%f&v-DguV zSazTxSKsZ{m3cRY1<6iSf#_d8*^6UDG$FnC8&23AT_0^)pfJ#z1QS&(^?jkJ1H{Ds zG;|?%=l+4Q<6LWw4ack6JDj*%-r8Q^9jX%|6DfJlfNrz0rhHTRbgriz`>KkN3{(Dv zFm6(Gd__z4jR6k-=I7$)a^pyll7d>G|D}rXqiu8P2G{MK1xB@}4VH2P0r6sD4-hHvpX~{+avS1 z|5&m{O1*%DJ#v|?szmkjiy$=4-!d>W5Ph~EA><(n`W7jMpAsvkOEZ?}zQ>cYhZ}XE zeovK7@&l@JoxmbJ&J+JoX@g&Uk6xHVvaqBqy+Tc{){Qe~fE;sUO#M^z1s>c!f#pa~ z243KSYHVi=ABO`0#N!{3(~BW84O!&#LM5q|i|s7$=?Zsl^BKgcBrBOVrg1cuN01)q zgS8*1+xHcH93c1RA!a`zaJG;|kiNO_h)SP4?6<6KN5g&Iszv#~ z^PD#G$s-Vo>~{;zEVpYyf4|!;sxM@RigFll#s~*5UG{XN7YaR|k(x-yN;8SNIiBB2l+Uelfk%UpV}_>{&q5vg>IWt?=WEoD3kX-?sOu6oBBcd zfQodSOfnv1u|Zy_&y*iBLx^9S?-1X%%Z`v>_=q@Hp6V5SBL1C=ulOEG!^ui#7h~v* zBq9SVsJ9s}HSDUD0blK&x`a6$$vasIz^5V*3*wZ&Ky7y!LUzlf*{?UyQRa_DY8Iq1 zTb5Wd!N^!6H8Ws9Klv83G0fj*y+oRIDMQNXBe+B659?8VR6Z=RHpeXQ%Xg-U8n(@oTS9>L#o#0j(Ctl*g6S#oe!sL=i6+Aot}?h7j|Sd;6`g zlih?ieoES0v%IezPFFnr>Q^~Xl#Ff#s#&JG@d~CJBT5&x5N~G}*}!_-{%;~GPyH-Q ze%CM+5Xiy=zt0x$5E$E@^WCR0`}qUa4qqRzb**PGfc2^o?jyxU*o9HWSIlBTUwG%f z*wlcVBL91Ab^GSO&?qifcTY_RjjEGGy87L79Tf9+uPG}+kUT8IU_ zHyF^m_OUA7-;o)pXRho7{-+p?_2p*9hc|@PuwqDuwbT=KdZ70y?O=b+>@tW$G14i7 z22y&Y#j>}k3*6fDd??J2hV%b!2IYid6ZszrmI29#6ysK4{xe1&`+)e6tot{z5Z*=H6J3 zzi`wqthkOt$@-wA5Zh!|AgY#JH=>>1R!rrg=*yuxSA1245t++LS)GuiAs77K9|<2; zVU8$U`C2x}<`%}`Y(85t&b6i7kXAjES+>w@j0Ku~PAMJM+?{#SlmfwEul~H_%9IZQ zlR(X9&e8+bM?j{_9rAfYx1L#GU*d$a{^9f#9B)dM0UY`M_^V*o$G0z}>YwCjFCvrf zc1&eWJvRh%=at&#&Ikx9M z`+N)hU=Ze(QZA#WPT|Bh!=g=-uSi{8IIbknQR5MZm2Nc(Ji#BBHWNV<+Ps~psZ`Gt zGk-^?bd@1w@~(QXp%hnqAv_X4?-2?9rzrN&sc_;{;OCuTWe1Q@m>nd5`chr14dAAu zB%4^!Y(@X-wd=pX-_|lo*H%AkY}CwoWAYd^JOXXr{jiJc@Wr9@iGwlwt+J3rov@?e z)h!KbiLU^D*iO`KwWJ~ZZJ9TQ6%BK^8|fP3p(*pO zW6U#Jb<}@~uI5fn9IfS#COLKj(mz#-m8zk_ZH{TOa`4gEZK^dOiq0`1#BN;FLUE{j z;p6GzA5ZHdP+7zh)mubwbln$_r?-2u&b#1GpNtdn3qA_AuDk-X8i>b#qP^BCu6loWEB%WjqmEa=Q+B%KP(dy| zNgw-mP&|aXO6rDbK$QgR?DYGA2HG4muN`zX<$5Se=97PyFS0~ia({5WpU5(Tj`mMY zWH?yvH}>~oM<%Ipo=;dPHhqYJf&q-=fIN-C>LwsRejysxWs2@Y^b1wka_p6IPb;`0 zq{=;Zh`N3F{3uO1C>h)NS?quVk)Mv_-fpN;A4t~I zf}RIbWkn2slFQZniOBq16-Zs$7*W+Sfok8^2oa0vuTJ9?E%L_7TFMO$ANLXMtKTU% zW6BW$@Tp(P@a62ItsZO8gP@wdmQe;sCuv+5)UJmB7si4O;jVm+Zr=*WnTQkj8Fa}- zr<}uB5P3#Fy)}Qul9)RL4bk;eyxU63Hn5-RU=6n3LJT@ms4!Z;zw^d#m?7auC~PnC z&j{hJrZ?^{K`e${6I)WIt-P%O{e3#bda$d7!i@6NPSRY_rD3u07FoJ#4?yPujUQ3u7f)-1;3@rR^x@ktGZ>!@x4HU-Y2__&6OmH8 z0c)yz2`ZuD$7X@}kfem5H%*{}x67`q)__Ahko-TQhLhCO(G%w*>GCE zpMZxyY)D%>Tht`CZiW!J0i{apEzmi3JMw)VyX#U2Gdy3RrlCHo(5*~NgvY5pylj3g zXipBK_!Y(cOX%Z}OtI$8o?3D$SrVj~sKlB8N`eY9sofi|Y=}M+W3So02 zMx|MP1(8p=g;CmMLtzzU{-GumIjM&I10S;HxAA5Bp56XqV5YzD`v8IYGiYti#>fQ+MBgRw{E()^GeY2{AsdrVHe1#wio<&LA)LubTen>UgM((d_j1lBvmg$J;48+ z_k)RI8+oX$<8w3x9|CPfU1;~s@L2s6Qqe|oaNCG7JXtr zLtj-%ZJ&-Lr0<{n<6I#KmQghOdBK25MHZww4}k5s#?Z<;BG2+-F`=(ShC|z4F$EtB z&`KOy9%Bkb=Gb6PS6L$VB}A|=<^gvT-z5S?glg=o9i(Q&UEOgS!jfWD6Ti6Y59FEhd!@Qg9UHdQ@@PeK|-+tA(q{ ziYoo3ni)^Tb-+)!TlrT6zhV;+373yf#)2~u|ulDUo77)oywTA>96Fi zsN_+^&`5pl2hvJjY3>%u|NlJ8S8N?ajLC6#N{jChM)O_68;;nHlD6 zq_W}kB`YEBJYpkixu(Zmrb~$5e=soq$_icYoGoKv@1lAwJ{Tym(UMP-pfhZ;;hcl5~yCq#t+^>mTmoy zyX}x_%+7JM_|A2n-X zrgFrfWmhBtZV5NGAz9So6wY^)^P9CSPI4?E{^8+utfbyirE>lPmso8*_KB_8AY~a`4!81DBy>|i!F2KA2w29ic0$J zin7E75yG+AeX-)<@#osho~LV_w<{CVG4pfMVO@e(v#tJ%g)+N{H8&Xx&4d0Sk38_U z5bo=0gC4Ihs93!?y-a3H$yQm1<2@=FXq^O*Jk4Lwq?()#E8GZ;b+2;4%O{m{*vGU^ z^+@ZC<33SF2?;?(vb)6>lt7YX%$&b%CKv_376X^>%#}IL zpqOG+O)1m($=%ZqbG%WNIHF{!nbO zY}&@5u+4#AAY4Ry=l_-24bp&|oHDR;8;i0X_4MZOeP|pfRGSfeAqN(U^mIb3^CmFG zhJElY5AF-Kx;qB%Tu1(c&+-3ccDq(0oc-RR-py?kgpg>y2?sWy{zNkG--#zXp?%I9 zbuL$-p>~Z%tM2eE7`9uY0QllbkqaqyN;zET;8LA@3M_iH=9s996SUFyy|wW_rKui1 z^`A(T38Q5+=E*YDlv>8;nE#Nbn#k0!HZ00lGKEISIj%3ZBizvbzO|r z=RJ2w3B?y_I=+7KXaQmnxh&G9)XuN0#v5Jf5P~fE;w1`Sx0zD#T6A0Bo7? z+Ft?>0CloZMnO3qlZ@&e737^9N?qOX&udD9l@_coOn#51kaOQ-O#Js4p~64Jb|E#< zpQ4UJYMz(*q5m?oTSlHeA+Bx|g%%v=mJy9o{ZHal@14ZgQF_f=Ksfp5OktxXbli2W zAFL_S;|>_iE<&-NWp^>-cHJi$@wY+qW%2%7o=nPzUGH>ufO z{QIvT-Lxw}om$%{PUYfnT9;*C7dmp2r6sK2C9*)RaRY}c9Et$yqrArU?t@@$h{4pC z@lurWTQl`x-za^t#HCd;VX6sLy(r})c1FMIfbY*aJ4a#hVHPb&xy3on*S!}VQGOQ9 z_CyhXUaug$-^($HIbwHy#WF40g_fy+up!dbptOzCdKj_yJ0~>Y_G~J&nwuV5F8d_U zoQ;dC*t7+wZ{ls7ff5RjeELfx#^|#->J+wN*|l^Eqow6Ej;tk}V#W!{PJZc^2j(px zTU3MxDsF(gTwQ#f!RPoQCAjWVU7Up_q|^gdq$oao@4^v+qtbSS5(l!61kG2V^3h!) z5h#SjPc4PS8}EXCbEkuS=ImjGuNmxFd`MZ$7LdY^OzOdF`|$uBhiftUQF#x5Dk&cO zmBz7+VU^Q|`?mDTKoPLTS(pR&S?5`uv(3@k9w1#nEhca(pWvGa6XY znk<#jjg23>Kf_`DBAdxC)lS*k*_z7+KavrdFXM-SKTO*T z$wj$5F^e}R_;1`ib;%*EuCM0^sle11X)iOsgaMkF|8aPJy6Op>~2Dd;iz*Y($6QCH2ASI3zb-fBtE# z>EnxBQ`^SGN8e^MX!lfFWUlY*KX(IMU*H6WJ(dT7(VSJoot_VZ#-O-V448Dh8s@p zkQ&<=@J@yFk6$Jm$o2sB%XA&`ubt1-pLFz3K;8%;E5=9MHK03B_~aRqvW0`bQzZvp zuh$Z5Aez>>H~d}(;MYA2hc0b(39Jeiqma@sgy$LdsD-(3 zg{yfd89nlE^f*t7bI|I%WXAsdE!Th@GOI?g9#@HUZ{Pdae z(TsiV6Y_oJiM1Ft^kLg94D@9oD9Atme;8~FZ($#I`imkIek0s2DR= zWdjvx8@e%ewE|5Ro}RQAJ49YWN{x2})h2Ihg23iJf=ECAvf0QqW-qv2VvfBDpc|!w z+;}18wf~7e-*yq3;}hxXN!OH2jybA-*-j8JI3_wLew(waj(2@|v~AB3JZu~2JlK6` z*P%;AzjxF(JIY%SoLQ>}3D(=~9(ToL9MgnKh}RX3aFDl(EE7QfETZ7jCko;Wr<=aC zt%xe}UvnptRW#8+r)Gq7;_PNZ3mPO1#qUJ8hc5H(9kMh3sY?eRrJJqt#)2}URixEF zJ`I=jrdzP6EPmrboJz|vQRc|6#0toFsoX%wQ#g*lCtXL}#Tie&M%9p?Fk)>>q*0Io zQ7#4UPq1^f{sx2|`Zu|MdrhTxC+ZT=R9_$|@FVf+XIM%b8Nm}t3T?X7R9jAbE`7Cv zuWbv6_#wE|g^*Gup<>(~2};0+uuhi1(ICV#jP40Eqf4-0EjqTz{EN5k*N-L3!9kA=L*T~#J4$gs}um3P42;!~lSbnoXvudc>X z<6>#9F^_eMH&oeVKe7EB!@lNYDX=F^oL7-SSMMn?FS<=%thk=Ee`Ce{=gKIq3y31E zwsdN^xteO&{olV`YFJf$ibI7JU3VDt(-cPBgTI@>P4aax_5vh=<}uiMpOme#ul8 zjtu;i;|7t!A>({l^w0$faHZ@_Khn=DVY*!EhSF9uF8+FxJr=7OjjK7Bc@xl93Zg1e z%$E%w?$7>`DLQ71m!-Sv8}xO`(JTn!^peQM5q{a``e+x%X@nv9re1E^;!CwY z^miRU`v(u?^BB1m6OPQMCJ5|XttssPg3({pDV$UK+_`Ws9@e3B^l9?4@yEK%=Tb!s04kT9?If`OzKF!2^jpJOI@aBa{A@=ZL z6}+;9xTYaSl8)t4sR|&OH!t-drln^lO$;;q^#fU-a3rrvs;IE)OQU>II>?4j_@AliA2N?*SX49Qg}L zM>dTG4RHMb10G$^R>!|W?3$lZW5)Q4+V1ux6x4<3i~md%zP{JQ5nV9*pqz&u{9wu; zu4~7VFMrrl=*N~IL(=k1*A|CbE?hgOdXTPq>AHg7ifcQ1@Tq|s!2ii*=gd4X zzoZP`L@aw^a5xl(R2xyJ1UnMVtyIXam(=RVT-DF;g`Io1$}}t7=_HLS0zgBx zaT*s%AS=lk*_u=lKq;v(7jG>{pX!HK-Fy0@JDVRHMMR0L9}8VjHQjTBKJ@w>d_#pV zlHW$3oEVt}&3xXKnq|!V9CF5uujR{?6lklFtuS6mJYD~bXzF|Url^8+Rhgqd1oNwt zL}4r0RBA=FhKGt0i2if?EBge*s)I3J0V@i%s`?@WvBZx}oBb*PQonZ{pk-?Sv&R=` zosWOLfaEtdaKt=oU^0HV1xcQRcF^)`kuTrUjefmumz_h~c?1!o6jN=9NkO1`flL6Nc$z1mHn$NWug3aq`2%{QWrsM%v-9Q9GS$q+$dtI#lcF@5vly|=r}{)p~TDtulO&IpcI==fT^h5=Uix(WhDos zhliwcxNdXOz#_4f5^N=ch@(qn4ySNNV>|jZ7$3+$n-HTi7y?whL?N@VqrI(49cKqgTj891M*_d}XA+OK$ZQo(^lYLVvmk#Q3&sAan? zFB_>3{NqwFEOu2d)HTK6pE7X&6^qS3z#UVey9IpMLBm$tLK{d4Nv{!czVLmaDGW%`gR*;k9 z!@j6hEuNIBirC-bm2!^6AI=90%kS-^LKvsoyy!onW_|1faZAi<#~y!G9&zV3hlODK ztioE=-gd33hM!nisM{@ZT?Gx5nUcnmqUKb!BSb(SkFD;epYQ@%l%P?@#d;v^E!+@l zv(pEA>BMz;ya?M&B*)O|3)P|Xz)#<;eJ6=}L3rkEx1o16RD_P{iTy}zwz=M%nbOmfcqXZB+S|8WnD)2ZG>&kb&wO7d~o(;Qcd7bwOXQUs=T( zLHd2y$g4ZwFtgf7i&?k0EMW`IhRcrw{iZNHRm2;Dmgv3OOlajT1=2%AU)MCiHEs;? z{RT4Nc?cSXdp1Orpf>=rtJjW-(|@3*qD{`)Zea|x84_?|JIN40Voxl>f!ms~o@A`} zt7MkA`JsJhh8j!Wf4`j09_H$K><=&R|Lv{)mqScD>}_S?06(C$oyQzGp&XIQm>-d$ zvsHO$x5c>Jff7)#aB-}B%^X2IW4~SR#l_a$mt9ESla2A%bM%?}PG@zZebE;m#_1 zW~QI2%<;q0Y@!e}>>aegddy@@=M@NIuWbBQHlzE@5h|ZMH8|zfsCwu<69!jB}n-@lpVg2dQT^^auYeivB z|6VSB6W7qQxG1d`*xEsEN$^UtpN=18fHXBCvCIw#x92AX7n07U9y?5T-2AA_IA&RB zA8~=nA@Qs4;d;N`w+~!Fdy1(`_azLxuP8&cZ9rIU`ColJWmFW}*27Rk4KorVJq#sC zibzWgC4D6%rIGGtaDbs3q`MT5l9U=@5JZp?q-z8mO5iFIQjh!Iy7znEeQ&+}kNX~IO)O>II3h^c@NiR|y=$+T~?b_qp7XE6|KX?7R**tyR zq#1nu<`Uf*%N&fiy3VGGcS8P0)dma0f0jDBj$X&P-17Pz{&zZ60nyM6BILvP5f0yA z#60Q+7m|5bKn^8Znj%1NH&xa6#gz~e+*f=YngWIP3sB5%v+(jl*^_q^aI@=gQ$N33 zCaVO?+vQFoXQMtFg&F=-0dIO?{ouRCH+9R;koGg8C7`@AXmO&vON&mIXpRL3t8&mj z{T5}~1$%A4+z!$6q?S35?4wL6bYJL}R6E>7JYHT^k`Y+3#>-6~AKvnb4Z^t};SpFg zq9PIDtkt6&P%`i>ZS+I8d7(gp$MA1} z>Yt?Z_X+I>d0g*fsT}!jGiqb`D$@t_3qWP%q#im)@XUv0D5<2XRwk=*Sa{VLH}$6Ce4Re~5?ZQfLFIne>dsVjVmoe4@9~49 z)$(HDI+|PZQUINvO!%GjeQU^;1-Dm?q_HKG^~ya*ETLD7ouPk@5{?+nD|Owm#fMTzoj za6fw3dSZbXKo7(ln?P1qLyA8$XwgId*#*iyTeJXKM(NDZw{y&Z{A8xvirWS8Rp;J1dlnCI z#>W1m55gC}YLj@2WQhDmG}cxDxol6hEBBV!iLU-cGAMAm#*>}bHJ}Tf&3*~m9c3X9 zS8od}^wkhaKhOD4`DAcA{*1P^7_ZFmTNwU_M6yce0-eiU>_xRul5RKKSeE2G0{nJG z#2P)-nF`57B)vZl@I><*bFj-m^{Ll0sFhs(kk|@Fa6w!9Sluc`^{jTcLjvmo#V@E^ z$ohCt@IHgiIA?;a9fF2gV$WYPW$&2k>uYCbk8mkf?5bqjQb!X}`$w#+E7nFX0 z44#8J31)fBBd;3$gof0W0x7jh-q-W}&cdAjzWlHs z-cj!CUk;unjyH-|GBcsAkF%$6LBglYU%eVQ7Gv@=ofgLme%Go@O4HwC?N~W60trl{ zJ=18!QsaUnUxoI}WH3yMOg!1ZO!26*mC1IbGgsC0azTS$nYAF(mlh1$lBvzS3wAQf zQv=5IKY-mCIP|~X-1zLv!q58=84P>NdzAoe*`ODzIuCf2iKzVbV?MV&tgXcmPq&eE zJB3FGf1X`d_SH4y3?kNb6Po+o4O?&moa^@D(X(|+jH5a?S4PA zTlWw6-5z?6z!&-MxvrFKq3GcuCJ$_8F-ik=6xUA$DHr@_mtB-+=Pa*>K5Fn|dmr{4 zu^3`rbJ%9yYTbwYoR3Qh9cn;Equ(2%kuy{a{%Un?9GtQuE-NLax?$(bM}t~Vy6&cl zVTPQqcknL-wy+F+939pCmQnul%=L&RKd`W&Qz?F^)1hg9RTFKbN@sG#nujEf=Gatg zQ{;2Kdl29%7ObnVRF@tGVhk;hwBPfZ|ClQMm$VdTze&$J3}PaXQM=d3GA82Qk0X|v z*ZVyf!ZYx$bi;oj9*snQm-D}N+m(huui*^5DE*(R_}7xF`Ir{$+@T6r^p#o(#2sU4%@sIdTFoy0jLWLW9otek8Fe^oR0YbM~P+~)n@Bi2WDw@5ug z_9^V+=0%Aos&aCTjRVzb^?(W!C4nC(vT@Lgu2rtvGI1%6Uj%%em$Nl())D6$XkS>I5gy(*_-r; zYIq2kT;z|6cMvG^S1FuvJjL@tq~kh-5+e=yAn#gM>^_!@b`xGhDDJE19vMJfO}ftR z`OSjt<~yQ6NGU2*ke;Rlvt!x=&f^)QQJ`>XR1sPVVr4dqEC1vq2U+X}bD(7gY0m|? z6<@59zR@Bv9tROV;8nG3^)>vq>cjag=8WK_WBCsQHp@;1cM<5rT5NIo&J6o2K`~ut z4lGyi75Zl5*%!w*Gm`pks0Q4D?WWHiZlIvakU2Wwx&CNqRi#B1Ot9v~zyhC9bfHaK zNt?X59QsSLOR4p7`b1gy*tH?+qR*9cBXb2m^YZ#1=S%)P7fH@3-0Qyz4PsW>&ljfX z?&1o4MGnWiRYlJd|Cr7)nNOS;$+=R&=N>)_e2TPgs_1(wj^Cv}%J4Y^IUAjvu^8dO z+Xu>t#VWe+O6U~GiRfN9qryMC%@*ik0~T3Dr{=7H+A`G>m*F5>bq5Rf^S>|pgJy(+ z&RRN%s3^Anh;6%z&XyYJNHY)Al&&dL@WBLfFz^=`A<~AWFPqJ(cRGgy3~^IXZGcR^ zQTXj(ZFm*&YO0Z*wDRL%VyC8{ph+*^Ii!PC`T9@lRLvd%VigN?vU z5?j@rILNlR=+@ciru+IFd7E8s5^NPn zsVv(H(`ZfRtT>Z@1SLPB!00_cnvW^p?q8mXW#Gs-TtbHzsaVp&7F#ml2x5kqGIhBp zVu;o1Rnmll@ba!PQA<=?6{ws%9*{+*7D{J>i#lOK`9aAs-glEAXSv3$eX*9^ohW>( z%C!XR(oBZxGlyg<9FI7e|GR68pZIKZu{8P@}B_~{0zzs{hKJ8OHld9ULbtcH+ zXnTV}HHN^fAcc~VB%l6nO8;K4)1tLw`KwQTYBS{PTBYo z0V{NB5HR6rU+PKC%V{fZ7C@4NUdmJI#sVyrZD-d4Oi`;|RX#kT_TuF2G`NXr*L!55 z^%xxwFdl`;J=pg@9&r@M`jU3at$iYlUZBb?lIk6u76BIz!pD)WvVe_x{SsSCM!-r9 zE4xHpda%|5=T?v%-R5cG3*%!S-QS`;iR=P@4vwA{Zw^NQIC)-{FL$I;=}xu{nKh|7 z9K&olQkO1JnG*haczkA3DKlvyw60$U`km#wg0nA?v89ab`X>@i(67h=v!M}Mt0tpc zn?Y2HhV8=1;;CAf&P}n}thQZ3sY3A_~Dw~SRTFvQ8&6|u` zYd-=$%5aUZDlx=xn690WUbF&J)`uzAH$KDmoms72Pb0>^H@+r$`YaYEVzEEXB=${t ztlzIO-rj2L$C-s#pxXmU{9*pr6?{yVMALeR%nKQ8dvt_GHTT3 zj}hnhdGV@9Af69?ZmPHDg$!y?+nVg<}wEEq!QF)oiqcsPFU+vZ_|Gf;V|s0VM1@%0QxEn?yVcy`l>O9=Ez3|M95(LG zu4O7+Z>uQDNwa&HzPHi&)+M6az1eCt*JCiw5($%y-gN1177@3{QZ;LpY*Pc4}$Kchk25I;7kvRYBTT_bct9U)Axl@ITDrFZEp; zi>|lF@Q=c8&%fts5g$1d4#A6kHv~BJAGaTyU=K^yK`)sY%P_(xjX!vkp{= zt~3^9$jtE*rqq`2gN&h! z@7RTlCMnB8X;uga`7`KnEvhs<#=p~~IMqCwQ7_-7qv^Tzduk1+LJ0WuLlW|SjIGj$ z)|OPtHU$kzR&woHM>h%kPU&Y~cpeMZzad+x)*g%#Zh1Xvj3u@swgX3_)`Y%`h3vN8N~0uyOP!en*yWr1st|_M?(1KQfvtfOME{wPGPuB?ryQ?v~K E2hIXh$N&HU literal 18851 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 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)