Make block equations breakable (#4226)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
Eric Biedert 2024-05-30 09:52:48 +02:00 committed by GitHub
parent b75f0a82d4
commit 5f6d942519
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 16 deletions

View File

@ -10,8 +10,8 @@ use crate::foundations::{
}; };
use crate::introspection::{Count, Counter, CounterUpdate, Locatable}; use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
use crate::layout::{ use crate::layout::{
Abs, AlignElem, Alignment, Axes, Em, FixedAlignment, Frame, LayoutMultiple, Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment, Fragment, Frame,
LayoutSingle, OuterHAlignment, Point, Regions, Size, SpecificAlignment, VAlignment, LayoutMultiple, OuterHAlignment, Point, Regions, Size, SpecificAlignment, VAlignment,
}; };
use crate::math::{ use crate::math::{
scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant, scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant,
@ -51,7 +51,7 @@ use crate::World;
Locatable, Locatable,
Synthesize, Synthesize,
ShowSet, ShowSet,
LayoutSingle, LayoutMultiple,
LayoutMath, LayoutMath,
Count, Count,
LocalName, LocalName,
@ -174,6 +174,7 @@ impl ShowSet for Packed<EquationElem> {
let mut out = Styles::new(); let mut out = Styles::new();
if self.block(styles) { if self.block(styles) {
out.set(AlignElem::set_alignment(Alignment::CENTER)); out.set(AlignElem::set_alignment(Alignment::CENTER));
out.set(BlockElem::set_breakable(false));
out.set(EquationElem::set_size(MathSize::Display)); out.set(EquationElem::set_size(MathSize::Display));
} else { } else {
out.set(EquationElem::set_size(MathSize::Text)); out.set(EquationElem::set_size(MathSize::Text));
@ -248,26 +249,89 @@ impl Packed<EquationElem> {
} }
} }
impl LayoutSingle for Packed<EquationElem> { impl LayoutMultiple for Packed<EquationElem> {
#[typst_macros::time(name = "math.equation", span = self.span())] #[typst_macros::time(name = "math.equation", span = self.span())]
fn layout( fn layout(
&self, &self,
engine: &mut Engine, engine: &mut Engine,
styles: StyleChain, styles: StyleChain,
regions: Regions, regions: Regions,
) -> SourceResult<Frame> { ) -> SourceResult<Fragment> {
assert!(self.block(styles)); assert!(self.block(styles));
let span = self.span(); let span = self.span();
let font = find_math_font(engine, styles, span)?; let font = find_math_font(engine, styles, span)?;
let mut ctx = MathContext::new(engine, styles, regions, &font); let mut ctx = MathContext::new(engine, styles, regions, &font);
let equation_builder = ctx let full_equation_builder = ctx
.layout_into_run(self, styles)? .layout_into_run(self, styles)?
.multiline_frame_builder(&ctx, styles); .multiline_frame_builder(&ctx, styles);
let width = full_equation_builder.size.x;
let equation_builders = if BlockElem::breakable_in(styles) {
let mut rows = full_equation_builder.frames.into_iter().peekable();
let mut equation_builders = vec![];
let mut last_first_pos = Point::zero();
for region in regions.iter() {
// Keep track of the position of the first row in this region,
// so that the offset can be reverted later.
let Some(&(_, first_pos)) = rows.peek() else { break };
last_first_pos = first_pos;
let mut frames = vec![];
let mut height = Abs::zero();
while let Some((sub, pos)) = rows.peek() {
let mut pos = *pos;
pos.y -= first_pos.y;
// Finish this region if the line doesn't fit. Only do it if
// we placed at least one line _or_ we still have non-last
// regions. Crucially, we don't want to infinitely create
// new regions which are too small.
if !region.y.fits(sub.height() + pos.y)
&& (!frames.is_empty() || !regions.in_last())
{
break;
}
let (sub, _) = rows.next().unwrap();
height = height.max(pos.y + sub.height());
frames.push((sub, pos));
}
equation_builders
.push(MathRunFrameBuilder { frames, size: Size::new(width, height) });
}
// Append remaining rows to the equation builder of the last region.
if let Some(equation_builder) = equation_builders.last_mut() {
equation_builder.frames.extend(rows.map(|(frame, mut pos)| {
pos.y -= last_first_pos.y;
(frame, pos)
}));
let height = equation_builder
.frames
.iter()
.map(|(frame, pos)| frame.height() + pos.y)
.max()
.unwrap_or(equation_builder.size.y);
equation_builder.size.y = height;
}
equation_builders
} else {
vec![full_equation_builder]
};
let Some(numbering) = (**self).numbering(styles) else { let Some(numbering) = (**self).numbering(styles) else {
return Ok(equation_builder.build()); let frames = equation_builders
.into_iter()
.map(MathRunFrameBuilder::build)
.collect();
return Ok(Fragment::frames(frames));
}; };
let pod = Regions::one(regions.base(), Axes::splat(false)); let pod = Regions::one(regions.base(), Axes::splat(false));
@ -286,16 +350,22 @@ impl LayoutSingle for Packed<EquationElem> {
SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v), SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v),
}; };
let frame = add_equation_number( // Add equation numbers to each equation region.
equation_builder, let frames = equation_builders
number, .into_iter()
number_align.resolve(styles), .map(|builder| {
AlignElem::alignment_in(styles).resolve(styles).x, add_equation_number(
regions.size.x, builder,
full_number_width, number.clone(),
); number_align.resolve(styles),
AlignElem::alignment_in(styles).resolve(styles).x,
regions.size.x,
full_number_width,
)
})
.collect();
Ok(frame) Ok(Fragment::frames(frames))
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

View File

@ -107,6 +107,29 @@ Multiple trailing line breaks.
$ $\ $ $\
Nothing: $ $, just empty. Nothing: $ $, just empty.
--- math-pagebreaking ---
// Test breaking of equations at page boundaries.
#set page(height: 5em)
#show math.equation: set block(breakable: true)
$ a &+ b + & c \
a &+ b & && + d \
a &+ b + & c && + d \
& & c && + d \
&= 0 $
--- math-pagebreaking-numbered ---
// Test breaking of equations with numbering.
#set page(height: 5em)
#set math.equation(numbering: "1")
#show math.equation: set block(breakable: true)
$ a &+ b + & c \
a &+ b & && + d \
a &+ b + & c && + d \
& & c && + d \
&= 0 $
--- issue-1948-math-text-break --- --- issue-1948-math-text-break ---
// Test text with linebreaks in math. // Test text with linebreaks in math.
$ x := "a\nb\nc\nd\ne" $ $ x := "a\nb\nc\nd\ne" $