show math.equation: set align(..) shall not break alignment points (#4094)

This commit is contained in:
Leedehai 2024-05-15 03:48:35 -04:00 committed by Laurenz
parent 1b405832a3
commit f535d90b7e
5 changed files with 60 additions and 37 deletions

View File

@ -10,7 +10,8 @@ use crate::layout::{
};
use crate::math::{
alignments, scaled_font_size, stack, style_for_denominator, AlignmentResult,
FrameFragment, GlyphFragment, LayoutMath, MathContext, Scaled, DELIM_SHORT_FALL,
FrameFragment, GlyphFragment, LayoutMath, LeftRightAlternator, MathContext, Scaled,
DELIM_SHORT_FALL,
};
use crate::syntax::{Span, Spanned};
use crate::text::TextElem;
@ -67,6 +68,7 @@ impl LayoutMath for Packed<VecElem> {
self.children(),
FixedAlignment::Center,
self.gap(styles),
LeftRightAlternator::Right,
)?;
layout_delimiters(
@ -324,6 +326,7 @@ impl LayoutMath for Packed<CasesElem> {
self.children(),
FixedAlignment::Start,
self.gap(styles),
LeftRightAlternator::None,
)?;
let (open, close) = if self.reverse(styles) {
@ -387,6 +390,7 @@ fn layout_vec_body(
column: &[Content],
align: FixedAlignment,
row_gap: Rel<Abs>,
alternator: LeftRightAlternator,
) -> SourceResult<Frame> {
let gap = row_gap.relative_to(ctx.regions.base().y);
@ -396,7 +400,7 @@ fn layout_vec_body(
flat.push(ctx.layout_into_run(child, styles.chain(&denom_style))?);
}
Ok(stack(flat, align, gap, 0))
Ok(stack(flat, align, gap, 0, alternator))
}
/// Layout the inner contents of a matrix.
@ -480,7 +484,7 @@ fn layout_mat_body(
let mut y = Abs::zero();
for (cell, &(ascent, descent)) in col.into_iter().zip(&heights) {
let cell = cell.into_line_frame(&points, FixedAlignment::Center);
let cell = cell.into_line_frame(&points, LeftRightAlternator::Right);
let pos = Point::new(
if points.is_empty() { x + (rcol - cell.width()) / 2.0 } else { x },
y + ascent - cell.ascent(),

View File

@ -3,7 +3,7 @@ use std::iter::once;
use unicode_math_class::MathClass;
use crate::foundations::{Resolve, StyleChain};
use crate::layout::{Abs, AlignElem, Em, FixedAlignment, Frame, FrameKind, Point, Size};
use crate::layout::{Abs, AlignElem, Em, Frame, FrameKind, Point, Size};
use crate::math::{
alignments, scaled_font_size, spacing, EquationElem, FrameFragment, MathContext,
MathFragment, MathParItem, MathSize,
@ -140,7 +140,7 @@ impl MathRun {
pub fn into_frame(self, ctx: &MathContext, styles: StyleChain) -> Frame {
if !self.is_multiline() {
self.into_line_frame(&[], AlignElem::alignment_in(styles).resolve(styles).x)
self.into_line_frame(&[], LeftRightAlternator::Right)
} else {
self.multiline_frame_builder(ctx, styles).build()
}
@ -181,7 +181,7 @@ impl MathRun {
continue;
}
let sub = row.into_line_frame(&alignments.points, align);
let sub = row.into_line_frame(&alignments.points, LeftRightAlternator::Right);
if i > 0 {
size.y += leading;
}
@ -200,43 +200,37 @@ impl MathRun {
/// 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 {
pub fn into_line_frame(
self,
points: &[Abs],
mut alternator: LeftRightAlternator,
) -> Frame {
let ascent = self.ascent();
let mut frame = Frame::soft(Size::new(Abs::zero(), ascent + self.descent()));
frame.set_baseline(ascent);
let mut next_x = {
let mut widths = Vec::new();
if !points.is_empty() && align != FixedAlignment::Start {
let mut width = Abs::zero();
for fragment in self.iter() {
if matches!(fragment, MathFragment::Align) {
widths.push(width);
width = Abs::zero();
} else {
width += fragment.width();
}
}
widths.push(width);
}
let widths = widths;
let widths: Vec<Abs> = if points.is_empty() {
vec![]
} else {
self.iter()
.as_slice()
.split(|e| matches!(e, MathFragment::Align))
.map(|chunk| chunk.iter().map(|e| e.width()).sum())
.collect()
};
let mut prev_points = once(Abs::zero()).chain(points.iter().copied());
let mut point_widths = points.iter().copied().zip(widths);
let mut alternator = LeftRightAlternator::Right;
move || match align {
FixedAlignment::Start => prev_points.next(),
FixedAlignment::End => {
point_widths.next().map(|(point, width)| point - width)
}
_ => point_widths
move || {
point_widths
.next()
.zip(prev_points.next())
.zip(alternator.next())
.map(|(((point, width), prev_point), alternator)| match alternator {
LeftRightAlternator::Left => prev_point,
LeftRightAlternator::Right => point - width,
}),
_ => prev_point,
})
}
};
let mut x = next_x().unwrap_or_default();
@ -352,8 +346,11 @@ impl<T: Into<MathFragment>> From<T> for MathRun {
}
}
/// An iterator that alternates between the `Left` and `Right` values, if the
/// initial value is not `None`.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum LeftRightAlternator {
pub enum LeftRightAlternator {
None,
Left,
Right,
}
@ -364,6 +361,7 @@ impl Iterator for LeftRightAlternator {
fn next(&mut self) -> Option<Self::Item> {
let r = Some(*self);
match self {
Self::None => {}
Self::Left => *self = Self::Right,
Self::Right => *self = Self::Left,
}

View File

@ -3,7 +3,8 @@ 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, MathRun, Scaled,
FrameFragment, GlyphFragment, LayoutMath, LeftRightAlternator, MathContext, MathRun,
Scaled,
};
use crate::syntax::Span;
use crate::text::TextElem;
@ -290,7 +291,8 @@ fn layout_underoverspreader(
baseline = rows.len() - 1;
}
let frame = stack(rows, FixedAlignment::Center, gap, baseline);
let frame =
stack(rows, FixedAlignment::Center, gap, baseline, LeftRightAlternator::Right);
ctx.push(FrameFragment::new(ctx, styles, frame).with_class(body_class));
Ok(())
@ -298,28 +300,30 @@ fn layout_underoverspreader(
/// Stack rows on top of each other.
///
/// Add a `gap` between each row and uses the baseline of the `baseline`th
/// row for the whole frame.
/// Add a `gap` between each row and uses the baseline of the `baseline`-th
/// row for the whole frame. `alternator` controls the left/right alternating
/// alignment behavior of `AlignPointElem` in the rows.
pub(super) fn stack(
rows: Vec<MathRun>,
align: FixedAlignment,
gap: Abs,
baseline: usize,
alternator: LeftRightAlternator,
) -> Frame {
let rows: Vec<_> = rows.into_iter().flat_map(|r| r.rows()).collect();
let AlignmentResult { points, width } = alignments(&rows);
let rows: Vec<_> = rows
.into_iter()
.map(|row| row.into_line_frame(&points, align))
.map(|row| row.into_line_frame(&points, alternator))
.collect();
let mut y = Abs::zero();
let mut frame = Frame::soft(Size::new(
width,
rows.iter().map(|row| row.height()).sum::<Abs>()
+ rows.len().saturating_sub(1) as f64 * gap,
));
let mut y = Abs::zero();
for (i, row) in rows.into_iter().enumerate() {
let x = align.position(width - row.width());
let pos = Point::new(x, y);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -32,3 +32,20 @@ $
a &=b & quad c&=d \
e &=f & g&=h
$
--- issue-3973-math-equation-align ---
// In this bug, the alignment set with "show math.equation: set align(...)"
// overrides the left-right alternating behavior of alignment points.
#let equations = [
$ a + b &= c \
e &= f + g + h $
$ a &= b + c \
e + f + g &= h $
]
#equations
#show math.equation: set align(start)
#equations
#show math.equation: set align(end)
#equations