From 3dcd8e6e6beb8c410e55141ca4fbc840679a1f14 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 19 Jul 2023 12:53:36 +0200 Subject: [PATCH] Positions placed elements relative to real container size (#1745) This positions placed elements relative to the real container size instead of relative to the base size of the region. This makes its usage more versatile. Fixes #82 Fixes #685 Fixes #1705 --- crates/typst-library/src/layout/flow.rs | 46 ++++++++++++++++------- crates/typst-library/src/layout/mod.rs | 3 ++ crates/typst-library/src/layout/place.rs | 29 ++------------ tests/ref/bugs/place-nested.png | Bin 0 -> 1298 bytes tests/ref/layout/place-nested.png | Bin 0 -> 6202 bytes tests/typ/layout/place-nested.typ | 33 ++++++++++++++++ 6 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 tests/ref/bugs/place-nested.png create mode 100644 tests/ref/layout/place-nested.png create mode 100644 tests/typ/layout/place-nested.typ diff --git a/crates/typst-library/src/layout/flow.rs b/crates/typst-library/src/layout/flow.rs index ce276f6d3..930857e75 100644 --- a/crates/typst-library/src/layout/flow.rs +++ b/crates/typst-library/src/layout/flow.rs @@ -66,6 +66,8 @@ impl Layout for FlowElem { sticky: true, movable: false, }); + } else if let Some(placed) = child.to::() { + layouter.layout_placed(vt, placed, styles)?; } else if child.can::() { layouter.layout_multiple(vt, child, styles)?; } else if child.is::() { @@ -128,7 +130,13 @@ enum FlowItem { /// (to keep it together with its footnotes). Frame { frame: Frame, aligns: Axes, sticky: bool, movable: bool }, /// An absolutely placed frame. - Placed { frame: Frame, y_align: Smart>, float: bool, clearance: Abs }, + Placed { + frame: Frame, + x_align: Align, + y_align: Smart>, + float: bool, + clearance: Abs, + }, /// A footnote frame (can also be the separator). Footnote(Frame), } @@ -258,6 +266,25 @@ impl<'a> FlowLayouter<'a> { Ok(()) } + /// Layout a placed element. + fn layout_placed( + &mut self, + vt: &mut Vt, + placed: &PlaceElem, + styles: StyleChain, + ) -> SourceResult<()> { + let float = placed.float(styles); + let clearance = placed.clearance(styles); + let alignment = placed.alignment(styles); + let x_align = alignment.map_or(Align::Center, |aligns| { + aligns.x.unwrap_or(GenAlign::Start).resolve(styles) + }); + let y_align = alignment.map(|align| align.y.resolve(styles)); + let frame = placed.layout(vt, styles, self.regions)?.into_frame(); + let item = FlowItem::Placed { frame, x_align, y_align, float, clearance }; + self.layout_item(vt, item) + } + /// Layout into multiple regions. fn layout_multiple( &mut self, @@ -265,16 +292,6 @@ impl<'a> FlowLayouter<'a> { block: &Content, styles: StyleChain, ) -> SourceResult<()> { - // Handle placed elements. - if let Some(placed) = block.to::() { - let float = placed.float(styles); - let clearance = placed.clearance(styles); - let y_align = placed.alignment(styles).map(|align| align.y.resolve(styles)); - let frame = placed.layout_inner(vt, styles, self.regions)?.into_frame(); - let item = FlowItem::Placed { frame, y_align, float, clearance }; - return self.layout_item(vt, item); - } - // Temporarily delegerate rootness to the columns. let is_root = self.root; if is_root && block.is::() { @@ -491,7 +508,8 @@ impl<'a> FlowLayouter<'a> { offset += frame.height(); output.push_frame(pos, frame); } - FlowItem::Placed { frame, y_align, float, .. } => { + FlowItem::Placed { frame, x_align, y_align, float, .. } => { + let x = x_align.position(size.x - frame.width()); let y = if float { match y_align { Smart::Custom(Some(Align::Top)) => { @@ -505,7 +523,7 @@ impl<'a> FlowLayouter<'a> { float_bottom_offset += frame.height(); y } - _ => offset + ruler.position(size.y - used.y), + _ => unreachable!("float must be y aligned"), } } else { match y_align { @@ -516,7 +534,7 @@ impl<'a> FlowLayouter<'a> { } }; - output.push_frame(Point::with_y(y), frame); + output.push_frame(Point::new(x, y), frame); } FlowItem::Footnote(frame) => { let y = size.y - footnote_height + footnote_offset; diff --git a/crates/typst-library/src/layout/mod.rs b/crates/typst-library/src/layout/mod.rs index 41490eb8f..ce728ccb7 100644 --- a/crates/typst-library/src/layout/mod.rs +++ b/crates/typst-library/src/layout/mod.rs @@ -266,6 +266,8 @@ fn realize_block<'a>( content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<(Content, StyleChain<'a>)> { + // These elements implement `Layout` but still require a flow for + // proper layout. if content.can::() && !content.is::() && !content.is::() @@ -275,6 +277,7 @@ fn realize_block<'a>( && !content.is::() && !content.is::() && !content.is::() + && !content.is::() && !applicable(content, styles) { return Ok((content.clone(), styles)); diff --git a/crates/typst-library/src/layout/place.rs b/crates/typst-library/src/layout/place.rs index 6f2681c10..115e5107a 100644 --- a/crates/typst-library/src/layout/place.rs +++ b/crates/typst-library/src/layout/place.rs @@ -91,36 +91,13 @@ impl Layout for PlaceElem { vt: &mut Vt, styles: StyleChain, regions: Regions, - ) -> SourceResult { - let mut frame = self.layout_inner(vt, styles, regions)?.into_frame(); - - // If expansion is off, zero all sizes so that we don't take up any - // space in our parent. Otherwise, respect the expand settings. - let target = regions.expand.select(regions.size, Size::zero()); - frame.resize(target, Align::LEFT_TOP); - - Ok(Fragment::frame(frame)) - } -} - -impl PlaceElem { - /// Layout without zeroing the frame size. - pub fn layout_inner( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, ) -> SourceResult { // The pod is the base area of the region because for absolute // placement we don't really care about the already used area. let base = regions.base(); - let expand = - Axes::new(base.x.is_finite(), base.y.is_finite() && !self.float(styles)); - - let pod = Regions::one(base, expand); - let float = self.float(styles); let alignment = self.alignment(styles); + if float && !matches!( alignment, @@ -145,7 +122,9 @@ impl PlaceElem { alignment.unwrap_or_else(|| Axes::with_x(Some(Align::Center.into()))), ); - child.layout(vt, styles, pod) + let pod = Regions::one(base, Axes::splat(false)); + let frame = child.layout(vt, styles, pod)?.into_frame(); + Ok(Fragment::frame(frame)) } } diff --git a/tests/ref/bugs/place-nested.png b/tests/ref/bugs/place-nested.png new file mode 100644 index 0000000000000000000000000000000000000000..b59dc5d3d86005de4acaa540196d1437a0d464b6 GIT binary patch literal 1298 zcmaiydr%Vu9LK-S@sL+5I^#qGiV%?jrG*2P4d-CYXgDN+j)tH-;t`@UkPcK31%_|L z2&@1Rk;h;l#6untD9kaCjfWyZhZ7m;GlLXv3R7%B8`W`dvItsl6K)>6cQs4|0Uu=5E~3$5V%}!bO3g3N0dJzxrq1z;eH79 zMTjqgybovu1L>CGil`EHq%59wErzFw_WTKNUs>YYWe?q8`HlEPTa4)brjy_gnOEb^aKAtvNIoq>(z2j&spQ4$`a&QhTmhuR+;h1s8&63~UlcFcy zE|Q9dsXSS(vA}EkKz9nepHxDYr?B^2s@gCTk@TxD?6`HyXof;}s@~`xy(8_GZ^goF zHl>syX9lu%k_ctl^wjW5Lo?;~)O`8aYVkUQCyHc4N!sH>hQIXnb}6#=rhZ1cKB(X6 zb;)OS?|!?AX|M6UX%UZfT6Q>=sU}D>OU0j?2v6HEpm{7#uL|d34v!{GHAi+m?BDxwU6#S52yt8{0+j6%#SUDnO>=JUb`I!DCp)?gaFY2u{_*|(r0{QgYmIbcRQM0>6O07K7) kpmkf#-7_TA=(X6+Y04^B32d9vb)TWn+~iI#b><%U15Ezv%>V!Z literal 0 HcmV?d00001 diff --git a/tests/ref/layout/place-nested.png b/tests/ref/layout/place-nested.png new file mode 100644 index 0000000000000000000000000000000000000000..ccd55ee4d200e19557b0bb95a96b0fe886f143e4 GIT binary patch literal 6202 zcmcIpcT`i^x<^q&42lH^(qu%cKq!Lrk!nEcg7hXGjI>Zf5yU9n2~A1}MWlmtqy&&E zEg&t_&|Bz2Lg$^Cd*5C6&YHPvy+7VrD{E!%oU`{n-~N8(+X>auP^G20O+!IJK?{AX zq(eb*?gH>Jq&^SyZ2Pu(P*8x+L6siqd5^76UVg5(@U3y%>nbSt&Mb25*ViB_cuC}jtYT0QHAumcA&&MU6Gp$7fYN`3X$0r~qE`cq(iCtN8o-qKXye-8Nj@Ra}7`1=qm zFDx$F+Swf)9-7tr4%v&b$#}myJwBM3nHjHinaft!yoAh!eyfe!auA5V%4TKu8Il^; ze(gw^OxodP9q%|>U%V%{#w(RSSYlQ4s+1AM86d?6+c4sM}%t*T%XxlZwGmAg@~upmr%}MAxN2)A8h#fxc{_*^bNde z^414CQX4Gkd|v5f+F2JuA8UbCXi-9b*-M*d!Gt3lL6? zM=jDyUt1!Z9yUWVODt1sA03pzZ?)VONdc*5Zb)t$#m{JcvUE!G+t@3p&4z6!2>IFh z_S;&f)ER=kaGnid7yNZA{H)z`DMJ$>3QAdRaNQ((m;Kb^L038q>=I?14=CX(HNX1I zC<{n$Q5cL1gUBCb;m`bNsC*S5Pb9$E%(mj<-Chzrfx}V3&zm3GU}i>o$S1UOPE5V6 z%vd(-hZy^PzWyzn;2E{WN%wZ%BTz%sx9dB$Gt>vTs?SU!PRzqzLlgUk!mj0hOI3oG zC}rOkb17f!YUt8*_L(<`DPpA7)jfcukan(cbyQV434oWO-=;0-B8dqi4I@}C!s(8K za`FIbVMW^2V+E|D(!q%JUt|OMG}M=`>TBYk6Z|iJ_tviy(OKS`-LZu9-F!2nk^ZIf z2K<3qBH^dV!Z;?;sQlnW?f^BcZxyJ~&$}dtJwk@R4-kn{zZ52J&=Xr+>uB4*c=&b4 zR$rli5v_1g!owTJYVT_WDZua_!Uby3(`H@-S=YVyk6HQTt*AMqnV9be$napW)>(B9 zmV)w2D--%48oCjhmdKmGNE)5C3PCg8RpGxH-B0DbUDGk*>B*p88urp1j{I_82zen) z`Q}1?JLp3(HI=UpBw~L*e0jpU#E0AJCryTXbmWAOS>h`PZkUS8!y9Fi^5UCH-HXRL zw5mFq#~CScIyT2a%#}{sHrU$Kh<& zGc%)m|HS{l|L_;X{4Y)Xx7+@-tmEdtX4Jo2=0C^(gBq5L+kRF0k85|N<(%K_I{05~ z^`~Y0J#*iROHNKkqt}T<#NO(&%zp2!>8699OYh#jgN;|Nhn}dO9TJNZ1dJKj+2uV~ z2T@CA4FUe^V-+{Ko>0@$+62d62BF)Ycsehv500s_v9bET=SAQ?= z=&KcD9v(Z(TJ8D9#~W8V4YRJEt?)aqp*>Kse)HK83Ug@>Y8|WAe+i*ZDLa)L;qxqK z6EXU#es#m_bim-nt=lot$DEki{-)fDXlwpWtf)agW6^j?v>Ufm#Y!xEXYuZdva2-K znVyY1#G8XBZ~iNgTcnN@n61OJ?^t~m4&8E)MF)>q6h5F=31(|+$Ps{RwYr(H5Pmm= zu1C>|&Guyln2tZ>v9zc9=$ibdDTh)Ab22-i{ z(rJ8zg7x9bc-GWsDaVQxd*Tl|kPY>lNVIk2;Zrq@x35QuA#?$wu#;v$b0d*$0#Ca; zBPI9E*F)(sh^e+-ZA9m^CjxkKxBZj`@_Ghm1$JL{+bp=ZZlz0UTdXeSx9}Szq!>RV z>!y(Z{x|>9_|LaY8gOFkC@I|lt)$~cSup6T!ut*{62bqa_^TY!#y!77#Z73q@!R$a zc40Z&0oV17pv_nt0jjG{1*(NTy1v5|2at93n`B7D#DQnk?x;XB((5VO#<=P_xRZs- zI02bS4lr=u_4U*i25E1y7MbA+&xn%!%+cb@3|g6wboUa-C&HgF*hv}A&jE~Q2~)EB zvtiK~r@^{XMfA+L+?gyW6YkHSm{j9U*3)GA@*-S0i*L+7v^++Fe{|$F;&_R`c8{c= z`)nleuru9QCEn1J$Ut2>EEJ|G~WIs~5gTY|!d4Z?#$L_blT>XB*J~@DcFPQczB~?E8 z_m1WqgQ5JY&?kdX9ehJ2U$Q?}Lv9Rb?PoS1b*~agKHsosu`Lj*BT3ONn$>3k{*{YY zsZzP(cEq5Gnm|kuwCp2-;zP2|A2m=(`|spI$h-=obTz^OR###U^u{uyGv_vf{i55BXE&|=si zzY=1Ob(Wikqr3qb9@#=EpY$;hf~#G5 zL&S38;TG7jM!f7xE(j?SnV!*ha$&~Mbx>a&WAhX!Yd8#A&3LjNb0QW>2JuJ63X#IW>WOGpw zpoWElf+`9yMnami@cKxZ=fOdo8 zCH?;C4_hnO_@Z~>^77sdAzt(jKy|i)Bgry1#{;3%{pEx^Hli>3xj2-{8aX;`Tk_Cc zwhJCyTmW8eqvVdj=a&kK30cK%(zFPw{LR$IQhpVt&UrgESWnv8N@44p*N$^oc)k=_ z+Bv?|u60QmOGPJ>m}60a0u|FpJCD+@6_I{;e61=lZauevzFBDA;@X(BQ>a_=`_@_V9z!P)oH7yUY7g*8hOlUVGh=*1E);DvRC>UFDJ9Bz+{q8fiBZO zuEq8Mbe~h-*qFtBJhcN6PX38_>UY=v6tSu*5f}G>s(5_df5K#;>=ED!_}FW;N8W5> zux@>0le`Bb09yp!YVJUr%M&sR?L6G3C%lqK%z6NHB)tHZdWlU}pzh+Uq)Z6-09cO2 zrhX}CX^|ykseO!_vQ6u2r0g5DqMIr3Rs9(3^`7(ZLa+zv?P=Qa&zy&F?VjMhJfBvz z&OFr6su+O2%qhxP+-!xPt5S1n)1xcm)d$EiyyR)2w74D;@-fL+FMl8n|E0*m9Q4LC zMqhxcB`+Bma{0-V!M$^9$rY6aMy955QzbSC;%)fqlIS~Z;3}En^2V8O6y5B?H4Xmc zI94?C(c9c5xJGZhe8NP=B;NXr8IU?R;o_OOwItsh3A54uuK|pKfTpwiz5rM6`g164 zeKYB?3%?RVCt%L9J-$F-P`Ds~PcxNKF%86uXnSJ&~y(Xp= zg6s27`NxwyrAgYSYlCC$c}BDgjPTV^a~7lW+Ov@GQI9$o=P)O7V;D}h*tABaiLPSi zJ^TTTBLsnm5O})|UkI$rI=B`aFzz8-1b{iNS!%_V5zj zFgP%BM+%^H&!#5aO9%xIRu9$hsX8{%N)WVaxe8;Iu_m>b;#<-Riu3BNJIx*xa|O)^ zTWB$m*OR2w5lnz~3SFrGWLo!F+m5nXKuWv)#A{%-FR1o~sEb z@#pXCpiji%QGk`EG5a`vw#PDF6FGSCXL*bcH#F}&HD#}jztTGdgnNnw$dED3*hoLvlHcQKhmr)NTl+%h0GTN{0n_dzAzW za7*8NIsirWwtNDnur$U2^-m0-!J$(Hzy<&z#jrM41#pFE5g6pWC(SsNmgd*mk3v)r zIK9q%HsfXT{j%}^*l!x@LXHXnCHUhBAh^|}WqF`TP{!$yOl~QZhk*~gg+0_U0wL7? zKbui@&%_?a9&Q^zVlRU(ADxOij#OL8Z^7|*q??-cvDZyxT*jf11vUQ;8GKVk-Fu8k&k#T{SAhB`TP(al$b4YP%XfG)N zepq~!KBFkm(WuuCV0Yy%oq3iIyN7M3LV3lwWlpH6(*4Z7oB}>WXCcXxZnV^T&1npO ztqk28mr0eY0%P>dpBEX%RFNg^fy2hJ2E)^7B0(JMgN0#Kmwtx~bb3ry%Rj@sAv}Jzyy&TuX+z#P7=asiAki^ zR4zl{P5LU>!JU0q%QY3^=@bl2e|g!SKwERs-tNtg%p+F7NnYmo77x>fL&7ZWnMEr( zkx`*B6~q}?iC%YV<7sw}8V+Up3t2D+}}n5=(?ui2dM+u#7e( z$ikZf2~ZQDa5;LdUaJiof$#Gi8Z8v(;#c#F0FxQFk^!Ut~Rd zYh7n%%+j#6QmwFNO!vMsU#-XTMWl#V-_uk0TfWN9uFqpay{_9EhFG1Uw2RDbEXOd+ zci0$Sx^)q)B{aRr?3f|((^s+{AZfrBe)T2;mCf5#DxlCGtf*M1lK1vRtbu?7o*Rko zji+zL72Z=#o-YrtYTLw4OSpX1KylW~0Wk&~uO?t8H@k-#P8(zxOB|O9jjKL>#yzBR zv2KfCl7>6y+7%j>aR~|v0teO7(np~+2zI4U8_UN<4 zWZ2-9i8>!qIk|wYc!B5>^Mm|itlHD3tnvYVwziAau5&uNy0D4r^ppy>h0fjG-EMPr zO-H%jb6MK6e`OAC*;GLg8 z?YckCtgIwrFgVTNPBCO!+8uuWeC2S4ZR@nss2S4wxcu3%s66H0oS^^tB>Ycj#fMjp a0@r(3Ho-0^3E)2g3aGM%Qjvm1@c#fVgu;vf literal 0 HcmV?d00001 diff --git a/tests/typ/layout/place-nested.typ b/tests/typ/layout/place-nested.typ new file mode 100644 index 000000000..c979176a7 --- /dev/null +++ b/tests/typ/layout/place-nested.typ @@ -0,0 +1,33 @@ +// Test vertical alignment with nested placement. + +--- +#box( + fill: aqua, + width: 30pt, + height: 30pt, + place(bottom, + place(line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 3pt)) + ) +) + +--- +#box( + fill: aqua, + width: 30pt, + height: 30pt, + { + box(fill: yellow, { + [Hello] + place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: red + 2pt)) + }) + place(horizon, line(start: (0pt, 0pt), end: (20pt, 0pt), stroke: green + 3pt)) + } +) + +--- +#box(fill: aqua)[ + #place(bottom + right)[Hi] + Hello World \ + How are \ + you? +]