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;