summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst/src/layout/align.rs71
-rw-r--r--crates/typst/src/layout/flow.rs4
-rw-r--r--crates/typst/src/layout/frame.rs18
-rw-r--r--crates/typst/src/math/align.rs4
-rw-r--r--crates/typst/src/math/ctx.rs31
-rw-r--r--crates/typst/src/math/equation.rs151
-rw-r--r--crates/typst/src/math/matrix.rs4
-rw-r--r--crates/typst/src/math/row.rs47
-rw-r--r--crates/typst/src/math/underover.rs10
-rw-r--r--tests/ref/math/equation-number.pngbin18851 -> 23214 bytes
-rw-r--r--tests/typ/math/equation-number.typ49
11 files changed, 269 insertions, 120 deletions
diff --git a/crates/typst/src/layout/align.rs b/crates/typst/src/layout/align.rs
index 5f516ebe..3e462d33 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<FixedAlignment> {
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<Side> 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<HAlignment> for VAlignment {
}
}
+impl Resolve for VAlignment {
+ type Output = FixedAlignment;
+
+ fn resolve(self, _: StyleChain) -> Self::Output {
+ self.fix(Dir::TTB)
+ }
+}
+
impl From<VAlignment> 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<H, V> {
impl<H, V> SpecificAlignment<H, V>
where
- H: Copy,
- V: Copy,
+ H: Default + Copy + FixAlignment,
+ V: Default + Copy + FixAlignment,
{
/// The horizontal component.
pub const fn x(self) -> Option<H> {
@@ -544,6 +560,26 @@ where
Self::H(_) => None,
}
}
+
+ /// Normalize the alignment to a LTR-TTB space.
+ pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
+ Axes::new(
+ self.x().unwrap_or_default().fix(text_dir),
+ self.y().unwrap_or_default().fix(text_dir),
+ )
+ }
+}
+
+impl<H, V> Resolve for SpecificAlignment<H, V>
+where
+ H: Default + Copy + FixAlignment,
+ V: Default + Copy + FixAlignment,
+{
+ type Output = Axes<FixedAlignment>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ self.fix(TextElem::dir_in(styles))
+ }
}
impl<H, V> From<SpecificAlignment<H, V>> 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<Side> for FixedAlignment {
diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs
index d35b39cc..003c9db7 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 3a21078e..09e36f8f 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<FixedAlignment>) {
- 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<FixedAlignment>) -> 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 0674f49e..467f7ac9 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::<Abs>::new();
let mut pending_width = Abs::zero();
diff --git a/crates/typst/src/math/ctx.rs b/crates/typst/src/math/ctx.rs
index 9acf0735..70de8a8e 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<MathRow> {
- let row = self.layout_into_fragments(elem, styles)?;
- Ok(MathRow::new(row))
+ ) -> SourceResult<MathRun> {
+ 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<MathFragment> {
- 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<BoxElem>,
@@ -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<TextElem>,
@@ -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 a272622e..ff34f40e 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<OuterHAlignment, VAlignment>,
/// A supplement for the equation.
///
@@ -210,12 +214,12 @@ impl Packed<EquationElem> {
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<EquationElem> {
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)?;
-
- 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();
-
- static NUMBER_GUTTER: Em = Em::new(0.5);
- let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
-
- 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,
- );
- }
+ let equation_builder = ctx
+ .layout_into_run(self, styles)?
+ .multiline_frame_builder(&ctx, styles);
+
+ let Some(numbering) = (**self).numbering(styles) else {
+ return Ok(equation_builder.build());
+ };
+
+ 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();
+
+ 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<FixedAlignment>,
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::<FixedAlignment>::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 b4d34a91..83d50dd6 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 e49c9142..afed8231 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<MathFragment>);
+pub struct MathRun(Vec<MathFragment>);
-impl MathRow {
+impl MathRun {
+ /// Takes the given [`MathFragment`]s and do some basic processing.
pub fn new(fragments: Vec<MathFragment>) -> Self {
let iter = fragments.into_iter().peekable();
let mut last: Option<usize> = 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> {
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<T: Into<MathFragment>> From<T> for MathRow {
+impl<T: Into<MathFragment>> From<T> 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 1e5fe861..4117342b 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<MathRow>,
+ rows: Vec<MathRun>,
align: FixedAlignment,
gap: Abs,
baseline: usize,
diff --git a/tests/ref/math/equation-number.png b/tests/ref/math/equation-number.png
index 96a1f21a..8ba91590 100644
--- a/tests/ref/math/equation-number.png
+++ b/tests/ref/math/equation-number.png
Binary files differ
diff --git a/tests/typ/math/equation-number.typ b/tests/typ/math/equation-number.typ
index aefa308a..3de611b3 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)