From b640747116866e5f849b77b4e8e535f48ce1bd7d Mon Sep 17 00:00:00 2001 From: Bastiaan van der Plaat Date: Mon, 16 Oct 2023 17:29:08 +0200 Subject: [PATCH] LibWeb: Add canvas context2d roundRect --- Base/res/html/misc/canvas-path-rect.html | 14 +- Tests/LibWeb/Ref/canvas-path-rect.html | 40 +++++ .../Ref/reference/canvas-path-rect-ref.html | 15 ++ .../reference/images/canvas-path-rect-ref.png | Bin 0 -> 2546 bytes .../LibWeb/HTML/Canvas/CanvasPath.cpp | 160 ++++++++++++++++++ .../Libraries/LibWeb/HTML/Canvas/CanvasPath.h | 2 + .../LibWeb/HTML/Canvas/CanvasPath.idl | 4 +- 7 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 Tests/LibWeb/Ref/canvas-path-rect.html create mode 100644 Tests/LibWeb/Ref/reference/canvas-path-rect-ref.html create mode 100644 Tests/LibWeb/Ref/reference/images/canvas-path-rect-ref.png diff --git a/Base/res/html/misc/canvas-path-rect.html b/Base/res/html/misc/canvas-path-rect.html index a132e52c0c..750f92d589 100644 --- a/Base/res/html/misc/canvas-path-rect.html +++ b/Base/res/html/misc/canvas-path-rect.html @@ -6,7 +6,6 @@ diff --git a/Tests/LibWeb/Ref/canvas-path-rect.html b/Tests/LibWeb/Ref/canvas-path-rect.html new file mode 100644 index 0000000000..e34eda08ca --- /dev/null +++ b/Tests/LibWeb/Ref/canvas-path-rect.html @@ -0,0 +1,40 @@ + + + + diff --git a/Tests/LibWeb/Ref/reference/canvas-path-rect-ref.html b/Tests/LibWeb/Ref/reference/canvas-path-rect-ref.html new file mode 100644 index 0000000000..9ea38fea2f --- /dev/null +++ b/Tests/LibWeb/Ref/reference/canvas-path-rect-ref.html @@ -0,0 +1,15 @@ + + + diff --git a/Tests/LibWeb/Ref/reference/images/canvas-path-rect-ref.png b/Tests/LibWeb/Ref/reference/images/canvas-path-rect-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..e684a704e12d66daee00abdf329ec675191889d6 GIT binary patch literal 2546 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8Y9Lx+141YNEcQ7z8><#blyA(U|7DK;d(d2{P_&$ zTN#cwFifA$u)mt2zn@`y8AE$J!}>yoqGo+_8)CV!d z$1{}sGK7aS{AXZbXkf^9XYluDNOxk0w`B;oVDL9)aMxw9*I>}sXE0Z0P*-Qrmt&BZ zXHb`9kQZSP7iSRXW8mj!;OAsuXJ=q%VPIfjs8?k;$iTqy!PCVtq~g|_+r5)S-6b0y zMz&aVA9%#?-Xf56a?uX2X$v?*JnsKb-RYK`Id$5}GYrKD7RHLA9{L8N9ws)qQNgbU4%{A{)oL!5DW z!T*Qu89qWKFOEx>ytuH?xt*`JZ-H|=pR85Mi((cKd10aRd}WA~>@OKdh}4DW999tZ zcJ5$ZvQ}R#o4~p*EIcm=ks5?XZFj5x{^;V%x09b*-LT+vnejt4olt zJ@i*zmi^akcJ}zrS8s zySVm+U&{+jxe?eCOdeHYiCF@IT-vHit=soQ&BAHQyTF-qjKRwsF(t3;em;UL!zbi8G zUzAVl-EF+Z_{;Xw&u(sd$NTr`rb_+xZ>|3_Ztl6y^OpN>$<00N&cB`ZZ_c^r?)=-y zKGN*{wF~<$On+-Fd1|5m=lf^v$jUkOmw$Jk-zPP5!T-IdcfZ~>&(?B(&I#xJuTF3N zdi&hGdGU3&YW%-K{jdLj^XBHAPZy5wi1{CFU;pjZXV0e#`wJufr|+*RGhAr%<>Sv^ z^}nue?%waC%3o^}yWhKA`=_Djh5fsB*FEPuzkEV_-Nw58@8stFe|h5RLiuaiCGS7{ z`TFtH^NaV_Uw`*+>L2mP9H$r8SMQFG|9AO%eBWcO{(lvF8{f&-{r>gWV%Dn{@%N+i zS7`sdFWXro`?qNC&2xOu=f92H*Y_{$?t;z7pYM)4fBmBT`smVcPm<)xUO#PmL5Uo8y01dX~(KuUogS zirIFn#4yPG{>AAZQ}x_rFHSp`ZO)$(saiJ2S}1p+{*B(vkH74#yIwi&X&BB!# z-cOhQoc(C^nZC>43%hJy9A9|RKV{}y3)dIsjc2osBh#$?1b5}MyWXFey6thu!uWRA z{S%Mp9DnIFeeQ&3wv}hf=9qn{)SXjVtLJ~S?{cf$C)Ts&Kj*%+NPV#>`jh#FeU0jl z?{?gI!Qc4r?WV5^8*hD9IUze;I`*BJciz5?3ZW-`r~CEFB9vX zpYLTC-(4;^rTlp17g4L#;-35d6>Y!WcezXNb6nz+t#_>}&y~%IE15X^ZPJPIbx%vT zJ^r$&`;4DinCg9td7f|7&KsWc8FBaD8G`*4+E!T7Vy~sq(s`|L& z#hccAUFYr<`jq_WOI~!iTt--S-{ToCKFA(-X@75_VpV%w@#0HszD~dUg&tp={hjU= zdVJZYuK3Gx9YTdesNm-}tou6l74(4=UMjS^ z7y2{j%aNZM-&bF{sI0SdT1H>Tz84nH0+Uiob{6nePLYtRz1Sk>=;$_G@Yh5>*XedY zC;GYW&)q#Cw)5437n4k5cg*tQmsqse`&!XKsg8FWUQ8-oczbS-Ubo^K6+`EGo!iBV zQk|bJyim%#RN#6&s4~BM;r-BCOUaWum*z(`JXZUw5v^STX|IU=bzr?xz zcFLSJYWD;GB^|r)Kgh~&SLk2qyBGe4T6ygf{q3-1Vf_+UE2$ce)4C?sVNJYjY|^R_|9Btl(aH%W>0%@fJb%) ciGuqXau418M_#y85}Sb4q9e0GAZi{{R30 literal 0 HcmV?d00001 diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp index 7f4c4534ff..9c46fac494 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp @@ -213,4 +213,164 @@ void CanvasPath::rect(double x, double y, double w, double h) m_path.move_to(transform.map(Gfx::FloatPoint { x, y })); } +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect +WebIDL::ExceptionOr CanvasPath::round_rect(double x, double y, double w, double h, Variant>> radii) +{ + using Radius = Variant; + + // 1. If any of x, y, w, or h are infinite or NaN, then return. + if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h)) + return {}; + + // 2. If radii is an unrestricted double or DOMPointInit, then set radii to « radii ». + if (radii.has() || radii.has()) { + Vector radii_list; + if (radii.has()) + radii_list.append(radii.get()); + else + radii_list.append(radii.get()); + radii = radii_list; + } + + // 3. If radii is not a list of size one, two, three, or four, then throw a RangeError. + if (radii.get>().is_empty() || radii.get>().size() > 4) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Can have between 1 and 4 radii"sv }; + + // 4. Let normalizedRadii be an empty list. + Vector normalized_radii; + + // 5. For each radius of radii: + for (auto const& radius : radii.get>()) { + // 5.1. If radius is a DOMPointInit: + if (radius.has()) { + auto const& radius_as_dom_point = radius.get(); + + // 5.1.1. If radius["x"] or radius["y"] is infinite or NaN, then return. + if (!isfinite(radius_as_dom_point.x) || !isfinite(radius_as_dom_point.y)) + return {}; + + // 5.1.2. If radius["x"] or radius["y"] is negative, then throw a RangeError. + if (radius_as_dom_point.x < 0 || radius_as_dom_point.y < 0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Radius can't be negative"sv }; + + // 5.1.3. Otherwise, append radius to normalizedRadii. + normalized_radii.append(radius_as_dom_point); + } + + // 5.2. If radius is a unrestricted double: + if (radius.has()) { + auto radius_as_double = radius.get(); + + // 5.2.1. If radius is infinite or NaN, then return. + if (!isfinite(radius_as_double)) + return {}; + + // 5.2.2. If radius is negative, then throw a RangeError. + if (radius_as_double < 0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Radius can't be negative"sv }; + + // 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to normalizedRadii. + normalized_radii.append(Geometry::DOMPointInit { radius_as_double, radius_as_double }); + } + } + + // 6. Let upperLeft, upperRight, lowerRight, and lowerLeft be null. + Geometry::DOMPointInit upper_left {}; + Geometry::DOMPointInit upper_right {}; + Geometry::DOMPointInit lower_right {}; + Geometry::DOMPointInit lower_left {}; + + // 7. If normalizedRadii's size is 4, then set upperLeft to normalizedRadii[0], set upperRight to normalizedRadii[1], set lowerRight to normalizedRadii[2], and set lowerLeft to normalizedRadii[3]. + if (normalized_radii.size() == 4) { + upper_left = normalized_radii.at(0); + upper_right = normalized_radii.at(1); + lower_right = normalized_radii.at(2); + lower_left = normalized_radii.at(3); + } + + // 8. If normalizedRadii's size is 3, then set upperLeft to normalizedRadii[0], set upperRight and lowerLeft to normalizedRadii[1], and set lowerRight to normalizedRadii[2]. + if (normalized_radii.size() == 3) { + upper_left = normalized_radii.at(0); + upper_right = lower_left = normalized_radii.at(1); + lower_right = normalized_radii.at(2); + } + + // 9. If normalizedRadii's size is 2, then set upperLeft and lowerRight to normalizedRadii[0] and set upperRight and lowerLeft to normalizedRadii[1]. + if (normalized_radii.size() == 2) { + upper_left = lower_right = normalized_radii.at(0); + upper_right = lower_left = normalized_radii.at(1); + } + + // 10. If normalizedRadii's size is 1, then set upperLeft, upperRight, lowerRight, and lowerLeft to normalizedRadii[0]. + if (normalized_radii.size() == 1) + upper_left = upper_right = lower_right = lower_left = normalized_radii.at(0); + + // 11. Corner curves must not overlap. Scale all radii to prevent this: + // 11.1. Let top be upperLeft["x"] + upperRight["x"]. + double top = upper_left.x + upper_right.x; + + // 11.2. Let right be upperRight["y"] + lowerRight["y"]. + double right = upper_right.y + lower_right.y; + + // 11.3. Let bottom be lowerRight["x"] + lowerLeft["x"]. + double bottom = lower_right.x + lower_left.x; + + // 11.4. Let left be upperLeft["y"] + lowerLeft["y"]. + double left = upper_left.y + lower_left.y; + + // 11.5. Let scale be the minimum value of the ratios w / top, h / right, w / bottom, h / left. + double scale = AK::min(AK::min(w / top, h / right), AK::min(w / bottom, h / left)); + + // 11.6. If scale is less than 1, then set the x and y members of upperLeft, upperRight, lowerLeft, and lowerRight to their current values multiplied by scale. + if (scale < 1) { + upper_left.x *= scale; + upper_left.y *= scale; + upper_right.x *= scale; + upper_right.y *= scale; + lower_left.x *= scale; + lower_left.y *= scale; + lower_right.x *= scale; + lower_right.y *= scale; + } + + // 12. Create a new subpath: + auto transform = active_transform(); + bool large_arc = false; + bool sweep = true; + + // 12.1. Move to the point (x + upperLeft["x"], y). + m_path.move_to(transform.map(Gfx::FloatPoint { x + upper_left.x, y })); + + // 12.2. Draw a straight line to the point (x + w − upperRight["x"], y). + m_path.line_to(transform.map(Gfx::FloatPoint { x + w - upper_right.x, y })); + + // 12.3. Draw an arc to the point (x + w, y + upperRight["y"]). + m_path.elliptical_arc_to(transform.map(Gfx::FloatPoint { x + w, y + upper_right.y }), { upper_right.x, upper_right.y }, transform.rotation(), large_arc, sweep); + + // 12.4. Draw a straight line to the point (x + w, y + h − lowerRight["y"]). + m_path.line_to(transform.map(Gfx::FloatPoint { x + w, y + h - lower_right.y })); + + // 12.5. Draw an arc to the point (x + w − lowerRight["x"], y + h). + m_path.elliptical_arc_to(transform.map(Gfx::FloatPoint { x + w - lower_right.x, y + h }), { lower_right.x, lower_right.y }, transform.rotation(), large_arc, sweep); + + // 12.6. Draw a straight line to the point (x + lowerLeft["x"], y + h). + m_path.line_to(transform.map(Gfx::FloatPoint { x + lower_left.x, y + h })); + + // 12.7. Draw an arc to the point (x, y + h − lowerLeft["y"]). + m_path.elliptical_arc_to(transform.map(Gfx::FloatPoint { x, y + h - lower_left.y }), { lower_left.x, lower_left.y }, transform.rotation(), large_arc, sweep); + + // 12.8. Draw a straight line to the point (x, y + upperLeft["y"]). + m_path.line_to(transform.map(Gfx::FloatPoint { x, y + upper_left.y })); + + // 12.9. Draw an arc to the point (x + upperLeft["x"], y). + m_path.elliptical_arc_to(transform.map(Gfx::FloatPoint { x + upper_left.x, y }), { upper_left.x, upper_left.y }, transform.rotation(), large_arc, sweep); + + // 13. Mark the subpath as closed. + m_path.close(); + + // 14. Create a new subpath with the point (x, y) as the only point in the subpath. + m_path.move_to(transform.map(Gfx::FloatPoint { x, y })); + return {}; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.h b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.h index 3285aa5fce..93c7affeea 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.h +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -25,6 +26,7 @@ public: void bezier_curve_to(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y); WebIDL::ExceptionOr arc_to(double x1, double y1, double x2, double y2, double radius); void rect(double x, double y, double w, double h); + WebIDL::ExceptionOr round_rect(double x, double y, double w, double h, Variant>> radii = { 0 }); WebIDL::ExceptionOr arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise); WebIDL::ExceptionOr ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise); diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.idl b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.idl index a0f490fab0..99aeea4210 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.idl +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.idl @@ -1,3 +1,5 @@ +#import + // https://html.spec.whatwg.org/multipage/canvas.html#canvaspath interface mixin CanvasPath { undefined closePath(); @@ -7,7 +9,7 @@ interface mixin CanvasPath { undefined bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y); undefined arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius); undefined rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); - // FIXME: undefined roundRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>) radii = 0); + undefined roundRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>) radii = 0); undefined arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false); undefined ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false); };