From 5f6d942519b36eb839a0a11e4ef3f1ea4013a8b5 Mon Sep 17 00:00:00 2001 From: Eric Biedert Date: Thu, 30 May 2024 09:52:48 +0200 Subject: [PATCH] Make block equations breakable (#4226) Co-authored-by: Laurenz --- crates/typst/src/math/equation.rs | 102 +++++++++++++++++++---- tests/ref/math-pagebreaking-numbered.png | Bin 0 -> 735 bytes tests/ref/math-pagebreaking.png | Bin 0 -> 705 bytes tests/suite/math/multiline.typ | 23 +++++ 4 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 tests/ref/math-pagebreaking-numbered.png create mode 100644 tests/ref/math-pagebreaking.png diff --git a/crates/typst/src/math/equation.rs b/crates/typst/src/math/equation.rs index d6a8d4c78..3dbb83d66 100644 --- a/crates/typst/src/math/equation.rs +++ b/crates/typst/src/math/equation.rs @@ -10,8 +10,8 @@ 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, SpecificAlignment, VAlignment, + Abs, AlignElem, Alignment, Axes, BlockElem, Em, FixedAlignment, Fragment, Frame, + LayoutMultiple, OuterHAlignment, Point, Regions, Size, SpecificAlignment, VAlignment, }; use crate::math::{ scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant, @@ -51,7 +51,7 @@ use crate::World; Locatable, Synthesize, ShowSet, - LayoutSingle, + LayoutMultiple, LayoutMath, Count, LocalName, @@ -174,6 +174,7 @@ impl ShowSet for Packed { let mut out = Styles::new(); if self.block(styles) { out.set(AlignElem::set_alignment(Alignment::CENTER)); + out.set(BlockElem::set_breakable(false)); out.set(EquationElem::set_size(MathSize::Display)); } else { out.set(EquationElem::set_size(MathSize::Text)); @@ -248,26 +249,89 @@ impl Packed { } } -impl LayoutSingle for Packed { +impl LayoutMultiple for Packed { #[typst_macros::time(name = "math.equation", span = self.span())] fn layout( &self, engine: &mut Engine, styles: StyleChain, regions: Regions, - ) -> SourceResult { + ) -> SourceResult { assert!(self.block(styles)); let span = self.span(); let font = find_math_font(engine, styles, span)?; let mut ctx = MathContext::new(engine, styles, regions, &font); - let equation_builder = ctx + let full_equation_builder = ctx .layout_into_run(self, 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 { - 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)); @@ -286,16 +350,22 @@ impl LayoutSingle for Packed { 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, - ); + // Add equation numbers to each equation region. + let frames = equation_builders + .into_iter() + .map(|builder| { + add_equation_number( + builder, + 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)) } } diff --git a/tests/ref/math-pagebreaking-numbered.png b/tests/ref/math-pagebreaking-numbered.png new file mode 100644 index 0000000000000000000000000000000000000000..8ddf637e4cfb6c008ac6b28de57944af389a6527 GIT binary patch literal 735 zcmV<50wDc~P)000003QKZ~0007}Nkl}xj`pxf&0N}}q1BYl3{*l*E5a~l zt=vpevRdfYhBNk{d-%k*2Ip8Vw%OShCdgFa%L59*6iKERq*k*on5JhONX!n zK;WWOy-SkDfyF{xx&wTaq&=slS|0$6p}|3QASef%#O!9eVAd zAvp}`x*kf{dttc6Ap*y5r4P!6ej=e3m9ScQ+BT&J1~Lh6czCm43~sN(KYMWms|(z^ z+3HLR4vZqwU@g|@C1Ne?gZKwzCJ4os>+FCXe@{ZR;KU=XFzh}&RxquAb9%3s`P&gj<;%!Tgq6J~N z>RZDxogDU^d36eL;YzEfvQrqw8@DlpaNZU~vKz5R*AQ*gYYg#sHDSVp2@@tvcpbv& zihpCmgb8yR_}$1BRi}YA#F{*+#x$^O=vO|z%yiT|R$&_O71=Al$l*^V$eLGS8fd6S zNb&e`;5G8pmR4qFgJ~~2ITO4{f*M+s!jt>I1TWHp3{Ip0@Hv% zg42L_Hk-gSAkew)vhPVu1Hu`~F%?W=8W67HslYTKQeV5X-jT#KAl{sb(*R+@gb5QS zOqkOErvbu*2@}3&=ttuDdrJi22fa99O#*gg3c@93X!NQ*um!GO4)-3w&S^9>ssk(A zI|)rkIxemoK!qtC7yBA}G)TNa>-HumKOlq&6DCZUFk!-k2@@tvnDDxQe*sGQUnNbb RSl<8u002ovPDHLkV1n4HQIY@v literal 0 HcmV?d00001 diff --git a/tests/ref/math-pagebreaking.png b/tests/ref/math-pagebreaking.png new file mode 100644 index 0000000000000000000000000000000000000000..c651860fe7e287e3a7235aa62700dd3d8648a882 GIT binary patch literal 705 zcmV;y0zUnTP)000003QKZ~0007rNklt5mY{Rck$7~F&BDnp4XBTSeuVTrK5K{6aSAd^Xt zY{Q|y3V$%Tzaw3^2br1;c4WHYN3KWKNU`B}4XH}61}^vZk3kC`eql5f8*a2=BY?zN zrG2xaOaou*aPB7XT2ZzhQ#xY+FjWJf*l<w=x24wr_m72on~A2hQM+J>FkLI5CM#W1U!~C&;z54dSpT3C+uO zVl;r?ryyH$=B{2C4)2?)o!7vX4=%X!!lB(S*Sm$`jytGHs{fs0wdn1`uZ=27wW9O_ zXtSUO!f?gsTRLhtKTJ4C0)E8H*%r!YBOarbf z6W0=I_?G!<^K)56K z1*QR!Mh3q01Pho3#Pc~h4G<6`#I%|oY?2E+F@T^PW27tR~yU2Ha`^8mO$jDub|u(s5ob%+lLVZww7 n6DCZUFk!-k2@@v#U&B8D!O3#>k)1~M00000NkvXXu0mjf8I3-m literal 0 HcmV?d00001 diff --git a/tests/suite/math/multiline.typ b/tests/suite/math/multiline.typ index edf974a10..35f10ea41 100644 --- a/tests/suite/math/multiline.typ +++ b/tests/suite/math/multiline.typ @@ -107,6 +107,29 @@ Multiple trailing line breaks. $ $\ 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 --- // Test text with linebreaks in math. $ x := "a\nb\nc\nd\ne" $