From 2d63b89a0a6087ece437e99d24081af63a0af092 Mon Sep 17 00:00:00 2001 From: mkorje Date: Fri, 11 Oct 2024 18:08:21 +1100 Subject: [PATCH 1/5] Add `column-gap` option to `math.equation` --- crates/typst-layout/src/math/mat.rs | 2 +- crates/typst-layout/src/math/run.rs | 4 ++- crates/typst-layout/src/math/shared.rs | 7 +++-- crates/typst-library/src/math/equation.rs | 18 +++++++++++-- tests/ref/math-align-columns.png | Bin 0 -> 1407 bytes tests/ref/math-equation-align-column-gap.png | Bin 0 -> 489 bytes tests/ref/math-equation-align-columns-1.png | Bin 0 -> 1950 bytes tests/ref/math-equation-align-columns-2.png | Bin 0 -> 771 bytes tests/ref/math-multiline-line-spacing.png | Bin 0 -> 474 bytes tests/suite/math/alignment.typ | 1 + tests/suite/math/call.typ | 1 + tests/suite/math/equation.typ | 26 +++++++++++++++++++ tests/suite/math/multiline.typ | 18 ++++++++++++- 13 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 tests/ref/math-align-columns.png create mode 100644 tests/ref/math-equation-align-column-gap.png create mode 100644 tests/ref/math-equation-align-columns-1.png create mode 100644 tests/ref/math-equation-align-columns-2.png create mode 100644 tests/ref/math-multiline-line-spacing.png diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index d678f8658..1056735a7 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -220,7 +220,7 @@ fn layout_body( let mut x = Abs::zero(); for (index, col) in cols.into_iter().enumerate() { - let AlignmentResult { points, width: rcol } = alignments(&col); + let AlignmentResult { points, width: rcol } = alignments(&col, Abs::zero()); let mut y = Abs::zero(); diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index ae64368d6..faee3797a 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -192,7 +192,9 @@ impl MathRun { pub fn multiline_frame_builder(self, styles: StyleChain) -> MathRunFrameBuilder { let rows: Vec<_> = self.rows(); let row_count = rows.len(); - let alignments = alignments(&rows); + + let column_gap = EquationElem::column_gap_in(styles).resolve(styles); + let alignments = alignments(&rows, column_gap); let leading = if EquationElem::size_in(styles) >= MathSize::Text { ParElem::leading_in(styles) diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 600c130d4..8b471376b 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -118,7 +118,7 @@ pub fn stack( baseline: usize, alternator: LeftRightAlternator, ) -> Frame { - let AlignmentResult { points, width } = alignments(&rows); + let AlignmentResult { points, width } = alignments(&rows, Abs::zero()); let rows: Vec<_> = rows .into_iter() .map(|row| row.into_line_frame(&points, alternator)) @@ -149,7 +149,7 @@ pub fn stack( } /// Determine the positions of the alignment points, according to the input rows combined. -pub fn alignments(rows: &[MathRun]) -> AlignmentResult { +pub fn alignments(rows: &[MathRun], gap: Abs) -> AlignmentResult { let mut widths = Vec::::new(); let mut pending_width = Abs::zero(); @@ -159,6 +159,9 @@ pub fn alignments(rows: &[MathRun]) -> AlignmentResult { for fragment in row.iter() { if matches!(fragment, MathFragment::Align) { + if alignment_index > 0 && alignment_index % 2 == 0 { + width += gap; + } if alignment_index < widths.len() { widths[alignment_index].set_max(width); } else { diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs index 32be216a4..8195ded2f 100644 --- a/crates/typst-library/src/math/equation.rs +++ b/crates/typst-library/src/math/equation.rs @@ -11,13 +11,15 @@ use crate::foundations::{ }; use crate::introspection::{Count, Counter, CounterUpdate, Locatable}; use crate::layout::{ - AlignElem, Alignment, BlockElem, InlineElem, OuterHAlignment, SpecificAlignment, - VAlignment, + AlignElem, Alignment, BlockElem, Em, InlineElem, Length, OuterHAlignment, + SpecificAlignment, VAlignment, }; use crate::math::{MathSize, MathVariant}; use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement}; use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem}; +const DEFAULT_COL_GAP: Em = Em::new(1.5); + /// A mathematical equation. /// /// Can be displayed inline with text or as a separate block. An equation @@ -101,6 +103,17 @@ pub struct EquationElem { /// ``` pub supplement: Smart>, + /// The gap between columns. + /// + /// ```example + /// #set math.equation(column-gap: 3em) + /// $ 4 &= 4 & &"yes" \ + /// 0 &= 0 & &"no" \ + /// 1+1 &= 2 & &"maybe" $ + /// ``` + #[default(DEFAULT_COL_GAP.into())] + pub column_gap: Length, + /// The contents of the equation. #[required] pub body: Content, @@ -185,6 +198,7 @@ impl Show for Packed { impl ShowSet for Packed { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); + out.set(EquationElem::set_column_gap(self.column_gap(styles))); if self.block(styles) { out.set(AlignElem::set_alignment(Alignment::CENTER)); out.set(BlockElem::set_breakable(false)); diff --git a/tests/ref/math-align-columns.png b/tests/ref/math-align-columns.png new file mode 100644 index 0000000000000000000000000000000000000000..8420ae315385954c72a71fc503728ae024bb8062 GIT binary patch literal 1407 zcmV-_1%UdAP)NklrYd69LI70hQ)2!GN+p{L#A=E;O2D8Hl3o;s7#&2TNn^05>(JA^OE3(sN6*5S_&1F zc|~zm7yf(_g5oP>U}chcq0;dk?}FH1I`QAxa1&V{RnP*A$Bkp>fCT%IAM1h68G0a^eIHL zmjOgLFMRV^R5(CR#ISS#Vz&IgCI+A?VpyaJ@W^rQZ`|*z^bJH}Wfc~;=ZH)+0FBNI zr${NZH@wh+nAPw?i?hNVp4;B7X#M<`zDiZ=OOQ)+(4ak>E4$EOVEQeoZ zc<(G3qfo5v`vRt{m$e|VF3<=4PO38 zE!4q#7I3l9^eJ?>L*9BQT@NRJMMU>i7hH~XAe?w-6%uAw22LIZ%~HDdo`>r}JpZu- zjw;Z2HX^bx52$}+f4H=w_H%&85yK*l07V|^aPMwV_^EdX4?%F~b)VzF($p)fuL0Z; z#^-?cHyr$PSwoZwlaUBJ4WZGnp$yjDggKosyAF#h483h;q_X({#rB8Kw8KQ{ix=tO zyBu(}&^Pljo$k;ut(US5Q0y*#anDQ(6qMN=PFm2;jZZQVQO#5)?YWNm$b4l&PzkL; zE0l3NHPmkQQO53WLG0Gnv*|^z=YaIdCsPjpx5B3rm8x1K8dSr27?aB3t=jIedLuo1 zE+Db)s9mq(clb*!JaGdIg@Qon9?u>Os%0Cyrt;Lo@kwAFWIKm3^TL2gYg8TeLsLsZw z;uWAzaX5VDXTY@+u|@YyB^}SPDLjS>svQpd`~gGnTA(LF@DyP1&E%C2VKjxXxedVL zkXgJ?U*~wZVJ?`~Xb`{na5R|XZNIo=IpA=(Imojhbw@Q~ z_h-cxT=1yFmS>y1o{b~iuhnWhkTPgrXvU_lrp9jha>-#iEQeo~e*w_?DYj-ii$MSY N002ovPDHLkV1iq5mEiyY literal 0 HcmV?d00001 diff --git a/tests/ref/math-equation-align-column-gap.png b/tests/ref/math-equation-align-column-gap.png new file mode 100644 index 0000000000000000000000000000000000000000..696793129838f29c4e5722abc53c47c3a26abb82 GIT binary patch literal 489 zcmV98`Nk2ggUDy^%cWF1h(M1Vew58C3V4;haUKc_afs_b^P$q<8i_oe;1&s=c z2Jw;zDtKi?yhKD4Q8Xf=lZnnFN8Fs%IZPBm&hHC6yzug`;DS_`unC*637=>9UyyQz z-unrsYAIK&Ssl(N<~$@{>w7))j%YJcQ0Cq&E zEdq_!-gT7W;23w2IFGTSXuFZeLvppaP^}K%@h0C*OBb(vR;qc5KB$RG)~fBwE;2E>F-_*8Bj|Bkws{f6zDD*W@_^!W{k)0E@BqZ8h@ zlH)+JAnYR8H?0!74x;scfl}cFfP1YaB9N`( z)3h?YeYL29 z5h!Dy^jte6hvTDL73Rj`2akdCt?2(40E+!jK&fQSR&c9I@DYHvMLYVT&;=KFa2H*X zeMM>R-sg{ldlko*iC6ZD5-2n!9o&q*`cJ{su1@ZSGHboS{Sis{+*z{=5>G7IWq_z~ zMFo_|4uixGfo1&X7_k{jT%BbFwb=u?KuS6^X{*& zL1H(S)IwSAN?+-(xd};yjyZ_owRrYQKRIx1IwlFj6Xw1)Mr*a&_tfv-YHl^Rn)_blUK2W?6?DlC@2f#}$l-Z9CAaUW z{EjL=G$2R|4Zi5>JIrHeZ{OmSiFk2&rIQljQF+V?{kKgBkixVx7oWT%gkNp^A z?!z?>&*gK&4TB|WQs7kghL9$3^6x7~FNK3?;PQj>F;qe>2D$ah>)qgn$$e0N1vql> zP$?L%G4&WwueX3PuZ_42IL}?3a~h~KR^@<+xIYh)rr8Bu03hrPJ_tC^ecez9wCiiP z+5q6>Lkr=caKCKs=u*rzrKM;7*$SrP#drI_6@^(|1%ND{8-PsieSaaOvO2hKf%snd z_!$$pErI<$0Iq&=0+7jVo`a(+1^1rmEdaA#yBm@Prz4Nc<$l?Q=!Cd^`RgI7d>|Fv zL`$1D0Q6_BJS>-c<|*VaERJlQ2T|+T0B}An)(HS`a#fm5CU@#;;Dx>~F|!w{1){E*?cio%&vNmq z58c%5G`D^cI>$q|vknji6oQLMhtT0Pcfl)_72(?!^%s8P6Ws&G;?V&~SV2ID^!ZU% zgdwV4(>DiZ&A(}y{M~93nEu%fkRNUm2iCdkrn%}pw1M_h{3*cE~6t!BAOPsg|soVx~X8=5d? z*0J|%SN=Fz>kTsNq`+7ve6jmOE>=SB_|O3+&}BHhKLxTHhvymU?+t2hHMg2u&8_BE zbE~-t^%bG7A{12Q+=Tjy(pQlSDr#<8eMRr9=mix$H@Utd_f>>~ijbRJUy=JNLP2$R zxykhvxv!Ebs79EZTwhW9Dw%?6gt^J}6}hjHDX2!6n_OQ}`zpDDYNWZz^%b?Rk}0T0 znwwl-QTr;Hf@-9>$@LYruaYUKMwXjgUs3xinSzRtn^0d-`YLikMa@m9uPA*LwV+bJ zf2+CG+-mN7mYXmepzH*w!vJz_`fPx{6QB=jpb%+MJL5Y(SFjUcxy${~HQDM4 z*W1Gk18BKlup>G)KSXy**=)cKpVh!P@5-E{Yy~?3mOCjFcn;t13YQ-S&~s14&&PyW zXETeN1ZA^p`l+NQ8F8- zipqf@B@cPaBEC;kuoEz^i?i2k-`Ll>Soh09nPC7uw`74GC8?CzfPW{hEhFp%vJ4P@ z7;YFq&wYK*-UFoBfHixs!A98$lw@bM$_xVtxe2oY!cKrX44~#F%myes0qQV7{T{34 kR&%Sl)!b@sHTTHjA2GG;u`!CnApigX07*qoM6N<$f<>sa&;S4c literal 0 HcmV?d00001 diff --git a/tests/ref/math-equation-align-columns-2.png b/tests/ref/math-equation-align-columns-2.png new file mode 100644 index 0000000000000000000000000000000000000000..edf9862855b14627d0082ec6e775a081cdbc50c3 GIT binary patch literal 771 zcmV+e1N{7nP)R-*v0008YNklSM1{EP)1a`p6=)zD_L+#|QAzP4&7U*V*=QPvUEkWB> znLL*!YHVg=tEKCKwgYaNt23aJA znr>VZzN@4g_Zn8*l?`Er#R(2kOE<9c4o`tl$in1@E-Ag;(r=koOnl`dI0&e^ZX5;g zzFH@O3534N_Zp`6*O$bboj}1IKO0a{lg54miEX!?2jq0I_XcPT;gllXZuEp{nEsP% zO@EK#I(owi;A0T^#VFjaW0MSpCR32fWCb>jB5RmFChzlhqPRMT$Sx;<+8dneaw)DJ zIo=(jxcUxoX_$UBGql`>Qc}&fF#?!0R310`6r=j0_%Dj|Oq-=%!*pb1lzN+n&aos` z?Y}VUT2phz(SY{pf}XCY>*;#>V$%mv_{5-(dAL6?kDQmNXfkV(UYLmBnnGB=(a1cS zONxS~nNwCND!vA5kvf6wSy8*^i=S~ z64TE}|EP$K$&>sN%OoKqAw$YG3aHV^9O0=9=Zqq)^-2@002ovPDHLkV1jHw Bc1!>O literal 0 HcmV?d00001 diff --git a/tests/ref/math-multiline-line-spacing.png b/tests/ref/math-multiline-line-spacing.png new file mode 100644 index 0000000000000000000000000000000000000000..468e92f2b2594d8a26281d90b4cfb17d11f21034 GIT binary patch literal 474 zcmV<00VV#4P)F`k!HK* zpp=b|<3$qG<!iIx-4LwO-J@Fa1XKwS_YJ~DwK{X%T4c}q-E-p(#?MPg%PjF+dxhpQ`o zrGrEfkAbDUqWN;WPLLa$Tjj?Cf?*6}7{eIGForSw|H3|9vb7W+>wH*#$F`R86Vk%X z>#PvP`g4@VU6q?8aa$u*3E{dfyFor-09=a|s>|f34v8On&{$bULfAeR3=5g%gwpnm zpemAR05UTY!rFV_nu3kh&y=a*7G1f z43$>_s8yzEi~FzzNb=4@qtMaMX_d)RtfV|Q{Qd!{0X=mVZ#9N7jNvZM7r24Y*)q8{ Q=>Px#07*qoM6N<$f`KK?djJ3c literal 0 HcmV?d00001 diff --git a/tests/suite/math/alignment.typ b/tests/suite/math/alignment.typ index 941c20556..521886cc9 100644 --- a/tests/suite/math/alignment.typ +++ b/tests/suite/math/alignment.typ @@ -28,6 +28,7 @@ $ --- math-align-toggle --- // Test #460 equations. +#set math.equation(column-gap: 0em) $ a &=b & quad c&=d \ e &=f & g&=h diff --git a/tests/suite/math/call.typ b/tests/suite/math/call.typ index 5caacfac6..116168a11 100644 --- a/tests/suite/math/call.typ +++ b/tests/suite/math/call.typ @@ -205,6 +205,7 @@ $ sin(#1) $ // attach, etc.) // // This is not good, so this test should fail and be updated once it is fixed. +#set math.equation(column-gap: 0em) #let id(body) = body #let bx(body) = box(body, stroke: blue+0.5pt, inset: (x:2pt, y:3pt)) #let eq(body) = math.equation(body) diff --git a/tests/suite/math/equation.typ b/tests/suite/math/equation.typ index 148a49d02..c379e5287 100644 --- a/tests/suite/math/equation.typ +++ b/tests/suite/math/equation.typ @@ -54,6 +54,32 @@ This is big: $sum_(i=0)^n$ #eq(start) #eq(end) +--- math-equation-align-columns-1 --- +// Test columns in equations. +#set page(width: auto) +$ sum a &<= & sum b &<= & &sum c \ + log sum a &<= & log sum b &<= & log &sum c $ + +#math.equation(block: true, column-gap: 0em, $ + sum a &<= & sum b &<= & &sum c \ + log sum a &<= & log sum b &<= & log &sum c +$) + +--- math-equation-align-columns-2 --- +// Test columns in equations. +#set page(width: auto) +#set math.equation(column-gap: 1em) +#block(stroke: black + 1pt, $ + && x & = y && & a & = b + c && && && \ + && -4 + 5x & = -2 && & a b & = c b && && && +$) + +--- math-equation-align-column-gap --- +// Test column-gap in equations. +#set math.equation(column-gap: 4em) +$ a &=b & c&=d \ + e &=f & g&=h $ + --- math-equation-number-align --- #set math.equation(numbering: "(1)") diff --git a/tests/suite/math/multiline.typ b/tests/suite/math/multiline.typ index 34e66b99c..ea5157219 100644 --- a/tests/suite/math/multiline.typ +++ b/tests/suite/math/multiline.typ @@ -17,6 +17,15 @@ $ x + 1 &= a^2 + b^2 \ $ a + b &= 2 + 3 &= 5 \ b &= c &= 3 $ +--- math-align-columns --- +// Test columns created with alignment points. +$ A &= B &= C \ + D &= E &= F $ +$ A &= B B B B &= C \ + D &= E &= F $ +$ A &= B & &= C \ + D &= E & &= F $ + --- math-align-cases --- // Test in case distinction. $ f := cases( @@ -55,6 +64,12 @@ $ $ Multiple trailing line breaks. +--- math-multiline-line-spacing --- +// Test modifying spacing between lines. +#set par(leading: 2em) +$ a &=b & c&=d \ + e &=f & g&=h $ + --- math-linebreaking-after-binop-and-rel --- // Basic breaking after binop, rel #let hrule(x) = box(line(length: x)) @@ -110,6 +125,7 @@ Nothing: $ $, just empty. --- math-pagebreaking --- // Test breaking of equations at page boundaries. #set page(height: 5em) +#set math.equation(column-gap: 0em) #show math.equation: set block(breakable: true) $ a &+ b + & c \ @@ -121,7 +137,7 @@ $ a &+ b + & c \ --- math-pagebreaking-numbered --- // Test breaking of equations with numbering. #set page(height: 5em) -#set math.equation(numbering: "1") +#set math.equation(column-gap: 0em, numbering: "1") #show math.equation: set block(breakable: true) $ a &+ b + & c \ From ac55a39b8ca94180567a680b0ffaaf165f50718f Mon Sep 17 00:00:00 2001 From: mkorje Date: Fri, 11 Oct 2024 18:24:43 +1100 Subject: [PATCH 2/5] Add note in docs --- docs/reference/library/math.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/reference/library/math.md b/docs/reference/library/math.md index 61f2bb58f..ac9c03ddb 100644 --- a/docs/reference/library/math.md +++ b/docs/reference/library/math.md @@ -78,6 +78,9 @@ alternating the alignment twice. `& &` and `&&` behave exactly the same way. Meanwhile, "multiply by 7" is right-aligned because just one `&` precedes it. Each alignment point simply alternates between right-aligned/left-aligned. +By default, there is a gap of `1.5em` added between columns. You can modify this +with the [`column-gap`]($math.equation.column-gap) parameter. + ```example $ (3x + y) / 7 &= 9 && "given" \ 3x + y &= 63 & "multiply by 7" \ From 8e13d1ecb886a106b2088102830ad647fb23f3da Mon Sep 17 00:00:00 2001 From: mkorje Date: Mon, 14 Oct 2024 13:30:14 +1100 Subject: [PATCH 3/5] Move setting style in show_set --- crates/typst-library/src/math/equation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs index 8195ded2f..137ebb9b7 100644 --- a/crates/typst-library/src/math/equation.rs +++ b/crates/typst-library/src/math/equation.rs @@ -198,12 +198,12 @@ impl Show for Packed { impl ShowSet for Packed { fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); - out.set(EquationElem::set_column_gap(self.column_gap(styles))); if self.block(styles) { out.set(AlignElem::set_alignment(Alignment::CENTER)); out.set(BlockElem::set_breakable(false)); out.set(ParLine::set_numbering(None)); out.set(EquationElem::set_size(MathSize::Display)); + out.set(EquationElem::set_column_gap(self.column_gap(styles))); } else { out.set(EquationElem::set_size(MathSize::Text)); } From f7bb5f72c5a580589455a87b41bf2a7eefdd7421 Mon Sep 17 00:00:00 2001 From: mkorje Date: Wed, 18 Dec 2024 00:20:05 +1100 Subject: [PATCH 4/5] Change default value to `1em` --- crates/typst-library/src/math/equation.rs | 2 +- docs/reference/library/math.md | 2 +- tests/ref/math-align-columns.png | Bin 1407 -> 1401 bytes tests/ref/math-equation-align-columns-1.png | Bin 1950 -> 1945 bytes tests/ref/math-multiline-line-spacing.png | Bin 474 -> 459 bytes 5 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs index 137ebb9b7..e60c055ad 100644 --- a/crates/typst-library/src/math/equation.rs +++ b/crates/typst-library/src/math/equation.rs @@ -18,7 +18,7 @@ use crate::math::{MathSize, MathVariant}; use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement}; use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem}; -const DEFAULT_COL_GAP: Em = Em::new(1.5); +const DEFAULT_COL_GAP: Em = Em::new(1.0); /// A mathematical equation. /// diff --git a/docs/reference/library/math.md b/docs/reference/library/math.md index ac9c03ddb..c632b286f 100644 --- a/docs/reference/library/math.md +++ b/docs/reference/library/math.md @@ -78,7 +78,7 @@ alternating the alignment twice. `& &` and `&&` behave exactly the same way. Meanwhile, "multiply by 7" is right-aligned because just one `&` precedes it. Each alignment point simply alternates between right-aligned/left-aligned. -By default, there is a gap of `1.5em` added between columns. You can modify this +By default, there is a gap of `1em` added between columns. You can modify this with the [`column-gap`]($math.equation.column-gap) parameter. ```example diff --git a/tests/ref/math-align-columns.png b/tests/ref/math-align-columns.png index 8420ae315385954c72a71fc503728ae024bb8062..fabfba8a5fc3796ca6e735019989e306d7b3fdcc 100644 GIT binary patch delta 1382 zcmV-s1)2K)3i%3%3C|K5i1SWwPp@@EZm}n=4ZX4hvQ96O)VCr^o5o3%_WEb4`FM2 zd;1_#qlSO=BW4#Kc6%z^zjklwxgZl_^;7fJIev)sejBXLUozmKF#dl+>M6v!-owlV zq@#6^=dq7^HjopFSi#PLSjMEIGwj9MF;9i7)&cvjNPpf=yCbp=BG4%udL5&_=_|lf z;kXnq&Km-ZJ%S+w^SgN3Q483591z4MQ17|0|6|5?)d7^}2z~=Fc!M>{4(M|n`TqGSjMI3J(CC~gm2rdA9Z%*>dWj~-yn~6m9 zc{q9!w$#AJo3La67PVsa2HRU^(P+&wfJ)cHxqUEIy0M=YJ}&`pE5-#+81Mz%+!1QF zK&7vEzEvCEWc;Z^j6f1^AT)=W#l4rOv=H)6ks z19~RLOKa~8 zbUA!D8@fM4V$RTT?`=C1(ld`9N?i_r+y+m50E5QC0?_t$;Z`F!*QycRLr}P;YCpId zHq8Z82Z8=t2P;5Ms38U;&UMxb9wFF!qJJXV^>A4L*gtIs+EflEfIu}P#tS%CrxIL3 z@V*{{>){{I(Y*5kfZ5;xV4gKdS5P0$jT!`w3EdpP<#1JMMpmH_3Dp^8=9ybe$WT`1 zHTmX}!*W;-%i&iQuIfYLnMR{|j&qjstj1^K?uN(bl_SwJ`%&|ulQ{EuTeUgM7k_ud zs(=C{x|TxOHk{~QI1HBU?uOfoeoh7$F$e|_6ohE=!gRbKert!M0z7p$tmsOJ0=TOZ z+(589`|;(?Hu15m1i9nDI}Hfj3~Tb$#{vMfwSqWJNl&`_PaP9$Mc@tuO4_~=zYsHzE zM3nA^gGxXhiFjj+aKw2%PB-?y4{nCbr(G+Gi9>8CesNhz><>6|^NkX>!}_*1oec>~ oo4|}SI=xQ5x#X}MmcuX6KfOIEtZFmY`2YX_07*qoM6N<$f|Uu5XaE2J delta 1388 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 literal 1950 zcmV;P2VwY$P)L29 z5h!Dy^jte6hvTDL73Rj`2akdCt?2(40E+!jK&fQSR&c9I@DYHvMLYVT&;=KFa2H*X zeMM>R-sg{ldlko*iC6ZD5-2n!9o&q*`cJ{su1@ZSGHboS{Sis{+*z{=5>G7IWq_z~ zMFo_|4uixGfo1&X7_k{jT%BbFwb=u?KuS6^X{*& zL1H(S)IwSAN?+-(xd};yjyZ_owRrYQKRIx1IwlFj6Xw1)Mr*a&_tfv-YHl^Rn)_blUK2W?6?DlC@2f#}$l-Z9CAaUW z{EjL=G$2R|4Zi5>JIrHeZ{OmSiFk2&rIQljQF+V?{kKgBkixVx7oWT%gkNp^A z?!z?>&*gK&4TB|WQs7kghL9$3^6x7~FNK3?;PQj>F;qe>2D$ah>)qgn$$e0N1vql> zP$?L%G4&WwueX3PuZ_42IL}?3a~h~KR^@<+xIYh)rr8Bu03hrPJ_tC^ecez9wCiiP z+5q6>Lkr=caKCKs=u*rzrKM;7*$SrP#drI_6@^(|1%ND{8-PsieSaaOvO2hKf%snd z_!$$pErI<$0Iq&=0+7jVo`a(+1^1rmEdaA#yBm@Prz4Nc<$l?Q=!Cd^`RgI7d>|Fv zL`$1D0Q6_BJS>-c<|*VaERJlQ2T|+T0B}An)(HS`a#fm5CU@#;;Dx>~F|!w{1){E*?cio%&vNmq z58c%5G`D^cI>$q|vknji6oQLMhtT0Pcfl)_72(?!^%s8P6Ws&G;?V&~SV2ID^!ZU% zgdwV4(>DiZ&A(}y{M~93nEu%fkRNUm2iCdkrn%}pw1M_h{3*cE~6t!BAOPsg|soVx~X8=5d? z*0J|%SN=Fz>kTsNq`+7ve6jmOE>=SB_|O3+&}BHhKLxTHhvymU?+t2hHMg2u&8_BE zbE~-t^%bG7A{12Q+=Tjy(pQlSDr#<8eMRr9=mix$H@Utd_f>>~ijbRJUy=JNLP2$R zxykhvxv!Ebs79EZTwhW9Dw%?6gt^J}6}hjHDX2!6n_OQ}`zpDDYNWZz^%b?Rk}0T0 znwwl-QTr;Hf@-9>$@LYruaYUKMwXjgUs3xinSzRtn^0d-`YLikMa@m9uPA*LwV+bJ zf2+CG+-mN7mYXmepzH*w!vJz_`fPx{6QB=jpb%+MJL5Y(SFjUcxy${~HQDM4 z*W1Gk18BKlup>G)KSXy**=)cKpVh!P@5-E{Yy~?3mOCjFcn;t13YQ-S&~s14&&PyW zXETeN1ZA^p`l+NQ8F8- zipqf@B@cPaBEC;kuoEz^i?i2k-`Ll>Soh09nPC7uw`74GC8?CzfPW{hEhFp%vJ4P@ z7;YFq&wYK*-UFoBfHixs!A98$lw@bM$_xVtxe2oY!cKrX44~#F%myes0qQV7{T{34 kR&%Sl)!b@sHTTHjA2GG;u`!CnApigX07*qoM6N<$f<>sa&;S4c diff --git a/tests/ref/math-multiline-line-spacing.png b/tests/ref/math-multiline-line-spacing.png index 468e92f2b2594d8a26281d90b4cfb17d11f21034..7d4447893d0567373a45666e5e946fb05ff56b8d 100644 GIT binary patch delta 433 zcmV;i0Z#tf1Iq)DB!7}gL_t(|+U?i7OG057$MOD|wupwJp@yQOsYZvQsmqYm&;<>r zf*{bzK#{Phv=EW73nWBP7GZ-?B3S9MFg3f7l!3SWg`3YAba=}Mp6~X)9Qg2X;Dx6K ze^d>_3^UAd6=914m7AaVd3axk${FpVuw=U_fHbpuNN*&EAAht{o8n0j_Ky}CUjewa z#f$*Z5wJT^(uq1f0Kl&;rpo}>G^83J2rInk1S#DMgB~R{Nhp9+mkExF!mWkNaUYpD zGL4=PN@@mTWa4xOOtvdQcxhL!bc3{DvXZmOwR0f*!l{}K@&m#!!wfUbFvAQp{6FEW zW(?)Z4Y_^?-hVKAP`1G^#Np@T9B8q=6>{}(o(HY{!Yd4KEhd)f3WH?JEnVV#?R15S z)Jv*6QMhh*~Fm{l$30R!A>V0ma^SpK^T+Q)-%u? zCUR6C)h9seLUd?d6vjPdy=ln*%OlkV+6NH*5{Bhv+A6fL$%@fw#00000NkvXXu0mjfbRo@k delta 448 zcmV;x0YCoB1KIj#6lbt6S1-+jWTI)NCHDBAq}ZPNxjqxQ9 zG7MuF!x-*XxZIa&(|qhLb#&vAX1nH~l#P$$MH1BI(LAm3p??F8B<#LdFCjd88=MCK z&a(bJDF8TSV4U`t%vKx#96wqN02+$gl23d%Jqq>!iIx-4LwO-J@Fa1XKwS_YJ~DwK z{X%T4c}q-E-p(#?MPg%PjF+dxhpQ`orGrEfkAbDUqWN;WPLLa$Tjj?Cf?*6}7{eIG zForSw|H3|9vVXM{AM1Qre#f?!@)Od+&FicX#rkuU#$A=0Byn3KRSDs`F1tZKVE|l< z6{^eRrw)l9deB%|Mnc#=7YqxT<%H7qjG!u#XaF)Z62jVh;F^Ms)z6f+5aow}*!W2e z=5S7Y_{^2u{1mWZTDx$r=}F>sg4XjOJ`9yt0jO1`X*rAgumwo+&O@Wn(a&j>$x^JO qJU9IQ0jU8!brx?mhB1ucF3uOYfza79xi;wl0000 Date: Wed, 15 Jan 2025 22:36:02 +1100 Subject: [PATCH 5/5] WIP --- crates/typst-layout/src/math/mat.rs | 2 +- crates/typst-layout/src/math/mod.rs | 16 ++- crates/typst-layout/src/math/run.rs | 21 ++-- crates/typst-layout/src/math/shared.rs | 94 +++++++++++++--- crates/typst-library/src/math/equation.rs | 124 ++++++++++++++++++++-- 5 files changed, 223 insertions(+), 34 deletions(-) diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index 1056735a7..f478675b0 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -220,7 +220,7 @@ fn layout_body( let mut x = Abs::zero(); for (index, col) in cols.into_iter().enumerate() { - let AlignmentResult { points, width: rcol } = alignments(&col, Abs::zero()); + let AlignmentResult { points, width: rcol, .. } = alignments(&col, None); let mut y = Abs::zero(); diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs index 708a4443d..b4791f26a 100644 --- a/crates/typst-layout/src/math/mod.rs +++ b/crates/typst-layout/src/math/mod.rs @@ -98,6 +98,12 @@ pub fn layout_equation_inline( Ok(items) } +pub struct EquationSizings<'a> { + region_size_x: Abs, + gaps: &'a [GapSizing], + padding: &'a [GapSizing], +} + /// Layout a block-level equation (in a flow). #[typst_macros::time(span = elem.span())] pub fn layout_equation_block( @@ -118,9 +124,17 @@ pub fn layout_equation_block( let scale_style = style_for_script_scale(&ctx); let styles = styles.chain(&scale_style); + let gaps = elem.column_gap(styles).resolve(styles); + let padding = elem.column_padding(styles).resolve(styles); + let sizings = EquationSizings { + region_size_x: regions.size.x, + gaps: gaps.0.as_slice(), + padding: padding.0.as_slice(), + }; + let full_equation_builder = ctx .layout_into_run(&elem.body, styles)? - .multiline_frame_builder(styles); + .multiline_frame_builder(styles, Some(sizings)); let width = full_equation_builder.size.x; let equation_builders = if BlockElem::breakable_in(styles) { diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index faee3797a..e542b6385 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -6,7 +6,7 @@ use typst_library::math::{EquationElem, MathSize, MEDIUM, THICK, THIN}; use typst_library::model::ParElem; use unicode_math_class::MathClass; -use super::{alignments, FrameFragment, MathFragment}; +use super::{alignments, EquationSizings, FrameFragment, MathFragment}; const TIGHT_LEADING: Em = Em::new(0.25); @@ -165,7 +165,7 @@ impl MathRun { if !self.is_multiline() { self.into_line_frame(&[], LeftRightAlternator::Right) } else { - self.multiline_frame_builder(styles).build() + self.multiline_frame_builder(styles, None).build() } } @@ -189,12 +189,15 @@ impl MathRun { /// Returns a builder that lays out the [`MathFragment`]s into a possibly /// multi-row [`Frame`]. The rows are aligned using the same set of alignment /// points computed from them as a whole. - pub fn multiline_frame_builder(self, styles: StyleChain) -> MathRunFrameBuilder { + pub fn multiline_frame_builder( + self, + styles: StyleChain, + sizings: Option, + ) -> MathRunFrameBuilder { let rows: Vec<_> = self.rows(); let row_count = rows.len(); - let column_gap = EquationElem::column_gap_in(styles).resolve(styles); - let alignments = alignments(&rows, column_gap); + let alignments = alignments(&rows, sizings); let leading = if EquationElem::size_in(styles) >= MathSize::Text { ParElem::leading_in(styles) @@ -215,15 +218,17 @@ impl MathRun { size.y += leading; } - let mut pos = Point::with_y(size.y); + let mut pos = Point::new(alignments.padding.0, size.y); if alignments.points.is_empty() { - pos.x = align.position(alignments.width - sub.width()); + pos.x += align.position(alignments.width - sub.width()); } - size.x.set_max(sub.width()); + size.x.set_max(sub.width() + alignments.padding.0); size.y += sub.height(); frames.push((sub, pos)); } + size.x += alignments.padding.1; + MathRunFrameBuilder { size, frames } } diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 8b471376b..d3c1cbc04 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -1,10 +1,12 @@ use ttf_parser::math::MathValue; use typst_library::foundations::{Style, StyleChain}; -use typst_library::layout::{Abs, Em, FixedAlignment, Frame, Point, Size, VAlignment}; -use typst_library::math::{EquationElem, MathSize}; +use typst_library::layout::{ + Abs, Em, FixedAlignment, Fr, Frame, Point, Size, VAlignment, +}; +use typst_library::math::{EquationElem, GapSizing, MathSize}; use typst_utils::LazyHash; -use super::{LeftRightAlternator, MathContext, MathFragment, MathRun}; +use super::{EquationSizings, LeftRightAlternator, MathContext, MathFragment, MathRun}; macro_rules! scaled { ($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => { @@ -118,7 +120,7 @@ pub fn stack( baseline: usize, alternator: LeftRightAlternator, ) -> Frame { - let AlignmentResult { points, width } = alignments(&rows, Abs::zero()); + let AlignmentResult { points, width, .. } = alignments(&rows, None); let rows: Vec<_> = rows .into_iter() .map(|row| row.into_line_frame(&points, alternator)) @@ -148,8 +150,15 @@ pub fn stack( frame } -/// Determine the positions of the alignment points, according to the input rows combined. -pub fn alignments(rows: &[MathRun], gap: Abs) -> AlignmentResult { +pub struct AlignmentResult { + pub points: Vec, + pub width: Abs, + pub padding: (Abs, Abs), +} + +/// Determine the positions of the alignment points, according to the input +/// rows combined. +pub fn alignments(rows: &[MathRun], sizings: Option) -> AlignmentResult { let mut widths = Vec::::new(); let mut pending_width = Abs::zero(); @@ -159,9 +168,6 @@ pub fn alignments(rows: &[MathRun], gap: Abs) -> AlignmentResult { for fragment in row.iter() { if matches!(fragment, MathFragment::Align) { - if alignment_index > 0 && alignment_index % 2 == 0 { - width += gap; - } if alignment_index < widths.len() { widths[alignment_index].set_max(width); } else { @@ -182,18 +188,80 @@ pub fn alignments(rows: &[MathRun], gap: Abs) -> AlignmentResult { } } + if widths.is_empty() { + widths.push(pending_width); + let padding = add_gaps(&mut widths, sizings); + return AlignmentResult { width: pending_width, points: vec![], padding }; + } + + let padding = add_gaps(&mut widths, sizings); let mut points = widths; for i in 1..points.len() { let prev = points[i - 1]; points[i] += prev; } AlignmentResult { - width: points.last().copied().unwrap_or(pending_width), + width: points.last().copied().unwrap(), points, + padding, } } -pub struct AlignmentResult { - pub points: Vec, - pub width: Abs, +/// Inserts gaps between columns given by the alignments. +fn add_gaps(widths: &mut [Abs], sizings: Option) -> (Abs, Abs) { + let Some(sizings) = sizings else { + return (Abs::zero(), Abs::zero()); + }; + + // Padding to be returned. + let mut padding = [Abs::zero(), Abs::zero()]; + + // Number of gaps between columns. + let len = widths.len(); + let ngaps = len.div_ceil(2).saturating_sub(1); + + // Discard excess gaps or repeat the last gap to match the number of gaps. + let mut gaps = sizings.gaps.to_vec(); + gaps.truncate(ngaps); + if let Some(last_gap) = gaps.last().copied() { + gaps.extend(std::iter::repeat_n(last_gap, ngaps.saturating_sub(gaps.len()))); + } + + // Sum of fractions of all fractional gaps. + let mut fr = Fr::zero(); + + // Resolve the size of all relative gaps and compute the sum of all + // fractional gaps. + let region_width = sizings.region_size_x; + for (i, gap) in gaps.iter().enumerate() { + match gap { + GapSizing::Rel(v) => widths[1 + i * 2] += v.relative_to(region_width), + GapSizing::Fr(v) => fr += *v, + } + } + for (i, gap) in sizings.padding.iter().enumerate() { + match gap { + GapSizing::Rel(v) => padding[i] = v.relative_to(region_width), + GapSizing::Fr(v) => fr += *v, + } + } + + // Size that is not used by fixed-size gaps. + let remaining = region_width - (widths.iter().sum::() + padding.iter().sum()); + + // Distribute remaining space to fractional gaps. + if !remaining.approx_empty() { + for (i, gap) in gaps.iter().enumerate() { + if let GapSizing::Fr(v) = gap { + widths[1 + i * 2] += v.share(fr, remaining); + } + } + for (i, gap) in sizings.padding.iter().enumerate() { + if let GapSizing::Fr(v) = gap { + padding[i] = v.share(fr, remaining); + } + } + } + + (padding[0], padding[1]) } diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs index e60c055ad..e91822fb2 100644 --- a/crates/typst-library/src/math/equation.rs +++ b/crates/typst-library/src/math/equation.rs @@ -1,25 +1,24 @@ use std::num::NonZeroUsize; -use typst_utils::NonZeroExt; +use smallvec::{smallvec, SmallVec}; +use typst_utils::{NonZeroExt, Numeric}; use unicode_math_class::MathClass; -use crate::diag::SourceResult; +use crate::diag::{bail, HintedStrResult, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - elem, Content, NativeElement, Packed, Show, ShowSet, Smart, StyleChain, Styles, - Synthesize, + cast, elem, Array, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, + StyleChain, Styles, Synthesize, Value, }; use crate::introspection::{Count, Counter, CounterUpdate, Locatable}; use crate::layout::{ - AlignElem, Alignment, BlockElem, Em, InlineElem, Length, OuterHAlignment, - SpecificAlignment, VAlignment, + Abs, AlignElem, Alignment, BlockElem, Fr, InlineElem, Length, OuterHAlignment, Rel, + Spacing, SpecificAlignment, VAlignment, }; use crate::math::{MathSize, MathVariant}; use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement}; use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem}; -const DEFAULT_COL_GAP: Em = Em::new(1.0); - /// A mathematical equation. /// /// Can be displayed inline with text or as a separate block. An equation @@ -111,8 +110,14 @@ pub struct EquationElem { /// 0 &= 0 & &"no" \ /// 1+1 &= 2 & &"maybe" $ /// ``` - #[default(DEFAULT_COL_GAP.into())] - pub column_gap: Length, + #[default(Fr::one().into())] + #[borrowed] + pub column_gap: GapSizings, + + /// + #[default(Fr::one().into())] + #[borrowed] + pub column_padding: PaddingSizings, /// The contents of the equation. #[required] @@ -203,7 +208,6 @@ impl ShowSet for Packed { out.set(BlockElem::set_breakable(false)); out.set(ParLine::set_numbering(None)); out.set(EquationElem::set_size(MathSize::Display)); - out.set(EquationElem::set_column_gap(self.column_gap(styles))); } else { out.set(EquationElem::set_size(MathSize::Text)); } @@ -262,3 +266,101 @@ impl Outlinable for Packed { Content::empty() } } + +/// Gap sizing definitions. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct GapSizings(pub SmallVec<[GapSizing; 1]>); + +impl> From for GapSizings { + fn from(spacing: T) -> Self { + Self(smallvec![GapSizing::from(spacing)]) + } +} + +impl Resolve for &GapSizings { + type Output = GapSizings; + + fn resolve(self, styles: StyleChain) -> Self::Output { + Self::Output { + 0: self.0.iter().map(|v| v.resolve(styles)).collect(), + } + } +} + +cast! { + GapSizings, + self => self.0.into_value(), + v: GapSizing => Self(smallvec![v]), + v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), +} + +/// Padding sizing definitions. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct PaddingSizings(pub SmallVec<[GapSizing; 2]>); + +impl> From for PaddingSizings { + fn from(spacing: T) -> Self { + let spacing = spacing.into(); + Self(smallvec![GapSizing::from(spacing), GapSizing::from(spacing)]) + } +} + +impl Resolve for &PaddingSizings { + type Output = PaddingSizings; + + fn resolve(self, styles: StyleChain) -> Self::Output { + Self::Output { + 0: self.0.iter().map(|v| v.resolve(styles)).collect(), + } + } +} + +cast! { + PaddingSizings, + self => self.0.into_value(), + v: GapSizing => Self(smallvec![v, v]), + v: Array => match v.as_slice() { + [start, end] => Self(smallvec![start.clone().cast()?, end.clone().cast()?]), + _ => bail!("expected 2 sizings, found {}", v.len()), + }, +} + +/// Defines how to size a gap along an axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum GapSizing { + /// A size specified in absolute terms and relative to the parent's size. + Rel(Rel), + /// A size specified as a fraction of the remaining free space in the + /// parent. + Fr(Fr), +} + +impl Resolve for GapSizing { + type Output = GapSizing; + + fn resolve(self, styles: StyleChain) -> Self::Output { + match self { + Self::Rel(rel) => Self::Output::Rel(rel.resolve(styles)), + Self::Fr(fr) => Self::Output::Fr(fr), + } + } +} + +impl> From for GapSizing { + fn from(spacing: T) -> Self { + match spacing.into() { + Spacing::Rel(rel) => Self::Rel(rel), + Spacing::Fr(fr) => Self::Fr(fr), + } + } +} + +cast! { + GapSizing, + self => match self { + Self::Rel(rel) => rel.into_value(), + Self::Fr(fr) => fr.into_value(), + }, + v: Rel => Self::Rel(v), + v: Fr => Self::Fr(v), +}