From 83b6bc4ccbc96179af3709b1b9083805981897b9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 9 Oct 2024 02:57:57 +0200 Subject: [PATCH] LibWeb: Don't allow SVG boxes to create a stacking context Prior to this change, SVGs were following the CSS painting order, which means SVG boxes could have established stacking context and be sorted by z-index. There is a section in the spec that defines what kind of SVG boxes should create a stacking context https://www.w3.org/TR/SVG2/render.html#EstablishingStackingContex Although this spec is marked as a draft and rendering order described in this spec does not match what other engines do. This spec issue comment has a good summary of what other engines actually do regarding painting order https://github.com/w3c/svgwg/issues/264#issuecomment-246432360 "as long as you're relying solely on the default z-index (which SVG1 does, by definition), nothing ever changes order when you apply opacity/filter/etc". This change aligns our implementation with other engines by forbidding SVGs to create a formatting context and painting them in order they are defined in tree tree. --- ...uld-not-affect-svg-painting-order-ref.html | 22 ++++ ...-should-not-affect-svg-painting-order.html | 25 +++++ .../images/css-transform-box-ref.png | Bin 4062 -> 6384 bytes .../images/svg-foreign-object-mask-ref.png | Bin 1491 -> 4040 bytes Userland/Libraries/LibWeb/Layout/Node.cpp | 3 + Userland/Libraries/LibWeb/Painting/Command.h | 37 ++++-- .../Libraries/LibWeb/Painting/DisplayList.cpp | 3 + .../Libraries/LibWeb/Painting/DisplayList.h | 3 + .../LibWeb/Painting/DisplayListPlayerSkia.cpp | 105 +++++++++++------- .../LibWeb/Painting/DisplayListPlayerSkia.h | 3 + .../LibWeb/Painting/DisplayListRecorder.cpp | 24 +++- .../LibWeb/Painting/DisplayListRecorder.h | 5 +- .../Libraries/LibWeb/Painting/SVGMaskable.cpp | 4 +- .../LibWeb/Painting/SVGSVGPaintable.cpp | 65 +++++++++++ .../LibWeb/Painting/SVGSVGPaintable.h | 2 + .../LibWeb/Painting/StackingContext.cpp | 65 +++++++---- .../LibWeb/Painting/StackingContext.h | 1 + 17 files changed, 296 insertions(+), 71 deletions(-) create mode 100644 Tests/LibWeb/Ref/reference/z-index-should-not-affect-svg-painting-order-ref.html create mode 100644 Tests/LibWeb/Ref/z-index-should-not-affect-svg-painting-order.html diff --git a/Tests/LibWeb/Ref/reference/z-index-should-not-affect-svg-painting-order-ref.html b/Tests/LibWeb/Ref/reference/z-index-should-not-affect-svg-painting-order-ref.html new file mode 100644 index 00000000000..f79aa40d8b5 --- /dev/null +++ b/Tests/LibWeb/Ref/reference/z-index-should-not-affect-svg-painting-order-ref.html @@ -0,0 +1,22 @@ + + + + + + diff --git a/Tests/LibWeb/Ref/z-index-should-not-affect-svg-painting-order.html b/Tests/LibWeb/Ref/z-index-should-not-affect-svg-painting-order.html new file mode 100644 index 00000000000..47c50971174 --- /dev/null +++ b/Tests/LibWeb/Ref/z-index-should-not-affect-svg-painting-order.html @@ -0,0 +1,25 @@ + + + + + + + diff --git a/Tests/LibWeb/Screenshot/images/css-transform-box-ref.png b/Tests/LibWeb/Screenshot/images/css-transform-box-ref.png index f66cc990c845d8c403f2c9e4e7059793c2ead33f..73be241ee7cd31b6833101fa92733362883e2211 100644 GIT binary patch literal 6384 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU{>N_V_;yIwqtoR1B1kNPZ!6KiaBrZ+GdBm zlt2FP{)~@|EWQG&9Sb7XMBHT2S(JZ3*43Ju`|Y+3n=XF3|4V3%E4Q?3`av&&5P_I0 zUK=*@E-}!uvp=&x;IYc;87Ggu@0|I%snN2y?{L4yo4TU=#mla~{#wP#z#y<;)k{VO zhK7fV4h#$p5{yg?3`aNw7#I}X8W1);AW$p|N5!2RCFWhI8U1pwe_4U_Ta*C_2W|`VDF)-A!aCCqB z`1ts*FE4}FMr~c?=H@m_j-8?5euHj=&aGZ)^Q^P8Ot0o`fBod-WZ!;fh6DKrxK0S{ zs`{E0yQ5%XT3T9GRn@L{_x47w4qyN8-Bxk^xHYe@uMgjSciqH^6Rk>Lg}lADclRp0 z^$j(p&(F<0@rHrn&pnnC0<&h$Tz)&2SL z=JVIr*RRjBt$y|JaC_*ot?e4_V>42+j662ZL3N$+gTVA zTsJWpwLHr|SoQ5qhSClr1$FPSeYz`!7Oi0g#FEO~{w ze#_6zu?&8FZSCr<+1Jli*8KXia$DYAtLnG2&GXadM>8@qBm^*}aGd3zaCWx&^(RlB zl&^o=-?K`-p3S9!fnkHdx3fG89k(THjaqwaYxeaw8~GP9Gkl2uvHzIjmbA08ERTN< zw|pioFF(Kb-hS5A5UZUUcozQDRzGErp z6{0tE>@Ixlwl;F}vRiM<=Gs(ldRfT8z_E$xP*d#Qs;zo4I|7W2jZJNBW6R&)TYGhN zc>VWV-QxP!zP`S``flF!6DK_8T9vLUdw(xBbbh$QzFo!7{Zh&p7^bl^9%?#!=FF9) z-qX|ii^gI`uksh zc6N6C%{vSXI`T{{tkK)^?#_8UT|fSxfrDA@EtBUz?(Qz%eE$3U`|Yb>De}3>8!(Tyxzt#3=I2O84oqB zjon>#?&Im{`t=oW|1i9aXRQ4A$kn#?*A`I0_4U=&t*NJ{EtM5vXh=OByCGtOBU4IA zNy)jw`i47uDmQP-zaMvdTkh(`?)`diConMRWQRFKZ1@?yVV-Su*!uYW>%PCgf4!Yw z{?*}j{>|s-+t=GYzx7Wy;%|J`j;yI{Yzz`=Of9Ul_cIkeIMB%Y|K>l@3I7;A_C97{ zIDB;d{KySI8I3yrF}zG?WnoCz5y6;v@ENU;pOrcD3K0SqUW9{a0V-hC=j)sI_5l zZ*9%~cBB0QBf|_j^x0cbL7l0t3TgS5Q!fJJg9O{Ec_Z z;1*+G2(@EA#K5+f``SY1_N@2!_L>?Q1zDHBn^SvNOoyT2p{hf~hOpJ4{_|{BMsLpx z-G2M;%dhHwbAG&g4Rf7>b=jL0veso+HYOj>N=r)%e7~{q@v&DWR;ISLf9LOuW?^W! z@4x!{=H5Rw*EjrqR(IpyOp~wQt#j}fx{?AAE%gPKJdj6O=ton1Dx#8$X zZyknbn_0OBL!S%OIoY4LJ^K5*yV+GeqKhxTymI7-%jIMSh68I-#TA~Pn+p<7uBnLu zH7(cNXks0REPKnBf5MmN^FxJ&ef>ZBZjb(Y-rm=rk%7U|SyW-J-_`hfQ`@8F!NE){ z3=I#HKm{9?&TGxpwX14FO}{}bNzxwcp z3j@OjrAWq=tE_gPC}vX6Cth%V*e^P(_PG&I%1|7-m5C5+E{FIS_;g1oh)TC?Y8x*@i z+oMfQuYM7^e`ojgFDwiU6HG%GKYhPHU!H-%!8qq2(?E3h-!5Z+as6dUR!z;Gmxf_I zot>SaUU7rvwAh9ctGUaLGcYjJ2}W-il0y|+X3H&z-+p^-_4jwCw#T|IzAVW~N?P>N zkbxoL+9vJ^#m~=ygbnTN^c)x%BEkkP20DIyKEJB7bJ_AA%Wkjz^?d%aq;&9~jX0+i7b zSUf`C)_utc1m&!Q(7~yYtBddM7BA&vV36oxSh;HRa)13-G7Jm`piCIfC%>-w`ns!c zPoA75DZ6&&#f!UaEhE|2#}>2eFfc?o&8+oxLd-jb_Xx<_w+a9w{ryNZQ@;fLx#b_ll2mP>H_ zcl`^)=RfN~a{&*&r?3(;?4Ra%pZU{|kB{SR*=zsoVPuF{`H%k;OO}tmo97$`1_lOC LS3j3^P6N_W?*1gm=+$$z`!soHNrE^*Ox(yfq{X8 zfsIj!0VK)Dz{tSBD8<0azyM+~gfU9P*>Q{-P&G^p42*^hObiSRq6`cSY3-Q|EKv0| z3=rS}<%8*f%!<^U+{BXnB87mWd=P7{9O-#x!EwNQn0$HR00}ywnR%9Y<$}5I?D>*+`A0(r1 zsAr&$OaOv7OpNCu(}92gKM+JIbO1&+i>m(1MMyyDFKJUa^m zBO9>WFk}$EL(&<6tkVElCz1@3&WhXuE9atAP@)OR&(E;~TaT88Z1mApp&8(tT9TNO zSd!?HSdwaIWME{XYha;kXc=N?Y-Ma@Wo)T!U}R-r05JwdEs{B)AhC)FN-fUMDFsEO zow<>Lfq{X(A&9im$B;(XiqILFmsw(G1l58jjIJvZA&<>MWNCD*{zaLoc_oQpw;Doi zLl%Rox6ucsNTd`Bj&X=+kc*ogmyJF+qkuAv9hXS&ncEBuj12)kA+Df|W^Byh?ah#$ z&QM>^(7?bjeLBPc|LW@M`uh5YhK9z*4CdzM_V)IUj*iaG&Mq!4ZfH>!i9?$ zFJ7`_$_lB#*G^{ZQ8VX^X6^awr$_OeaDU+ zyLRo`w{PG6{re9cI&{R^`pA(ZM~@yoZeel!`0>-HPoFt+=G?h+7cN}5bm`LN%a^ZS zy?X81wVO9@-nw<`_U+qu?%cV5|Nesq4<0^z`1tYTCr_R{efsp-vu7_}ym~^j;+;yAUwgYbN9?v{{@_C$NruX z5>{L6=aMs_&gcE!rCWoR_?<4$+#i4Gn}e0e`G@Z70S_I&L~GeT z)4Zz{2BxW@0v?NQ@I5Kn)x78Dgnqp!F%}_hlf3ztA68h!)CsA*ixy)MlI~pZXqIig z)obeQuK6Z)uTx(SUmQCL~Li77qOa7Rm5H0?4ccYJrvo*&$$0)A&9(|u@ z=hYf-I0|@tKJ@tAsbBAYlojQ(J1XRE-kiAiv26OX?Ob83ja&M|Wb<`(B7|d>G(%$( zM{uJ=FeCKUNM zAh{h=2qVohF|sf*H8ys#{eI{*^N+J$RQeludIlQ^vJvL`zL^@<^S$ibS~^lePe&?P(vct{9X&kpbhC7}?M`uyvI*Z@ ze|?ED?CLuDi`iX=Wz+X!$@$)G@1?iTaws_R>y^^pmv=N96(;O;UsF{bbM}sUheO}5 zj;+!sT#U9fKag6o^uy5~hf@CK2zAJ^G@9t!yj!>CeP6!%_c8&NOJ>&6*R1aqrd6#@ z=oRqzopJT!wL?v!hg_|o>4E_qhOqDhhdvge8BFk4gvB^K78~E3yzt&rKav*_FHo~# zN;z0CbYl^M8;B?+6a;?o?Q|_>D1(+?aJQnk7h(;D5JVWoW(=W*h%MF?d3r|AW@(-X zQn;p^{HOP62P@PX4Al+_7(xt<2M#WhoA)`V_QU$>r)@jaJ(wb6CI7tc<6?2wf!K`^ z7%WL#XpVjGr1RI0{N;5PKP>;}%uM#nP%z#v^JmWe|7OQ6k{TUA_CC0ve`$8sRId)D zrHT{sw4NS|Ev?ounrV?#%DLn*Ba4y%3lopRgp7pn-wFa2e!u?j>N{3DTfDI*Sy)Qa zupeX!ay|jabAtnh5X0_YKUb<{^Yho8%ML!{$(xdV_|HR7fIu}M+>PlgCJvT;&ieM> zo(C0vSYLhfim^`P9V1_hBt~d(BH|arFmN76L_64QLspoEn-@;6PpIBz_v8BBqQiHL zRT|f<upWMF#v zA^bQq&(weWySIL9&;P76;qTs9qh6weS@-XskjY;BrB{6n)lDbNT3;u#5fsUY-~~Gy zWF3akf$uM#9{(4k#rLmNe|A-y>V)WJobEbk%CH!QRfzI7!h;_jzy1qs^-^`2cuBy+ ze5(J)SPR*iJ~A73v0VDdz$D1Q#K|((2AKc= diff --git a/Tests/LibWeb/Screenshot/images/svg-foreign-object-mask-ref.png b/Tests/LibWeb/Screenshot/images/svg-foreign-object-mask-ref.png index 4e86a598539cb15d325ac1761d28c674dad5b3c3..ee930bd3de8925b271e63e25329d861276475e17 100644 GIT binary patch literal 4040 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>iV_;yIRn}C%z`)<{>EaktG3V`F`|P=2 zr5iq4tGjF8YKmf=yQP_*z`)?( z*1*8PAj!zYz>vbi!N4#<5H2`#E8B^0-@fJK=AM1Me!rc#Ud)W6Nrtm$&n|v=sC8$_ z%S#}cUTO1dmO=~+6-_xdui81nS6_|UTUEO6*Q?c$Ys1`soqhJ~SCdm%>$4*t9p<;6BVYI9U?v9x!<@q7d=vKn`<1O1yUS$v?%mn3Po7SXpBKG7@9fXd z&#za-GB7;&ViDHx=KcHg+UxfiZO^}NH!)Q&ZjVL%zn{-_rk@txX~)3up+ffcyye^P z*Zn^7C^_xfx3{-HzuWzO-i@z}3=PL$pI|Kd`|In?n>Q^_#-^n`dvS!4-Pi(EPCo? z#?QczAydV)DQa!n&X{w`b?ZOeFf%httFOPmx{{IM0gvUE`+vWEE0dEqHfCpM-@JG4 z-1Dpq41VVpGJZO1eqW?|&aRsb3`AwIB;Xz8}Q4t1)36>@f=jPkTzbSl`#>~L*x;&#{&#zajkIF62 zGh<-bP}k0Of?d8Q051K)`NjUf-)`^uey{qf-Tw`nHyh{g`}r(+Ce&JpN1o*$`oyNg zz+h3$x0f}0W5k)|^XsDCeB$pKX&3g03jTw9Q?v2!& ze$s}4VL{=N_YFGJPyc+g`Mh7%^#8l7zOMSgz|dg)!b4rbxZa-o)2;0FXW#GtU$=;R z@5f`(n>TMZ?rzOjVPZHCS^R`y=FFS7Z_gHwuPJm~`}_NDeg+1M$8xXYmLE+rG|#^`N51|~;k?RcGu!3r-rd>x^WX3H z&!@-N72XzQVEDssb7}v-e!E{8@9yojzP>KjI?N#_FYnye?Ca0&?XCX&@wmMGDJ}+v z1Hw;koNxFuTiQJD%=P$sTl@ciKEHYQ?%dw$@6R4QNSIgsZs*R5kB|2J|M&az&*$^c zTfg74dEG>izLd3~tob3|rh<_HBZp$mwxfb*<=AL|j0On2QX5SSqlsZOG2m+QjpmNg o+%cLvMsvq#Qxn<|epY|>hQYfziOVV(7#J8lUHx3vIVCg!0E`WKi2wiq literal 1491 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>iU|?X_FMD?l0|Vo}yTH-Ap)DDU zZf@Kt@O-cGpAUwcXR9t`(P9;DaBxsi5D?(tU}0hUuREczY{%7%vc657wE`#hEZQ$) zx8@tSu;K)N^S1`eYqOPEjBM@n4w;AZ39@{8m#MF_;eU!6OVG5Rei0ew+l84l{heEj ztraIEt}rnXS;;l+7&pN)81dU1jODtBrdVJ&fJB}qaH)HM`-)1LpV)pTC zi|nq=JKN%LFF!K<{_h`Ox52!PA%x^Mn71*6 zV49I)9771E87WGL5yA*F1py2pj4*Rhz!1U+v(aFL7mLs+!3Z mask_bitmap; - Gfx::Bitmap::MaskKind mask_kind; -}; - struct PushStackingContext { float opacity; // The bounding box of the source paintable (pre-transform). @@ -117,7 +112,6 @@ struct PushStackingContext { // A translation to be applied after the stacking context has been transformed. Gfx::IntPoint post_transform_translation; StackingContextTransform transform; - Optional mask = {}; Optional clip_path = {}; void translate_by(Gfx::IntPoint const& offset) @@ -387,6 +381,32 @@ struct PaintScrollBar { } }; +struct ApplyOpacity { + float opacity; +}; + +struct ApplyTransform { + Gfx::FloatPoint origin; + Gfx::FloatMatrix4x4 matrix; + Gfx::IntPoint post_transform_translation; + + void translate_by(Gfx::IntPoint const& offset) + { + origin.translate_by(offset.to_type()); + } +}; + +struct ApplyMaskBitmap { + Gfx::IntPoint origin; + NonnullRefPtr bitmap; + Gfx::Bitmap::MaskKind kind; + + void translate_by(Gfx::IntPoint const& offset) + { + origin.translate_by(offset); + } +}; + using Command = Variant< DrawGlyphRun, FillRect, @@ -418,6 +438,9 @@ using Command = Variant< AddRoundedRectClip, AddMask, PaintNestedDisplayList, - PaintScrollBar>; + PaintScrollBar, + ApplyOpacity, + ApplyTransform, + ApplyMaskBitmap>; } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayList.cpp b/Userland/Libraries/LibWeb/Painting/DisplayList.cpp index be641277ee5..0dfddbcd6ff 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayList.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayList.cpp @@ -99,6 +99,9 @@ void DisplayListPlayer::execute(DisplayList& display_list) else HANDLE_COMMAND(AddMask, add_mask) else HANDLE_COMMAND(PaintScrollBar, paint_scrollbar) else HANDLE_COMMAND(PaintNestedDisplayList, paint_nested_display_list) + else HANDLE_COMMAND(ApplyOpacity, apply_opacity) + else HANDLE_COMMAND(ApplyTransform, apply_transform) + else HANDLE_COMMAND(ApplyMaskBitmap, apply_mask_bitmap) else VERIFY_NOT_REACHED(); // clang-format on } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayList.h b/Userland/Libraries/LibWeb/Painting/DisplayList.h index 3ffbb504961..aee3e4849e2 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayList.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayList.h @@ -73,6 +73,9 @@ private: virtual void add_mask(AddMask const&) = 0; virtual void paint_nested_display_list(PaintNestedDisplayList const&) = 0; virtual void paint_scrollbar(PaintScrollBar const&) = 0; + virtual void apply_opacity(ApplyOpacity const&) = 0; + virtual void apply_transform(ApplyTransform const&) = 0; + virtual void apply_mask_bitmap(ApplyMaskBitmap const&) = 0; virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0; }; diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index 5489ab6ceb5..bcd496751c5 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -452,47 +452,6 @@ void DisplayListPlayerSkia::push_stacking_context(PushStackingContext const& com canvas.clipPath(to_skia_path(command.clip_path.value()), true); } - if (command.mask.has_value()) { - auto sk_bitmap = to_skia_bitmap(*command.mask.value().mask_bitmap); - auto mask_image = SkImages::RasterFromBitmap(sk_bitmap); - - char const* sksl_shader = nullptr; - if (command.mask->mask_kind == Gfx::Bitmap::MaskKind::Luminance) { - sksl_shader = R"( - uniform shader mask_image; - half4 main(float2 coord) { - half4 color = mask_image.eval(coord); - half luminance = 0.2126 * color.b + 0.7152 * color.g + 0.0722 * color.r; - return half4(0.0, 0.0, 0.0, color.a * luminance); - } - )"; - } else if (command.mask->mask_kind == Gfx::Bitmap::MaskKind::Alpha) { - sksl_shader = R"( - uniform shader mask_image; - half4 main(float2 coord) { - half4 color = mask_image.eval(coord); - return half4(0.0, 0.0, 0.0, color.a); - } - )"; - } else { - VERIFY_NOT_REACHED(); - } - - auto [effect, error] = SkRuntimeEffect::MakeForShader(SkString(sksl_shader)); - if (!effect) { - dbgln("SkSL error: {}", error.c_str()); - VERIFY_NOT_REACHED(); - } - - SkMatrix mask_matrix; - auto mask_position = command.source_paintable_rect.location(); - mask_matrix.setTranslate(mask_position.x(), mask_position.y()); - - SkRuntimeShaderBuilder builder(effect); - builder.child("mask_image") = mask_image->makeShader(SkSamplingOptions(), mask_matrix); - canvas.clipShader(builder.makeShader()); - } - canvas.concat(matrix); } @@ -1283,6 +1242,70 @@ void DisplayListPlayerSkia::paint_scrollbar(PaintScrollBar const& command) canvas.drawRRect(rrect, stroke_paint); } +void DisplayListPlayerSkia::apply_opacity(ApplyOpacity const& command) +{ + auto& canvas = surface().canvas(); + SkPaint paint; + paint.setAlphaf(command.opacity); + canvas.saveLayer(nullptr, &paint); +} + +void DisplayListPlayerSkia::apply_transform(ApplyTransform const& command) +{ + auto affine_transform = Gfx::extract_2d_affine_transform(command.matrix); + auto new_transform = Gfx::AffineTransform {} + .set_translation(command.post_transform_translation.to_type()) + .translate(command.origin) + .multiply(affine_transform) + .translate(-command.origin); + auto matrix = to_skia_matrix(new_transform); + surface().canvas().concat(matrix); +} + +void DisplayListPlayerSkia::apply_mask_bitmap(ApplyMaskBitmap const& command) +{ + auto& canvas = surface().canvas(); + + auto sk_bitmap = to_skia_bitmap(*command.bitmap); + auto mask_image = SkImages::RasterFromBitmap(sk_bitmap); + + char const* sksl_shader = nullptr; + if (command.kind == Gfx::Bitmap::MaskKind::Luminance) { + sksl_shader = R"( + uniform shader mask_image; + half4 main(float2 coord) { + half4 color = mask_image.eval(coord); + half luminance = 0.2126 * color.b + 0.7152 * color.g + 0.0722 * color.r; + return half4(0.0, 0.0, 0.0, color.a * luminance); + } + )"; + } else if (command.kind == Gfx::Bitmap::MaskKind::Alpha) { + sksl_shader = R"( + uniform shader mask_image; + half4 main(float2 coord) { + half4 color = mask_image.eval(coord); + return half4(0.0, 0.0, 0.0, color.a); + } + )"; + } else { + VERIFY_NOT_REACHED(); + } + + auto [effect, error] = SkRuntimeEffect::MakeForShader(SkString(sksl_shader)); + if (!effect) { + dbgln("SkSL error: {}", error.c_str()); + VERIFY_NOT_REACHED(); + } + + SkMatrix mask_matrix; + auto mask_position = command.origin; + mask_matrix.setTranslate(mask_position.x(), mask_position.y()); + + SkRuntimeShaderBuilder builder(effect); + builder.child("mask_image") = mask_image->makeShader(SkSamplingOptions(), mask_matrix); + canvas.clipShader(builder.makeShader()); +} + bool DisplayListPlayerSkia::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const { return surface().canvas().quickReject(to_skia_rect(rect)); diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h index e13bafc174f..03f7bb267fe 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h @@ -79,6 +79,9 @@ private: void add_mask(AddMask const&) override; void paint_scrollbar(PaintScrollBar const&) override; void paint_nested_display_list(PaintNestedDisplayList const&) override; + void apply_opacity(ApplyOpacity const&) override; + void apply_transform(ApplyTransform const&) override; + void apply_mask_bitmap(ApplyMaskBitmap const&) override; bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override; diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index 6c763a1166e..8a24da42ead 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -306,7 +306,6 @@ void DisplayListRecorder::push_stacking_context(PushStackingContextParams params .origin = params.transform.origin, .matrix = params.transform.matrix, }, - .mask = params.mask, .clip_path = params.clip_path }); m_state_stack.append(State()); } @@ -410,4 +409,27 @@ void DisplayListRecorder::paint_scrollbar(int scroll_frame_id, Gfx::IntRect rect .vertical = vertical }); } +void DisplayListRecorder::apply_opacity(float opacity) +{ + append(ApplyOpacity { .opacity = opacity }); +} + +void DisplayListRecorder::apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix) +{ + append(ApplyTransform { + .origin = origin, + .matrix = matrix, + .post_transform_translation = state().translation.translation().to_rounded(), + }); +} + +void DisplayListRecorder::apply_mask_bitmap(Gfx::IntPoint origin, Gfx::Bitmap const& bitmap, Gfx::Bitmap::MaskKind kind) +{ + append(ApplyMaskBitmap { + .origin = origin, + .bitmap = bitmap, + .kind = kind, + }); +} + } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h index e329e57e88a..60349ae641f 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h @@ -122,7 +122,6 @@ public: bool is_fixed_position; Gfx::IntRect source_paintable_rect; StackingContextTransform transform; - Optional mask = {}; Optional clip_path = {}; }; void push_stacking_context(PushStackingContextParams params); @@ -147,6 +146,10 @@ public: void paint_scrollbar(int scroll_frame_id, Gfx::IntRect, CSSPixelFraction scroll_size, bool vertical); + void apply_opacity(float opacity); + void apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4); + void apply_mask_bitmap(Gfx::IntPoint origin, Gfx::Bitmap const&, Gfx::Bitmap::MaskKind); + DisplayListRecorder(DisplayList&); ~DisplayListRecorder(); diff --git a/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp b/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp index 16875022ede..f52b8daf4ae 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp @@ -18,6 +18,8 @@ namespace Web::Painting { template static T const* first_child_layout_node_of_type(SVG::SVGGraphicsElement const& graphics_element) { + if (!graphics_element.layout_node()) + return nullptr; return graphics_element.layout_node()->first_child_of_type(); } @@ -87,7 +89,7 @@ RefPtr SVGMaskable::calculate_mask_of_svg(PaintContext& context, CS auto paint_context = context.clone(display_list_recorder); paint_context.set_svg_transform(graphics_element.get_transform()); paint_context.set_draw_svg_geometry_for_clip_path(is(paintable)); - StackingContext::paint_node_as_stacking_context(paintable, paint_context); + StackingContext::paint_svg(paint_context, paintable, PaintPhase::Foreground); DisplayListPlayerSkia display_list_player { *mask_bitmap }; display_list_player.execute(display_list); return mask_bitmap; diff --git a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp index 5fbdf919902..3fb691d81e4 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.cpp @@ -43,4 +43,69 @@ void SVGSVGPaintable::after_children_paint(PaintContext& context, PaintPhase pha context.display_list_recorder().restore(); } +static Gfx::FloatMatrix4x4 matrix_with_scaled_translation(Gfx::FloatMatrix4x4 matrix, float scale) +{ + auto* m = matrix.elements(); + m[0][3] *= scale; + m[1][3] *= scale; + m[2][3] *= scale; + return matrix; +} + +void SVGSVGPaintable::paint_descendants(PaintContext& context, PaintableBox const& paintable, PaintPhase phase) +{ + if (phase != PaintPhase::Foreground) + return; + + auto paint_svg_box = [&](auto& svg_box) { + auto const& computed_values = svg_box.computed_values(); + + auto masking_area = svg_box.get_masking_area(); + auto needs_to_save_state = computed_values.opacity() < 1 || svg_box.has_css_transform() || svg_box.get_masking_area().has_value(); + + if (needs_to_save_state) { + context.display_list_recorder().save(); + } + + if (computed_values.opacity() < 1) { + context.display_list_recorder().apply_opacity(computed_values.opacity()); + } + + if (svg_box.has_css_transform()) { + auto transform_matrix = svg_box.transform(); + Gfx::FloatPoint transform_origin = svg_box.transform_origin().template to_type(); + auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel()); + context.display_list_recorder().apply_transform(transform_origin.scaled(to_device_pixels_scale), matrix_with_scaled_translation(transform_matrix, to_device_pixels_scale)); + } + + if (masking_area.has_value()) { + if (masking_area->is_empty()) + return; + auto mask_bitmap = svg_box.calculate_mask(context, *masking_area); + if (mask_bitmap) { + auto source_paintable_rect = context.enclosing_device_rect(*masking_area).template to_type(); + auto origin = source_paintable_rect.location(); + context.display_list_recorder().apply_mask_bitmap(origin, mask_bitmap.release_nonnull(), *svg_box.get_mask_type()); + } + } + + svg_box.before_paint(context, PaintPhase::Foreground); + svg_box.paint(context, PaintPhase::Foreground); + svg_box.after_paint(context, PaintPhase::Foreground); + + paint_descendants(context, svg_box, phase); + + if (needs_to_save_state) { + context.display_list_recorder().restore(); + } + }; + + paintable.before_children_paint(context, PaintPhase::Foreground); + paintable.for_each_child([&](auto& child) { + paint_svg_box(verify_cast(child)); + return IterationDecision::Continue; + }); + paintable.after_children_paint(context, PaintPhase::Foreground); +} + } diff --git a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h index dd0ca04218e..a8697b13523 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/SVGSVGPaintable.h @@ -23,6 +23,8 @@ public: Layout::SVGSVGBox const& layout_box() const; + static void paint_descendants(PaintContext& context, PaintableBox const& paintable, PaintPhase phase); + protected: SVGSVGPaintable(Layout::SVGSVGBox const&); }; diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index 4a6006d3fcc..bd199ac98d0 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -5,19 +5,16 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include -#include #include #include #include -#include #include #include #include #include #include -#include +#include #include #include @@ -81,6 +78,11 @@ static PaintPhase to_paint_phase(StackingContext::StackingContextPaintPhase phas void StackingContext::paint_node_as_stacking_context(Paintable const& paintable, PaintContext& context) { + if (paintable.layout_node().is_svg_svg_box()) { + paint_svg(context, static_cast(paintable), PaintPhase::Foreground); + return; + } + VERIFY(!paintable.layout_node().is_svg_svg_box()); paint_node(paintable, context, PaintPhase::Background); paint_node(paintable, context, PaintPhase::Border); paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBorders); @@ -93,6 +95,18 @@ void StackingContext::paint_node_as_stacking_context(Paintable const& paintable, paint_descendants(context, paintable, StackingContextPaintPhase::FocusAndOverlay); } +void StackingContext::paint_svg(PaintContext& context, PaintableBox const& paintable, PaintPhase phase) +{ + if (phase != PaintPhase::Foreground) + return; + + paintable.apply_clip_overflow_rect(context, PaintPhase::Foreground); + paint_node(paintable, context, PaintPhase::Background); + paint_node(paintable, context, PaintPhase::Border); + SVGSVGPaintable::paint_descendants(context, paintable, phase); + paintable.clear_clip_overflow_rect(context, PaintPhase::Foreground); +} + void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase) { paintable.before_children_paint(context, to_paint_phase(phase)); @@ -101,6 +115,11 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const& auto* stacking_context = child.stacking_context(); auto const& z_index = child.computed_values().z_index(); + if (child.layout_node().is_svg_svg_box()) { + paint_svg(context, static_cast(child), to_paint_phase(phase)); + return IterationDecision::Continue; + } + // NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated // the same way as CSS2 defines for inline-blocks: // "For each one of these, treat the element as if it created a new stacking context, but any positioned @@ -174,6 +193,9 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const& void StackingContext::paint_child(PaintContext& context, StackingContext const& child) { + VERIFY(!child.paintable().layout_node().is_svg_box()); + VERIFY(!child.paintable().layout_node().is_svg_svg_box()); + const_cast(child).set_last_paint_generation_id(context.paint_generation_id()); auto parent_paintable = child.paintable().parent(); @@ -188,6 +210,12 @@ void StackingContext::paint_child(PaintContext& context, StackingContext const& void StackingContext::paint_internal(PaintContext& context) const { + VERIFY(!paintable().layout_node().is_svg_box()); + if (paintable().layout_node().is_svg_svg_box()) { + paint_svg(context, paintable_box(), PaintPhase::Foreground); + return; + } + // For a more elaborate description of the algorithm, see CSS 2.1 Appendix E // Draw the background and borders for the context root (steps 1, 2) paint_node(paintable(), context, PaintPhase::Background); @@ -299,22 +327,6 @@ void StackingContext::paint(PaintContext& context) const }, }; - if (paintable().is_paintable_box()) { - if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) { - if (masking_area->is_empty()) - return; - auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area); - if (mask_bitmap) { - auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type(); - push_stacking_context_params.source_paintable_rect = source_paintable_rect; - push_stacking_context_params.mask = StackingContextMask { - .mask_bitmap = mask_bitmap.release_nonnull(), - .mask_kind = *paintable_box().get_mask_type() - }; - } - } - } - auto const& computed_values = paintable().computed_values(); if (auto clip_path = computed_values.clip_path(); clip_path.has_value() && clip_path->is_basic_shape()) { auto const& masking_area = paintable_box().get_masking_area(); @@ -332,6 +344,19 @@ void StackingContext::paint(PaintContext& context) const if (paintable().is_paintable_box() && paintable_box().scroll_frame_id().has_value()) context.display_list_recorder().set_scroll_frame_id(*paintable_box().scroll_frame_id()); context.display_list_recorder().push_stacking_context(push_stacking_context_params); + + if (paintable().is_paintable_box()) { + if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) { + if (masking_area->is_empty()) + return; + auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area); + if (mask_bitmap) { + auto masking_area_rect = context.enclosing_device_rect(*masking_area).to_type(); + context.display_list_recorder().apply_mask_bitmap(masking_area_rect.location(), mask_bitmap.release_nonnull(), *paintable_box().get_mask_type()); + } + } + } + paint_internal(context); context.display_list_recorder().pop_stacking_context(); if (has_css_transform) diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.h b/Userland/Libraries/LibWeb/Painting/StackingContext.h index 8beb5627eeb..8fb9ecb6e88 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.h +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.h @@ -36,6 +36,7 @@ public: static void paint_node_as_stacking_context(Paintable const&, PaintContext&); static void paint_descendants(PaintContext&, Paintable const&, StackingContextPaintPhase); + static void paint_svg(PaintContext&, PaintableBox const&, PaintPhase); void paint(PaintContext&) const; [[nodiscard]] TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function const& callback) const;