diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.h b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.h new file mode 100644 index 00000000000..63eefb45bda --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, İbrahim UYSAL + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/canvas.html#canvasshadowstyles +template +class CanvasShadowStyles { +public: + ~CanvasShadowStyles() = default; + + virtual float shadow_offset_x() const = 0; + virtual void set_shadow_offset_x(float offsetX) = 0; + + virtual float shadow_offset_y() const = 0; + virtual void set_shadow_offset_y(float offsetY) = 0; + + virtual String shadow_color() const = 0; + virtual void set_shadow_color(String color) = 0; + +protected: + CanvasShadowStyles() = default; + +private: + CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast(*this).drawing_state(); } + CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast(*this).drawing_state(); } +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.idl b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.idl index 47649465982..913a1254528 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.idl +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasShadowStyles.idl @@ -1,8 +1,8 @@ // https://html.spec.whatwg.org/multipage/canvas.html#canvasshadowstyles interface mixin CanvasShadowStyles { // shadows - [FIXME] attribute unrestricted double shadowOffsetX; // (default 0) - [FIXME] attribute unrestricted double shadowOffsetY; // (default 0) + attribute unrestricted double shadowOffsetX; // (default 0) + attribute unrestricted double shadowOffsetY; // (default 0) [FIXME] attribute unrestricted double shadowBlur; // (default 0) - [FIXME] attribute DOMString shadowColor; // (default transparent black) + attribute DOMString shadowColor; // (default transparent black) }; diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h index 1eb24a3b889..3d33e336c65 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h @@ -80,6 +80,9 @@ public: Gfx::AffineTransform transform; FillOrStrokeStyle fill_style { Gfx::Color::Black }; FillOrStrokeStyle stroke_style { Gfx::Color::Black }; + float shadow_offset_x { 0.0f }; + float shadow_offset_y { 0.0f }; + Gfx::Color shadow_color { Gfx::Color::Transparent }; float line_width { 1 }; Vector dash_list; bool image_smoothing_enabled { true }; diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 278cdd14fed..f97d9eba6af 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -262,6 +262,8 @@ void CanvasRenderingContext2D::stroke_internal(Gfx::Path const& path) if (!painter) return; + paint_shadow_for_stroke_internal(path); + auto& state = drawing_state(); if (auto color = state.stroke_style.as_color(); color.has_value()) { @@ -299,6 +301,8 @@ void CanvasRenderingContext2D::fill_internal(Gfx::Path const& path, Gfx::Winding if (!painter) return; + paint_shadow_for_fill_internal(path, winding_rule); + auto path_to_fill = path; path_to_fill.close_all_subpaths(); auto& state = this->drawing_state(); @@ -719,4 +723,95 @@ void CanvasRenderingContext2D::set_global_alpha(float alpha) drawing_state().global_alpha = alpha; } +float CanvasRenderingContext2D::shadow_offset_x() const +{ + return drawing_state().shadow_offset_x; +} + +void CanvasRenderingContext2D::set_shadow_offset_x(float offsetX) +{ + // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowoffsetx + drawing_state().shadow_offset_x = offsetX; +} + +float CanvasRenderingContext2D::shadow_offset_y() const +{ + return drawing_state().shadow_offset_y; +} + +void CanvasRenderingContext2D::set_shadow_offset_y(float offsetY) +{ + // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowoffsety + drawing_state().shadow_offset_y = offsetY; +} + +String CanvasRenderingContext2D::shadow_color() const +{ + // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowcolor + return drawing_state().shadow_color.to_string(Gfx::Color::HTMLCompatibleSerialization::Yes); +} + +void CanvasRenderingContext2D::set_shadow_color(String color) +{ + auto& realm = static_cast(*this).realm(); + + // 1. Let context be this's canvas attribute's value, if that is an element; otherwise null. + auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), color); + + auto style_value = parser.parse_as_css_value(CSS::PropertyID::Color); + + // 2. Let parsedValue be the result of parsing the given value with context if non-null. + if (style_value && style_value->has_color()) { + auto parsedValue = style_value->to_color(OptionalNone()); + + // 4. Set this's shadow color to parsedValue. + drawing_state().shadow_color = parsedValue; + } else { + // 3. If parsedValue is failure, then return. + return; + } +} +void CanvasRenderingContext2D::paint_shadow_for_fill_internal(Gfx::Path const& path, Gfx::WindingRule winding_rule) +{ + auto* painter = this->painter(); + if (!painter) + return; + + auto path_to_fill = path; + path_to_fill.close_all_subpaths(); + + auto& state = this->drawing_state(); + + painter->save(); + + Gfx::AffineTransform transform; + transform.translate(state.shadow_offset_x, state.shadow_offset_y); + painter->set_transform(transform); + painter->fill_path(path_to_fill, state.shadow_color.with_opacity(state.global_alpha), winding_rule); + + painter->restore(); + + did_draw(path_to_fill.bounding_box()); +} + +void CanvasRenderingContext2D::paint_shadow_for_stroke_internal(Gfx::Path const& path) +{ + auto* painter = this->painter(); + if (!painter) + return; + + auto& state = drawing_state(); + + painter->save(); + + Gfx::AffineTransform transform; + transform.translate(state.shadow_offset_x, state.shadow_offset_y); + painter->set_transform(transform); + painter->stroke_path(path, state.shadow_color.with_opacity(state.global_alpha), state.line_width); + + painter->restore(); + + did_draw(path.bounding_box()); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index 8ad9732388c..d39ebef8de0 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ class CanvasRenderingContext2D , public CanvasState , public CanvasTransform , public CanvasFillStrokeStyles + , public CanvasShadowStyles , public CanvasRect , public CanvasDrawPath , public CanvasText @@ -99,6 +101,13 @@ public: virtual float global_alpha() const override; virtual void set_global_alpha(float) override; + virtual float shadow_offset_x() const override; + virtual void set_shadow_offset_x(float) override; + virtual float shadow_offset_y() const override; + virtual void set_shadow_offset_y(float) override; + virtual String shadow_color() const override; + virtual void set_shadow_color(String) override; + HTMLCanvasElement& canvas_element(); HTMLCanvasElement const& canvas_element() const; @@ -131,6 +140,8 @@ private: void stroke_internal(Gfx::Path const&); void fill_internal(Gfx::Path const&, Gfx::WindingRule); void clip_internal(Gfx::Path&, Gfx::WindingRule); + void paint_shadow_for_fill_internal(Gfx::Path const&, Gfx::WindingRule); + void paint_shadow_for_stroke_internal(Gfx::Path const&); JS::NonnullGCPtr m_element; OwnPtr m_painter;