diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2022-08-12 14:00:00 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-08-14 11:30:40 +0200 |
commit | 08e6071ebbe9b468284bdd887d862aa7c81a2129 (patch) | |
tree | 1c67b970bba6e1f2d8b9cf449ac587874ca83eee | |
parent | 7b61d162627fad51fc015e745882c289dad7d7bb (diff) | |
download | serenity-08e6071ebbe9b468284bdd887d862aa7c81a2129.zip |
LibWeb: Extract CanvasState class from CRC2D
As with CanvasPath, this is to better match the spec IDL.
7 files changed, 154 insertions, 93 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index ed47ff7b0c..f04f658b76 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -136,6 +136,7 @@ set(SOURCES HTML/BrowsingContext.cpp HTML/BrowsingContextContainer.cpp HTML/Canvas/CanvasPath.cpp + HTML/Canvas/CanvasState.cpp HTML/CanvasGradient.cpp HTML/CanvasRenderingContext2D.cpp HTML/CrossOrigin/Reporting.cpp diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.cpp b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.cpp new file mode 100644 index 0000000000..c907fe52a2 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> + * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWeb/HTML/Canvas/CanvasState.h> + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-save +void CanvasState::save() +{ + // The save() method steps are to push a copy of the current drawing state onto the drawing state stack. + m_drawing_state_stack.append(m_drawing_state); +} + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-restore +void CanvasState::restore() +{ + // The restore() method steps are to pop the top entry in the drawing state stack, and reset the drawing state it describes. If there is no saved state, then the method must do nothing. + if (m_drawing_state_stack.is_empty()) + return; + m_drawing_state = m_drawing_state_stack.take_last(); +} + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-reset +void CanvasState::reset() +{ + // The reset() method steps are to reset the rendering context to its default state. + reset_to_default_state(); +} + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-iscontextlost +bool CanvasState::is_context_lost() +{ + // The isContextLost() method steps are to return this's context lost. + return m_context_lost; +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h new file mode 100644 index 0000000000..9e230c8ba7 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Vector.h> +#include <LibGfx/AffineTransform.h> +#include <LibGfx/Color.h> + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/canvas.html#canvasstate +class CanvasState { +public: + virtual ~CanvasState() = default; + + void save(); + void restore(); + void reset(); + bool is_context_lost(); + + // https://html.spec.whatwg.org/multipage/canvas.html#drawing-state + struct DrawingState { + Gfx::AffineTransform transform; + Gfx::Color fill_style { Gfx::Color::Black }; + Gfx::Color stroke_style { Gfx::Color::Black }; + float line_width { 1 }; + }; + DrawingState& drawing_state() { return m_drawing_state; } + DrawingState const& drawing_state() const { return m_drawing_state; } + + void clear_drawing_state_stack() { m_drawing_state_stack.clear(); } + void reset_drawing_state() { m_drawing_state = {}; } + + virtual void reset_to_default_state() = 0; + +protected: + CanvasState() = default; + +private: + DrawingState m_drawing_state; + Vector<DrawingState> m_drawing_state_stack; + + // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-context-lost + bool m_context_lost { false }; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.idl b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.idl new file mode 100644 index 0000000000..9608bbdb18 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasState.idl @@ -0,0 +1,7 @@ +// https://html.spec.whatwg.org/multipage/canvas.html#canvasstate +interface mixin CanvasState { + undefined save(); + undefined restore(); + undefined reset(); + boolean isContextLost(); +}; diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 9d736925b5..08d42cb396 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -49,12 +49,12 @@ NonnullRefPtr<HTMLCanvasElement> CanvasRenderingContext2D::canvas_for_binding() void CanvasRenderingContext2D::set_fill_style(String style) { // FIXME: 2. If the given value is a CanvasPattern object that is marked as not origin-clean, then set this's origin-clean flag to false. - m_drawing_state.fill_style = Gfx::Color::from_string(style).value_or(Color::Black); + drawing_state().fill_style = Gfx::Color::from_string(style).value_or(Color::Black); } String CanvasRenderingContext2D::fill_style() const { - return m_drawing_state.fill_style.to_string(); + return drawing_state().fill_style.to_string(); } void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float height) @@ -63,8 +63,10 @@ void CanvasRenderingContext2D::fill_rect(float x, float y, float width, float he if (!painter) return; - auto rect = m_drawing_state.transform.map(Gfx::FloatRect(x, y, width, height)); - painter->fill_rect(enclosing_int_rect(rect), m_drawing_state.fill_style); + auto& drawing_state = this->drawing_state(); + + auto rect = drawing_state.transform.map(Gfx::FloatRect(x, y, width, height)); + painter->fill_rect(enclosing_int_rect(rect), drawing_state.fill_style); did_draw(rect); } @@ -74,7 +76,7 @@ void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float h if (!painter) return; - auto rect = m_drawing_state.transform.map(Gfx::FloatRect(x, y, width, height)); + auto rect = drawing_state().transform.map(Gfx::FloatRect(x, y, width, height)); painter->clear_rect(enclosing_int_rect(rect), Color()); did_draw(rect); } @@ -82,12 +84,12 @@ void CanvasRenderingContext2D::clear_rect(float x, float y, float width, float h void CanvasRenderingContext2D::set_stroke_style(String style) { // FIXME: 2. If the given value is a CanvasPattern object that is marked as not origin-clean, then set this's origin-clean flag to false. - m_drawing_state.stroke_style = Gfx::Color::from_string(style).value_or(Color::Black); + drawing_state().stroke_style = Gfx::Color::from_string(style).value_or(Color::Black); } String CanvasRenderingContext2D::stroke_style() const { - return m_drawing_state.stroke_style.to_string(); + return drawing_state().stroke_style.to_string(); } void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float height) @@ -96,17 +98,19 @@ void CanvasRenderingContext2D::stroke_rect(float x, float y, float width, float if (!painter) return; - auto rect = m_drawing_state.transform.map(Gfx::FloatRect(x, y, width, height)); + auto& drawing_state = this->drawing_state(); + + auto rect = drawing_state.transform.map(Gfx::FloatRect(x, y, width, height)); - auto top_left = m_drawing_state.transform.map(Gfx::FloatPoint(x, y)).to_type<int>(); - auto top_right = m_drawing_state.transform.map(Gfx::FloatPoint(x + width - 1, y)).to_type<int>(); - auto bottom_left = m_drawing_state.transform.map(Gfx::FloatPoint(x, y + height - 1)).to_type<int>(); - auto bottom_right = m_drawing_state.transform.map(Gfx::FloatPoint(x + width - 1, y + height - 1)).to_type<int>(); + auto top_left = drawing_state.transform.map(Gfx::FloatPoint(x, y)).to_type<int>(); + auto top_right = drawing_state.transform.map(Gfx::FloatPoint(x + width - 1, y)).to_type<int>(); + auto bottom_left = drawing_state.transform.map(Gfx::FloatPoint(x, y + height - 1)).to_type<int>(); + auto bottom_right = drawing_state.transform.map(Gfx::FloatPoint(x + width - 1, y + height - 1)).to_type<int>(); - painter->draw_line(top_left, top_right, m_drawing_state.stroke_style, m_drawing_state.line_width); - painter->draw_line(top_right, bottom_right, m_drawing_state.stroke_style, m_drawing_state.line_width); - painter->draw_line(bottom_right, bottom_left, m_drawing_state.stroke_style, m_drawing_state.line_width); - painter->draw_line(bottom_left, top_left, m_drawing_state.stroke_style, m_drawing_state.line_width); + painter->draw_line(top_left, top_right, drawing_state.stroke_style, drawing_state.line_width); + painter->draw_line(top_right, bottom_right, drawing_state.stroke_style, drawing_state.line_width); + painter->draw_line(bottom_right, bottom_left, drawing_state.stroke_style, drawing_state.line_width); + painter->draw_line(bottom_left, top_left, drawing_state.stroke_style, drawing_state.line_width); did_draw(rect); } @@ -197,10 +201,12 @@ DOM::ExceptionOr<void> CanvasRenderingContext2D::draw_image(CanvasImageSource co if (!painter) return {}; - if (m_drawing_state.transform.is_identity_or_translation()) { - painter->translate(m_drawing_state.transform.e(), m_drawing_state.transform.f()); + auto& drawing_state = this->drawing_state(); + + if (drawing_state.transform.is_identity_or_translation()) { + painter->translate(drawing_state.transform.e(), drawing_state.transform.f()); painter->draw_scaled_bitmap(destination_rect.to_rounded<int>(), *bitmap, source_rect, 1.0f, Gfx::Painter::ScalingMode::BilinearBlend); - painter->translate(-m_drawing_state.transform.e(), -m_drawing_state.transform.f()); + painter->translate(-drawing_state.transform.e(), -drawing_state.transform.f()); } else { // The context has an affine transform, we have to draw through it! @@ -215,11 +221,11 @@ DOM::ExceptionOr<void> CanvasRenderingContext2D::draw_image(CanvasImageSource co // FIXME: Gfx::Painter should have an affine transform as part of its state and handle all of this instead. - auto inverse_transform = m_drawing_state.transform.inverse(); + auto inverse_transform = drawing_state.transform.inverse(); if (!inverse_transform.has_value()) return {}; - auto destination_quad = m_drawing_state.transform.map_to_quad(destination_rect); + auto destination_quad = drawing_state.transform.map_to_quad(destination_rect); auto destination_bounding_rect = destination_quad.bounding_rect().to_rounded<int>(); Gfx::AffineTransform source_transform; @@ -260,19 +266,19 @@ DOM::ExceptionOr<void> CanvasRenderingContext2D::draw_image(CanvasImageSource co void CanvasRenderingContext2D::scale(float sx, float sy) { dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasRenderingContext2D::scale({}, {})", sx, sy); - m_drawing_state.transform.scale(sx, sy); + drawing_state().transform.scale(sx, sy); } void CanvasRenderingContext2D::translate(float tx, float ty) { dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasRenderingContext2D::translate({}, {})", tx, ty); - m_drawing_state.transform.translate(tx, ty); + drawing_state().transform.translate(tx, ty); } void CanvasRenderingContext2D::rotate(float radians) { dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasRenderingContext2D::rotate({})", radians); - m_drawing_state.transform.rotate_radians(radians); + drawing_state().transform.rotate_radians(radians); } void CanvasRenderingContext2D::did_draw(Gfx::FloatRect const&) @@ -302,10 +308,12 @@ void CanvasRenderingContext2D::fill_text(String const& text, float x, float y, O if (!painter) return; + auto& drawing_state = this->drawing_state(); + // FIXME: painter only supports integer rects for text right now, so this effectively chops off any fractional position auto text_rect = Gfx::IntRect(x, y, max_width.has_value() ? max_width.value() : painter->font().width(text), painter->font().pixel_size()); - auto transformed_rect = m_drawing_state.transform.map(text_rect); - painter->draw_text(transformed_rect, text, Gfx::TextAlignment::TopLeft, m_drawing_state.fill_style); + auto transformed_rect = drawing_state.transform.map(text_rect); + painter->draw_text(transformed_rect, text, Gfx::TextAlignment::TopLeft, drawing_state.fill_style); did_draw(transformed_rect.to_type<float>()); } @@ -326,7 +334,9 @@ void CanvasRenderingContext2D::stroke_internal(Gfx::Path const& path) if (!painter) return; - painter->stroke_path(path, m_drawing_state.stroke_style, m_drawing_state.line_width); + auto& drawing_state = this->drawing_state(); + + painter->stroke_path(path, drawing_state.stroke_style, drawing_state.line_width); did_draw(path.bounding_box()); } @@ -337,7 +347,7 @@ void CanvasRenderingContext2D::stroke() void CanvasRenderingContext2D::stroke(Path2D const& path) { - auto transformed_path = path.path().copy_transformed(m_drawing_state.transform); + auto transformed_path = path.path().copy_transformed(drawing_state().transform); stroke_internal(transformed_path); } @@ -357,7 +367,7 @@ void CanvasRenderingContext2D::fill_internal(Gfx::Path& path, String const& fill else dbgln("Unrecognized fillRule for CRC2D.fill() - this problem goes away once we pass an enum instead of a string"); - painter->fill_path(path, m_drawing_state.fill_style, winding); + painter->fill_path(path, drawing_state().fill_style, winding); did_draw(path.bounding_box()); } @@ -368,7 +378,7 @@ void CanvasRenderingContext2D::fill(String const& fill_rule) void CanvasRenderingContext2D::fill(Path2D& path, String const& fill_rule) { - auto transformed_path = path.path().copy_transformed(m_drawing_state.transform); + auto transformed_path = path.path().copy_transformed(drawing_state().transform); return fill_internal(transformed_path, fill_rule); } @@ -432,36 +442,6 @@ void CanvasRenderingContext2D::put_image_data(ImageData const& image_data, float did_draw(Gfx::FloatRect(x, y, image_data.width(), image_data.height())); } -// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-save -void CanvasRenderingContext2D::save() -{ - // The save() method steps are to push a copy of the current drawing state onto the drawing state stack. - m_drawing_state_stack.append(m_drawing_state); -} - -// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-restore -void CanvasRenderingContext2D::restore() -{ - // The restore() method steps are to pop the top entry in the drawing state stack, and reset the drawing state it describes. If there is no saved state, then the method must do nothing. - if (m_drawing_state_stack.is_empty()) - return; - m_drawing_state = m_drawing_state_stack.take_last(); -} - -// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-reset -void CanvasRenderingContext2D::reset() -{ - // The reset() method steps are to reset the rendering context to its default state. - reset_to_default_state(); -} - -// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-iscontextlost -bool CanvasRenderingContext2D::is_context_lost() -{ - // The isContextLost() method steps are to return this's context lost. - return m_context_lost; -} - // https://html.spec.whatwg.org/multipage/canvas.html#reset-the-rendering-context-to-its-default-state void CanvasRenderingContext2D::reset_to_default_state() { @@ -475,10 +455,10 @@ void CanvasRenderingContext2D::reset_to_default_state() path().clear(); // 3. Clear the context's drawing state stack. - m_drawing_state_stack.clear(); + clear_drawing_state_stack(); // 4. Reset everything that drawing state consists of to their initial values. - m_drawing_state = {}; + reset_drawing_state(); if (painter) did_draw(painter->target()->rect().to_type<float>()); @@ -632,7 +612,7 @@ void CanvasRenderingContext2D::transform(double a, double b, double c, double d, // a c e // b d f // 0 0 1 - m_drawing_state.transform.multiply({ static_cast<float>(a), static_cast<float>(b), static_cast<float>(c), static_cast<float>(d), static_cast<float>(e), static_cast<float>(f) }); + drawing_state().transform.multiply({ static_cast<float>(a), static_cast<float>(b), static_cast<float>(c), static_cast<float>(d), static_cast<float>(e), static_cast<float>(f) }); } // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-settransform @@ -643,7 +623,7 @@ void CanvasRenderingContext2D::set_transform(double a, double b, double c, doubl return; // 2. Reset the current transformation matrix to the identity matrix. - m_drawing_state.transform = {}; + drawing_state().transform = {}; // 3. Invoke the transform(a, b, c, d, e, f) method with the same arguments. transform(a, b, c, d, e, f); @@ -653,7 +633,7 @@ void CanvasRenderingContext2D::set_transform(double a, double b, double c, doubl void CanvasRenderingContext2D::reset_transform() { // The resetTransform() method, when invoked, must reset the current transformation matrix to the identity matrix. - m_drawing_state.transform = {}; + drawing_state().transform = {}; } void CanvasRenderingContext2D::clip() diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index 4eb0c4ee6d..9951dd5973 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -17,6 +17,7 @@ #include <LibWeb/Bindings/Wrappable.h> #include <LibWeb/DOM/ExceptionOr.h> #include <LibWeb/HTML/Canvas/CanvasPath.h> +#include <LibWeb/HTML/Canvas/CanvasState.h> #include <LibWeb/HTML/CanvasGradient.h> #include <LibWeb/Layout/InlineNode.h> #include <LibWeb/Layout/LineBox.h> @@ -30,7 +31,8 @@ using CanvasImageSource = Variant<NonnullRefPtr<HTMLImageElement>, NonnullRefPtr class CanvasRenderingContext2D : public RefCountForwarder<HTMLCanvasElement> , public Bindings::Wrappable - , public CanvasPath { + , public CanvasPath + , public CanvasState { AK_MAKE_NONCOPYABLE(CanvasRenderingContext2D); AK_MAKE_NONMOVABLE(CanvasRenderingContext2D); @@ -59,8 +61,8 @@ public: void translate(float x, float y); void rotate(float degrees); - void set_line_width(float line_width) { m_drawing_state.line_width = line_width; } - float line_width() const { return m_drawing_state.line_width; } + void set_line_width(float line_width) { drawing_state().line_width = line_width; } + float line_width() const { return drawing_state().line_width; } void begin_path(); void stroke(); @@ -76,12 +78,7 @@ public: DOM::ExceptionOr<RefPtr<ImageData>> get_image_data(int x, int y, int width, int height) const; void put_image_data(ImageData const&, float x, float y); - void save(); - void restore(); - void reset(); - bool is_context_lost(); - - void reset_to_default_state(); + virtual void reset_to_default_state() override; NonnullRefPtr<HTMLCanvasElement> canvas_for_binding() const; @@ -121,22 +118,8 @@ private: void stroke_internal(Gfx::Path const&); void fill_internal(Gfx::Path&, String const& fill_rule); - // https://html.spec.whatwg.org/multipage/canvas.html#drawing-state - struct DrawingState { - Gfx::AffineTransform transform; - Gfx::Color fill_style { Gfx::Color::Black }; - Gfx::Color stroke_style { Gfx::Color::Black }; - float line_width { 1 }; - }; - - DrawingState m_drawing_state; - Vector<DrawingState> m_drawing_state_stack; - // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-origin-clean bool m_origin_clean { true }; - - // https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-context-lost - bool m_context_lost { false }; }; enum class CanvasImageSourceUsability { diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl index 471fa01c33..041c00b770 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.idl @@ -4,6 +4,7 @@ #import <HTML/TextMetrics.idl> #import <HTML/CanvasGradient.idl> #import <HTML/Canvas/CanvasPath.idl> +#import <HTML/Canvas/CanvasState.idl> #import <HTML/Path2D.idl> // https://html.spec.whatwg.org/multipage/canvas.html#canvasrenderingcontext2d @@ -41,11 +42,6 @@ interface CanvasRenderingContext2D { ImageData getImageData(long sx, long sy, long sw, long sh); undefined putImageData(ImageData imagedata, double dx, double dy); - undefined save(); - undefined restore(); - undefined reset(); - boolean isContextLost(); - [ImplementedAs=canvas_for_binding] readonly attribute HTMLCanvasElement canvas; TextMetrics measureText(DOMString text); @@ -65,4 +61,5 @@ interface CanvasRenderingContext2D { }; +CanvasRenderingContext2D includes CanvasState; CanvasRenderingContext2D includes CanvasPath; |