From feb0c913954d385a3f906f9d9d8ec33cbcf2b2d0 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 16 Aug 2024 11:18:45 +0200 Subject: [PATCH] Move paragraph widow and orphan prevention into flow (#4767) --- crates/typst/src/layout/flow.rs | 44 +++++++++++++++++- crates/typst/src/layout/inline/collect.rs | 6 +-- crates/typst/src/layout/inline/finalize.rs | 36 ++------------ crates/typst/src/layout/inline/prepare.rs | 3 -- tests/ref/flow-widow-forced.png | Bin 0 -> 1453 bytes .../grid-header-and-footer-lack-of-space.png | Bin 3768 -> 3985 bytes tests/ref/grid-header-lack-of-space.png | Bin 3193 -> 2785 bytes tests/ref/grid-rowspan-split-9.png | Bin 967 -> 965 bytes ...sue-1445-widow-orphan-unnecessary-skip.png | Bin 0 -> 3549 bytes .../issue-multiple-footnote-in-one-line.png | Bin 713 -> 704 bytes tests/suite/layout/flow/orphan.typ | 11 +++++ tests/suite/layout/grid/headers.typ | 2 +- tests/suite/model/footnote.typ | 7 ++- 13 files changed, 63 insertions(+), 46 deletions(-) create mode 100644 tests/ref/flow-widow-forced.png create mode 100644 tests/ref/issue-1445-widow-orphan-unnecessary-skip.png diff --git a/crates/typst/src/layout/flow.rs b/crates/typst/src/layout/flow.rs index a1f1402e4..e15159e11 100644 --- a/crates/typst/src/layout/flow.rs +++ b/crates/typst/src/layout/flow.rs @@ -14,10 +14,12 @@ use crate::foundations::{ use crate::introspection::{Locator, SplitLocator, Tag, TagElem}; use crate::layout::{ Abs, AlignElem, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem, Fr, - Fragment, Frame, FrameItem, PlaceElem, Point, Regions, Rel, Size, Spacing, VElem, + Fragment, Frame, FrameItem, PlaceElem, Point, Ratio, Regions, Rel, Size, Spacing, + VElem, }; use crate::model::{FootnoteElem, FootnoteEntry, ParElem}; use crate::realize::StyleVec; +use crate::text::TextElem; use crate::utils::Numeric; /// Arranges spacing, paragraphs and block-level elements into a flow. @@ -278,6 +280,7 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { // Fetch properties. let align = AlignElem::alignment_in(styles).resolve(styles); let leading = ParElem::leading_in(styles); + let costs = TextElem::costs_in(styles); // Layout the paragraph into lines. This only depends on the base size, // not on the Y position. @@ -305,12 +308,51 @@ impl<'a, 'e> FlowLayouter<'a, 'e> { } } + // Determine whether to prevent widow and orphans. + let len = lines.len(); + let prevent_orphans = + costs.orphan() > Ratio::zero() && len >= 2 && !lines[1].is_empty(); + let prevent_widows = + costs.widow() > Ratio::zero() && len >= 2 && !lines[len - 2].is_empty(); + let prevent_all = len == 3 && prevent_orphans && prevent_widows; + + // Store the heights of lines at the edges because we'll potentially + // need these later when `lines` is already moved. + let height_at = |i| lines.get(i).map(Frame::height).unwrap_or_default(); + let front_1 = height_at(0); + let front_2 = height_at(1); + let back_2 = height_at(len.saturating_sub(2)); + let back_1 = height_at(len.saturating_sub(1)); + // Layout the lines. for (i, mut frame) in lines.into_iter().enumerate() { if i > 0 { self.handle_item(FlowItem::Absolute(leading, true))?; } + // To prevent widows and orphans, we require enough space for + // - all lines if it's just three + // - the first two lines if we're at the first line + // - the last two lines if we're at the second to last line + let needed = if prevent_all && i == 0 { + front_1 + leading + front_2 + leading + back_1 + } else if prevent_orphans && i == 0 { + front_1 + leading + front_2 + } else if prevent_widows && i >= 2 && i + 2 == len { + back_2 + leading + back_1 + } else { + frame.height() + }; + + // If the line(s) don't fit into this region, but they do fit into + // the next, then advance. + if !self.regions.in_last() + && !self.regions.size.y.fits(needed) + && self.regions.iter().nth(1).is_some_and(|region| region.y.fits(needed)) + { + self.finish_region(false)?; + } + self.drain_tag(&mut frame); self.handle_item(FlowItem::Frame { frame, diff --git a/crates/typst/src/layout/inline/collect.rs b/crates/typst/src/layout/inline/collect.rs index b6a847f57..5021dc555 100644 --- a/crates/typst/src/layout/inline/collect.rs +++ b/crates/typst/src/layout/inline/collect.rs @@ -128,11 +128,11 @@ pub fn collect<'a>( let mut iter = children.chain(styles).peekable(); let mut locator = locator.split(); + let outer_dir = TextElem::dir_in(*styles); let first_line_indent = ParElem::first_line_indent_in(*styles); if !first_line_indent.is_zero() && consecutive - && AlignElem::alignment_in(*styles).resolve(*styles).x - == TextElem::dir_in(*styles).start().into() + && AlignElem::alignment_in(*styles).resolve(*styles).x == outer_dir.start().into() { collector.push_item(Item::Absolute(first_line_indent.resolve(*styles), false)); collector.spans.push(1, Span::detached()); @@ -144,8 +144,6 @@ pub fn collect<'a>( collector.spans.push(1, Span::detached()); } - let outer_dir = TextElem::dir_in(*styles); - while let Some((child, styles)) = iter.next() { let prev_len = collector.full.len(); diff --git a/crates/typst/src/layout/inline/finalize.rs b/crates/typst/src/layout/inline/finalize.rs index c8ba47292..03493af5a 100644 --- a/crates/typst/src/layout/inline/finalize.rs +++ b/crates/typst/src/layout/inline/finalize.rs @@ -1,5 +1,4 @@ use super::*; -use crate::layout::{Abs, Frame, Point}; use crate::utils::Numeric; /// Turns the selected lines into frames. @@ -26,38 +25,9 @@ pub fn finalize( // Stack the lines into one frame per region. let shrink = ParElem::shrink_in(styles); - let mut frames: Vec = lines + lines .iter() .map(|line| commit(engine, p, line, width, region.y, shrink)) - .collect::>()?; - - // Positive ratios enable prevention, while zero and negative ratios disable - // it. - if p.costs.orphan().get() > 0.0 { - // Prevent orphans. - if frames.len() >= 2 && !frames[1].is_empty() { - let second = frames.remove(1); - let first = &mut frames[0]; - merge(first, second, p.leading); - } - } - if p.costs.widow().get() > 0.0 { - // Prevent widows. - let len = frames.len(); - if len >= 2 && !frames[len - 2].is_empty() { - let second = frames.pop().unwrap(); - let first = frames.last_mut().unwrap(); - merge(first, second, p.leading); - } - } - - Ok(Fragment::frames(frames)) -} - -/// Merge two line frames -fn merge(first: &mut Frame, second: Frame, leading: Abs) { - let offset = first.height() + leading; - let total = offset + second.height(); - first.push_frame(Point::with_y(offset), second); - first.size_mut().y = total; + .collect::>() + .map(Fragment::frames) } diff --git a/crates/typst/src/layout/inline/prepare.rs b/crates/typst/src/layout/inline/prepare.rs index 59682b2c8..9e73af660 100644 --- a/crates/typst/src/layout/inline/prepare.rs +++ b/crates/typst/src/layout/inline/prepare.rs @@ -43,8 +43,6 @@ pub struct Preparation<'a> { pub cjk_latin_spacing: bool, /// Whether font fallback is enabled for this paragraph. pub fallback: bool, - /// The leading of the paragraph. - pub leading: Abs, /// How to determine line breaks. pub linebreaks: Smart, /// The text size. @@ -136,7 +134,6 @@ pub fn prepare<'a>( hang: ParElem::hanging_indent_in(styles), cjk_latin_spacing, fallback: TextElem::fallback_in(styles), - leading: ParElem::leading_in(styles), linebreaks: ParElem::linebreaks_in(styles), size: TextElem::size_in(styles), }) diff --git a/tests/ref/flow-widow-forced.png b/tests/ref/flow-widow-forced.png new file mode 100644 index 0000000000000000000000000000000000000000..98a953afe56ea2f1085b9461752cffe6431b3224 GIT binary patch literal 1453 zcmV;e1ycHnP)7*$g1ElEHm?zq~(TKMkG_kS+laeSu630S}a7BB!s zj{)e;==?A6nMoJ;*Q)k1LP}o%-p#FKSicU?Y6<2WMyQ_=Dv`81)H-6Wn4gpG?S)I?^H$K@< z*>jt@z0ctLn0~ge5Ys9U7+fp5Cbc zQLlHv@tN9{D@rBsdp%R^ZU=>| z+VSA25@MtTT7#tUv;_aU;KNluSAo=BHv(QiJr>>a(l3@|WrA7lnGU=OjVpfQeYtPP z^-ny<4t1YrK(w>>iOCf3n85}tW*B;oC0yZ(#cYTE5}yF~Uy7-D@0k@6l_4vnIw{-` zV|N*V-iVk7oR?zB+MVHDNM6c8o}W3>OUel(GK#NsnoZchE$Cld=AER9BLW)?OUt*V=I)|y z6{jOuapBdd;qG{qJ)s%c(7dwVT2tII1kY*#c@j^Yutn!U&P=Ido~F3XIR~(zWPzr* zeGqP1P0C3e8yGdb|F&)^f!CQ~Y*^YezLoYeXLGj>*eFqd!-L!5Os94km-=UQPUxil zpda;E)AD5(?e~1m@8)xSKP#$64;Kcdxjps{D|gB~=Te*bdt67~<`nG~pxISkMU*mf zK)s5V68RR&jCPuHbaF}GQ6H;xom(o zhQmH80GcL`4#pOf2>=F0z8Ub(r;qFah8LR%0Ja|q>ysnPsgsAyW@pmF4Of24Fl@br zQkjG#g|%%c&T+fE^$sPe!TTzJq_ff>T(pP~`*Z+GRNIXW3GxP#qZTFt?FWud_Z+;~^;54VSH@Yh`$N9&(5}bcct#0alx0uq_s?erS$hCr zd!B|xnTK{f4k9`caB5>V+cc~yw+OdICEA-NgDoj1u>rf*qnD7Z^N*YaydPmp^D2U} z0xJMN#j8dJVH<|v#^r8|*EPP4jQIOiTMb~#oOc9Yum}gKUoOF_y5`%Sgu~3lu$<7d z5475i>IJ=3X16@1Qn#4E8UsMWdYa5$cbQ_&_0{WY0SSk?AMVq5d%4+94lK^>_@cx6 z3LZ5j-4@_dx!PY_>~k&P{{EnM(^WCLjz2=RhZL2Z^3Q4vIG*WMe8IQssQZ>pCNM_? zXE*ISTHuw@wL@Jd0~C^qYM5jJtroC=1uS3z3s}Gc7O;Q?EMNiu-{8LhkGC*q!%ZAo00000NkvXX Hu0mjf1hc}b literal 0 HcmV?d00001 diff --git a/tests/ref/grid-header-and-footer-lack-of-space.png b/tests/ref/grid-header-and-footer-lack-of-space.png index 78705776d59660b30c6dd12e15051299b3f153aa..303c6f31f0eff679188c2b05f3e89a16c4340cd7 100644 GIT binary patch delta 3329 zcmY+HbySq=7KevWUAt`l%bIx7&{?@zJxA*?u{p|SfZDNdLjs@Kzpdtob37>%v4*<9Wt7gx_N26o8 zvH{UQ6wr{^O@UPEL{*6GBe)?OB#8BazrQa1y?2kPxE>IGmmNgLRj_ia*HM&|^RY(S zsYKX*Wl@NFN7yM(R-NKV9z6%m838+`c%%{{Ci3X9w== z?j}6`yD&Fp=MNCbBp%fgJr9Gy?j0z#X*rF7Z$3tNdL1;VTh|`?Dk_jtb)_v!=r@gq zglki<0>iTaY{JJzdF9O&HKwRt0jAt*-Xb}tN&c5p_w3@bfbRqc{ZVA0MNpIi_O#jsOZVtR^&Vh4!;Hvv)|Bin>_fX(<&xSDc z|8K_ZW;Or4X?Crf}rtwdWUBvGy+a?#y=i!<- z#z_QSP~q+079m7Gzn4n><*DVcyc45-ZYJDnKgXv;F>gdGUo<7l%mn@VhmPIE8ZTOE zdV1}zwkWN3=hMMr%4jCwO3y33+~C59^e#_aE2`?Z!)jmZqTsB1o&y)zSXLIomj2hx z)PL{v!F%e(3gbThv8V4?8D4O91sfvb(rLXLeCWAlWn9}G`JFeqD?Jx$kAQb`*e^AI zPOeEjo|ZIM{Zy)7J3q+*Mn8URVmfbGwlh<5_^cKhsPk?6uld6*2-n~#=Ere_M&W4M z@P6uQdV>%m-hjt0lGu}7D9mQ;0hdVblE2>Ay$recY@QKpi=t0NSkwSCcu{eRgw)jf zmSOyg6nOemK`XFi3y&>%j+kRJLOUzs^i>7g;CoJy%aYouN(7qAz**)lP~Q zj>ofbcRoMrv6q|b3ROO5Bn?Bv6w3L~hG|>LKs>RPnST8zJ^tQZEhW7nZ)*4v*{q#^ z@cwhYjv0FO-FWJ4`NfTpgXDs_t5~|1+9&o;H1C=NAbPPfoCqx6zrWMf0@z_!2i*cy z@!;IRJ(zv_;Mfo4V#$4^z9!-9elu?JQyj_-7-WqrBm$c1hY1EWkC)B9^x;t(bY>r+1sUhM_9^|F%gy{F&DAw(} z*+>$mmt_x&xf#db|6sTqR1!qUxfe`Tp??49M~lir@6uo_94IR6F1NFhrM&&p`lPyz z*0G%$GbphaII%(6Q#sV2i41AGkCnntINy!;b~j0<6=LJ{OkAB!szp#WabEw%(;|$00a|L{!1KIMkaxmWU&t9za1( zJ+YcGt-C}N@>1@IoGQvdFS_JzU4ETp5)4m}J2JFKeX1ft44P9mg?{8qH%0ALdb;sq zYy~2b1Su)1$f>D+XS&mPVKdC3zHLJH9k61MoaWQQ`eCx7^Ycb!^qj_rKv&&U{A}#XIt-K< zE>i(^-W%QypEN!yA@vhDDRD+VtF!vmmxRYOFo}}H!f5D^Ef<&ad;K~KYLw`)l0Ebo zrJMn6QyX1-DAoW`7Bb;3uh5~(+O=iff_3w_6i#%*yda%$;dvoqOP^g#TA~=GUXHua z0(QfAo)@4AFFjDb^pg94&og4nrHy;%>upK8^90qu{m3ycxDL2c|Aw?u4gUJ4IB!bu z-f?2D)3G0$1s)uUUc`a2%O|2Ltg#Fp9iM~SW&&Uqg#+tFg%q|TLts2*#^Y5j_3t5G z09#v(D8Vz|9dS9mdeT(LIp(?lqOhe|^)N4S3;F{2vEgOc*nd zO>vZi_l`4!1iwkHB9E#c-tcTRcwAhPdu8fbo2Y9M$No z&dO=n&b$^=_M3#XUSEDA>bfwisjlB<7C%(T+*Zxd*-(`I8SfzRB}G;%Mx1V5bqp@N*jWGFi@DX7 z0eV7RtBvd;>bgM?pWGw0;wb2=WIRwmDch0iB(j^*f4d;}ywAR3W<!i^|wUn^?c9 z#~Q_sKeWgiWi0ZRKJ9(75`?4qvPugH;-A%V9OGj*x2a8$uy#CRF>}nv9_+d8n}Ycz zgf&I%mw`z}zFLl(l6jZ{$m0fr{?FP8U#A|9lqIE557z5wf2i&}7}NN=au8D^#~~t} zxvE_rlic#VkkD`=f~JZPy=rj_+bHIIndm?TC~0)305!b&k}}(fdA-Z)2H2j-5g@1W zJxb@s2w4L-eiD)14n(Qg|y9^ElwTFDxT z>?ap`19cN@@D3g!8(j;Vo4Dv|irwr>{-^`hs(us)dJ4nmKL!b#8rKBucp9}T(8~Ye zJ(A9GHx?+33c@XWS+8$tLX|pEYW=MI%|iwASh3B)jmMi$?|diqc}Fa%;E7++WcuUW zvhAZayUhfc8jbPS_X&+4IpEC=(wORI(Gad7xMwFF%R*$WcQ2UFQI@<84ApXKn(DZ mmk9HO4$!oV5jVI^b_phsPKTd`7VKOhUKpk%( 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 EUrf>H`Tzg` literal 3193 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 delta 713 zcmX@gew=-RPW^`uAKt%z|Ki1q=g*)2pX>R4vg?Ei6B-&C>g(%kYHF&htN%Ni{jxXt zYG?e}*7&`x@f#b%r&b2{EcEYK=-o8eyF2KSKpzhUg4!&?y0<9M|F+1%2F-mg_=t9G!#8OJ!jXeD|mQ#OjlEwtSUcA zRo=zLrBz9$QBk@jDQLSCv|UaC}9B2Pv`NtVEHvhq~YHX@9$C9VX)X z;h+A=lU#2M+3F_NKbJMCoHY5(jK{a;r%d|tyRBRK=epmUUXQsH>&gNKbw4D_G|C+o)2Dbo?ImKqpbX$F6W*|tBSAAOgwD$%?EAy#kI?S zzWN&(a`$#u*59`c?dEQuW5qYUsM+&#=6z?cU);6or}pnz^1t#*O-{|Vhl`*2d^R@N zar=QqmRiH(6*C$77N;$L2;saeWNJRRR?bxTK;ac1hU0<8j56+L`3@Ie`2w;GXcxow zsw*`aHP2RPo(#Mk7(8jR29ugubXUTLrOTEsJI=|&7M&FZgiQw%3?y<^YI^p~nj~YY zYr0pCbK8|IK=?(b`QWr!ASgGvg~`Z!?@|t^gheAr56F@PZ(na;&!Q_+o~+1R$;sBd zxBB(|m6KFFfvD(8$(1Q7D_`om8r%rF41~fwY`^_{@2`AmZ94fslRIPnWOwF3-SzHZ n2Y?L&nHimBmF4xSw~j&V)c)w2m3KvfvCQD<>gTe~DWM4fLlZg@ diff --git a/tests/ref/issue-1445-widow-orphan-unnecessary-skip.png b/tests/ref/issue-1445-widow-orphan-unnecessary-skip.png new file mode 100644 index 0000000000000000000000000000000000000000..7cd7888da70b3daab81d86edaa02d04fe830cc94 GIT binary patch literal 3549 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 XTpLak-i}o000000NkvXXu0mjf+1$t5 literal 0 HcmV?d00001 diff --git a/tests/ref/issue-multiple-footnote-in-one-line.png b/tests/ref/issue-multiple-footnote-in-one-line.png index 1d8c017d4329c3d8d5dacdfa66aff49d96e36fbe..cdb83af204902f3fea498fd5d19b764976fab52e 100644 GIT binary patch delta 680 zcmV;Z0$2UX1;7Q6B!5a!OjJex|Nq(9*`}tZsHmvP$;s>M>*nX{O;1-(Pft}gwu} zl9H^ftXWxE;Nala*4Ce&pQ)*-`T6<%{r$$q#_#X%jg5`n-QBdbw7|f?V`F3T^77u^ z-d$g3R8&-AVq%@0opW<@_V)IXk&%>?l!=Ln)YR0lu&{1!Zfb0FMMqD}&eq-E=kD+G z_xJhn@$tdI!GG1&)ym4sYiny{WMp@Dcd@at_4W1V=~gelRdFH8nLpK0ZG`KO-X}0002xX^waR00A*cL_t(| z+U?ZEPD4=?h2e!1cXumJaSFwy^cFp*#idZ(dH*Q`(tjzV<{py%-wXW7PWBQ200000 z000000000000000x#HUqE)O7^s-=v)HTk0=B-Pd!DY&t@6}`N=Ubpf?Rpq#3_Kl>N zr}GP&y74C*Nq$I&2PsKjeCI4sRD7G34+5u0?|CxDNb>e}Cnu~iRz8TF9KXC~!k(tS zdSesy80000000000Kz8N-Nm*9F z!E;e(N4uP`jcty?O|4p(4?74qw>zB6mX}7HgjZJrqw-zCS``$Ih4^n|T~~Ks zuuKlvo?ey8f%M(I$|uI%EZ3+S>Yap3<<&U}mm$m!hUG$dj_(HWZ~Xv!>0pn^E6U&i O0000gwv|=k4R;B+L!NI}4z<%^rKhi^rlzH(rK6*xp`oFmprD+d zrkR_gnVO!MnVpxHmzI{6la-y6l$4T^l8=#@k&%&&kC%;&jfIDgg@uKLgoJ>Cihh8I zfPjF1e}8^{et&v;dU<(yb8~ZWaBymDbZcvCYHDg?WNc$&V_aNZR#sM3RaH?@QBO}# zOiWBkNl8dZNJU3aLq$zKK0Z4;J32Z#H8nLbFfb@6C?g{yARr(b8X5oq01r9gUjP6B zG)Y83RCwC$)mKsiK@>*e-!VHX=$LcPIfIG`Q88jh1%J#r?ElRj@FI3wc)kss>h8L? z1poj50000000000000000Kk9oWeGPAAd~83-kneCc<^LeOFSZZXXeAvl1HiOeo%&$ zpZC5r$n!iI7Hw->JG2lkJdyHW9rNMMm`|Oj3$E+3QjlG8YI9`R{L^BrOG}|Gnf5N< zv%KwAnSb`?jP7c0KP=Ne0>|>9G!okF9a9n=BpPd2#00000 zfS(JbrQg$#77vq6a+@ncw$J=>H`a5&r|K5!UTw#LnhQX)343N;s+Y5i8;AnIlIwIC)GrfIsUS XlzLqRLaK3000000NkvXXu0mjfZgXzZ diff --git a/tests/suite/layout/flow/orphan.typ b/tests/suite/layout/flow/orphan.typ index 70eac731b..bd938d96d 100644 --- a/tests/suite/layout/flow/orphan.typ +++ b/tests/suite/layout/flow/orphan.typ @@ -29,3 +29,14 @@ This is the start and it goes on. // All three lines go to the next page. #set text(olive) #lorem(10) + +--- flow-widow-forced --- +// Ensure that a widow is allowed when the three lines don't all fit. +#set page(height: 50pt) +#lorem(10) + +--- issue-1445-widow-orphan-unnecessary-skip --- +// Ensure that widow/orphan prevention doesn't unnecessarily move things +// to another page. +#set page(width: 16cm) +#block(height: 30pt, fill: aqua, columns(2, lorem(19))) diff --git a/tests/suite/layout/grid/headers.typ b/tests/suite/layout/grid/headers.typ index c9c95e134..c3b929979 100644 --- a/tests/suite/layout/grid/headers.typ +++ b/tests/suite/layout/grid/headers.typ @@ -220,7 +220,7 @@ --- grid-header-lack-of-space --- // Test lack of space for header + text. -#set page(height: 9em) +#set page(height: 8em) #table( rows: (auto, 2.5em, auto, auto, 10em), diff --git a/tests/suite/model/footnote.typ b/tests/suite/model/footnote.typ index d72ca25a0..993725518 100644 --- a/tests/suite/model/footnote.typ +++ b/tests/suite/model/footnote.typ @@ -163,11 +163,10 @@ Ref @fn --- issue-multiple-footnote-in-one-line --- // Test that the logic that keeps footnote entry together with // their markers also works for multiple footnotes in a single -// line or frame (here, there are two lines, but they are one -// unit due to orphan prevention). +// line. #set page(height: 100pt) -#v(40pt) -A #footnote[a] \ +#v(50pt) +A #footnote[a] B #footnote[b] --- issue-1433-footnote-in-list ---