From e7d5a293045022ea0aa28e10ac2f5e3b970d7ad5 Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski Date: Mon, 28 Apr 2025 10:55:28 -0400 Subject: [PATCH] remove line break opportunity when math operator precededes a closing paren --- crates/typst-layout/src/math/run.rs | 31 ++++++++++-------- ...ebreaking-after-relation-without-space.png | Bin 439 -> 2630 bytes tests/suite/math/multiline.typ | 3 ++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index ae64368d6..4ec76c253 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -278,6 +278,9 @@ impl MathRun { frame } + /// Convert this run of math fragments into a vector of inline items for + /// paragraph layout. Creates multiple fragments when relation or binary + /// operators are present to allow for line-breaking opportunities later. pub fn into_par_items(self) -> Vec { let mut items = vec![]; @@ -295,21 +298,24 @@ impl MathRun { let mut space_is_visible = false; - let is_relation = |f: &MathFragment| matches!(f.class(), MathClass::Relation); let is_space = |f: &MathFragment| { matches!(f, MathFragment::Space(_) | MathFragment::Spacing(_, _)) }; + let is_line_break_opportunity = |class, next_fragment| match class { + // Don't split when two relations are in a row or when preceding a + // closing parenthesis. + MathClass::Binary => next_fragment != Some(MathClass::Closing), + MathClass::Relation => { + !matches!(next_fragment, Some(MathClass::Relation | MathClass::Closing)) + } + _ => false, + }; let mut iter = self.0.into_iter().peekable(); while let Some(fragment) = iter.next() { - if space_is_visible { - match fragment { - MathFragment::Space(width) | MathFragment::Spacing(width, _) => { - items.push(InlineItem::Space(width, true)); - continue; - } - _ => {} - } + if space_is_visible && is_space(&fragment) { + items.push(InlineItem::Space(fragment.width(), true)); + continue; } let class = fragment.class(); @@ -323,10 +329,9 @@ impl MathRun { frame.push_frame(pos, fragment.into_frame()); empty = false; - if class == MathClass::Binary - || (class == MathClass::Relation - && !iter.peek().map(is_relation).unwrap_or_default()) - { + // Split our current frame when we encounter a binary operator or + // relation so that there is a line-breaking opportunity. + if is_line_break_opportunity(class, iter.peek().map(|f| f.class())) { let mut frame_prev = std::mem::replace(&mut frame, Frame::soft(Size::zero())); diff --git a/tests/ref/math-linebreaking-after-relation-without-space.png b/tests/ref/math-linebreaking-after-relation-without-space.png index 7c569ad1fd2166800e30b88c2f5ca689be659341..fb14137680a55a42a8717f11fe555378489031c0 100644 GIT binary patch literal 2630 zcmb7G4^UJ09S>FfgJq7Q(iS0|=-7reb#CN3kVplsaT0hc{*c1LB%sLZ0GU zN)f2VmKN?{U5|2=K}xM4%bzEI0|i6`it8FYoR5OK^AAdfV;@xqI*X ze))bs|Gux}jpW0D0nZ087>vLpuO_?+zD3~k(S~*4f4Tf8(F{gF;E{y*-;4Z=H`n}q z=N~@l#c83btZ%U4Oj1U3j;3|>@6@H*PhOMN{(9dIlk=Unx6gf99FsMk(mIq%bAP6p zze+cLn3F$Fr}nkBFVep{sY$mYTffO^cA)|HqPZUayV*63liouQ2?w1QJJ25K{>$!V(8~qym0ptp}j2hGksV=yEF}z!@)W}Ed z`{_g8o0m#MSeDb6?X+Y*Q~Glm`JHSK4Tili4F;*n;Hcy{gL#I(@`@+K$_c%0!@X!$ zkx*1DynmIJaBLZz-ZdVZfR09OS!i#RY9`8ZRA4P<#LH#h_DCdK7#eL zy`h3xDPrX!Jp!^41Zy%+9j4I7s3Ix~mouE;nLnxwVpTEbD96Cq zdq2z}1Y>FX38`^Hq-RSr7}Dtw=>@j<8e8wX%RDX+RH!)=uDBDWdeL!)v zBO^hfp`l*wT%}u1%Z<@0yBMtsj!ZV6VA@Ws-pC859cm(iV~Lm)>B|(Lt;lQlE+K26 zrZ~MO)1Er~xw?)<)ncQi9Q1qTO^5IRbkY~(Sc+#0;;cgYLlqsN=4zRr0~D-fdG4^lK~BmgqpJCJb}&glrn`kX zfIjo65FeYCkeMeC&qO-nv8IcS*Fm%4kXN_cO_IZOETA6iOr@h>Ln~YP_*O|@v19|l z18DpyScrMF*pLvh3jv!xQQurIVd!i*y55JPj%TjKO9t8`n^r|VB37uyX2_?{D7=I^ z%P;FXLkdSid}1#M+K-o{%(iMy>jSm-18*!M{wFnL6uCT1?UqrCvRnO?ksv@~;l;D1hJc@@-FsA-$KN=Ns# zI#bLrH55Pq?CVFHU4o0iEEmV$?y-Y$@}@ie;~rGTm8?%|Ix3mtN_W+{zN!NnZUVxT zeT|~S!omq*3;2crCb>XBMKd@x2X3-XwVKmM- zdSej;ZU_SFmlNo)aAt#^P2H~p(wzl(3#1r+f# zxE8bB8_XMTLI{b^nXbnH@ zqgWHvK@h-v9TI|#?DPF>$jC4`IBYf`ltE!yQtgouzms+4XEou|Oln#($}S9EZLePE zYSPWHrFypLU9QMqyV#`tz`Y+vdM0AabatnZ31I*E0iX|ovyfZRdiY)@v6n&K-t76Q zm-4P~+e@oOe%S=nss#Gc7j-lPSvib!Cn?90T3TAb(t+Xn*w$BU(9XQ5Eo!91^8FiS@oF=oT`IZ3{26RJ} zSBXlh824*b4{Agp4ETS(_S{3F_|zPK-Y&)LqOu85pw>~Qd-lAJ_UH_?V$(F73Xr|_ zq(AFb5e37tUa#^7}^o0NhHpOeC7`8Rvs&^m3Jp&m?N`Fx_PqHN%>pa6Bz-*L?2?dcZqPE)DS$d#f0VGHijBy=Syq68$IcU}B1?`b`pr`|&TRJ@Yy=mPJD Oj3bH33C*t@FZws;1~!fW delta 426 zcmV;b0agCS6t@GA7k_F900000QW46Y0004gNkl2#n5REgAXKhDI)yq{y_C3#65@6e+Qmv7MUKF1EJWOj|y3?vLF3zMt6l{RCch zKIeRo2c$d-E3B}>3cL0ip}^16;Ti$Hv$j4MkGj-%$=W#KJAZYD2aE*(=M#r+@Sd^Y z^rQzwVTH}ZZzQN{Y`U?+9S67K6u7bG&!C=_ zhRch=0)U&4FkI?MlQyod1oF<#@*6idta~I!=ZZ<4Q^|m%a^qPJRMh5r=%1qx^C UMq-$2r~m)}07*qoM6N<$f)`iH{Qv*} diff --git a/tests/suite/math/multiline.typ b/tests/suite/math/multiline.typ index 34e66b99c..70838dd8c 100644 --- a/tests/suite/math/multiline.typ +++ b/tests/suite/math/multiline.typ @@ -99,6 +99,9 @@ Multiple trailing line breaks. #let hrule(x) = box(line(length: x)) #hrule(90pt)$<;$\ #hrule(95pt)$<;$\ +// We don't linebreak before a closing paren, but do before an opening paren. +#hrule(90pt)$<($\ +#hrule(95pt)$<($ #hrule(90pt)$<)$\ #hrule(95pt)$<)$