diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 22bd9d8c32..ae22ebcc08 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -502,6 +502,7 @@ set(SOURCES SVG/SVGForeignObjectElement.cpp SVG/SVGLength.cpp SVG/SVGLineElement.cpp + SVG/SVGLinearGradientElement.cpp SVG/SVGPolygonElement.cpp SVG/SVGPolylineElement.cpp SVG/SVGRectElement.cpp diff --git a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp index 0153fc633d..b2d943046e 100644 --- a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp +++ b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -432,6 +433,8 @@ static WebIDL::ExceptionOr> create_svg_element(JS::Re return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); if (local_name == SVG::TagNames::line) return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); + if (local_name == SVG::TagNames::linearGradient) + return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); if (local_name == SVG::TagNames::path) return MUST_OR_THROW_OOM(realm.heap().allocate(realm, document, move(qualified_name))); if (local_name == SVG::TagNames::polygon) diff --git a/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp new file mode 100644 index 0000000000..0ddfe0a0f5 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::SVG { + +SVGLinearGradientElement::SVGLinearGradientElement(DOM::Document& document, DOM::QualifiedName qualified_name) + : SVGGradientElement(document, qualified_name) +{ +} + +JS::ThrowCompletionOr SVGLinearGradientElement::initialize(JS::Realm& realm) +{ + MUST_OR_THROW_OOM(Base::initialize(realm)); + set_prototype(&Bindings::ensure_web_prototype(realm, "SVGLinearGradientElement")); + + return {}; +} + +void SVGLinearGradientElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) +{ + SVGGradientElement::parse_attribute(name, value); + + // FIXME: Should allow for ` | ` for x1, x2, y1, y2 + if (name == SVG::AttributeNames::x1) { + m_x1 = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::y1) { + m_y1 = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::x2) { + m_x2 = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } else if (name == SVG::AttributeNames::y2) { + m_y2 = AttributeParser::parse_number_percentage(value); + m_paint_style = nullptr; + } +} + +// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementX1Attribute +NumberPercentage SVGLinearGradientElement::start_x() const +{ + if (m_x1.has_value()) + return *m_x1; + if (auto href = linear_gradient_xlink_href()) + return href->start_x(); + // If the attribute is not specified, the effect is as if a value of '0%' were specified. + return NumberPercentage::create_percentage(0); +} + +// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementY1Attribute +NumberPercentage SVGLinearGradientElement::start_y() const +{ + if (m_y1.has_value()) + return *m_y1; + if (auto href = linear_gradient_xlink_href()) + return href->start_x(); + // If the attribute is not specified, the effect is as if a value of '0%' were specified. + return NumberPercentage::create_percentage(0); +} + +// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementX2Attribute +NumberPercentage SVGLinearGradientElement::end_x() const +{ + if (m_x2.has_value()) + return *m_x2; + if (auto href = linear_gradient_xlink_href()) + return href->start_x(); + // If the attribute is not specified, the effect is as if a value of '100%' were specified. + return NumberPercentage::create_percentage(100); +} + +// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementY2Attribute +NumberPercentage SVGLinearGradientElement::end_y() const +{ + if (m_y2.has_value()) + return *m_y2; + if (auto href = linear_gradient_xlink_href()) + return href->start_x(); + // If the attribute is not specified, the effect is as if a value of '0%' were specified. + return NumberPercentage::create_percentage(0); +} + +Optional SVGLinearGradientElement::to_gfx_paint_style(SVGPaintContext const& paint_context) const +{ + auto units = gradient_units(); + // FIXME: Resolve percentages properly + Gfx::FloatPoint start_point {}; + Gfx::FloatPoint end_point {}; + // https://svgwg.org/svg2-draft/pservers.html#LinearGradientElementGradientUnitsAttribute + if (units == GradientUnits::ObjectBoundingBox) { + // If gradientUnits="objectBoundingBox", the user coordinate system for attributes ‘x1’, ‘y1’, ‘x2’ and ‘y2’ + // is established using the bounding box of the element to which the gradient is applied (see Object bounding + // box units) and then applying the transform specified by attribute ‘gradientTransform’. Percentages represent + // values relative to the bounding box for the object. + // Note: For gradientUnits="objectBoundingBox" both "100%" and "1" are treated the same. + start_point = paint_context.path_bounding_box.location() + Gfx::FloatPoint { start_x().value() * paint_context.path_bounding_box.width(), start_y().value() * paint_context.path_bounding_box.height() }; + end_point = paint_context.path_bounding_box.location() + Gfx::FloatPoint { end_x().value() * paint_context.path_bounding_box.width(), end_y().value() * paint_context.path_bounding_box.height() }; + } else { + // GradientUnits::UserSpaceOnUse + // If gradientUnits="userSpaceOnUse", ‘x1’, ‘y1’, ‘x2’, and ‘y2’ represent values in the coordinate system + // that results from taking the current user coordinate system in place at the time when the gradient element + // is referenced (i.e., the user coordinate system for the element referencing the gradient element via a + // fill or stroke property) and then applying the transform specified by attribute ‘gradientTransform’. + // Percentages represent values relative to the current SVG viewport. + start_point = Gfx::FloatPoint { + start_x().resolve_relative_to(paint_context.viewport.width()), + start_y().resolve_relative_to(paint_context.viewport.height()), + }; + end_point = Gfx::FloatPoint { + end_x().resolve_relative_to(paint_context.viewport.width()), + end_y().resolve_relative_to(paint_context.viewport.height()), + }; + } + + if (!m_paint_style) { + m_paint_style = Gfx::SVGLinearGradientPaintStyle::create(start_point, end_point) + .release_value_but_fixme_should_propagate_errors(); + // FIXME: Update this if DOM changes? + for_each_color_stop([&](auto& stop) { + m_paint_style->add_color_stop(stop.stop_offset().value(), stop.stop_color()).release_value_but_fixme_should_propagate_errors(); + }); + } else { + m_paint_style->set_start_point(start_point); + m_paint_style->set_end_point(end_point); + } + + auto gradient_affine_transform = gradient_transform().value_or(Gfx::AffineTransform {}); + + if (units == GradientUnits::ObjectBoundingBox) { + // Adjust transform to take place in the coordinate system defined by the bounding box: + gradient_affine_transform = Gfx::AffineTransform {} + .translate(paint_context.path_bounding_box.location()) + .scale(paint_context.path_bounding_box.width(), paint_context.path_bounding_box.height()) + .multiply(gradient_affine_transform) + .scale(1 / paint_context.path_bounding_box.width(), 1 / paint_context.path_bounding_box.height()) + .translate(-paint_context.path_bounding_box.location()); + } + + m_paint_style->set_gradient_transform(Gfx::AffineTransform { paint_context.transform }.multiply(gradient_affine_transform)); + return *m_paint_style; +} + +JS::NonnullGCPtr SVGLinearGradientElement::x1() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGLinearGradientElement::y1() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGLinearGradientElement::x2() const +{ + TODO(); +} + +JS::NonnullGCPtr SVGLinearGradientElement::y2() const +{ + TODO(); +} + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.h b/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.h new file mode 100644 index 0000000000..e9e5cc8047 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, MacDue + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::SVG { + +class SVGLinearGradientElement : public SVGGradientElement { + WEB_PLATFORM_OBJECT(SVGLinearGradientElement, SVGGradientElement); + +public: + virtual ~SVGLinearGradientElement() override = default; + + virtual void parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; + + virtual Optional to_gfx_paint_style(SVGPaintContext const&) const override; + + JS::NonnullGCPtr x1() const; + JS::NonnullGCPtr y1() const; + JS::NonnullGCPtr x2() const; + JS::NonnullGCPtr y2() const; + +protected: + SVGLinearGradientElement(DOM::Document&, DOM::QualifiedName); + + virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; + +private: + JS::GCPtr linear_gradient_xlink_href() const + { + if (auto href = xlink_href(); href && is(*href)) + return &verify_cast(*href); + return {}; + } + + NumberPercentage start_x() const; + NumberPercentage start_y() const; + NumberPercentage end_x() const; + NumberPercentage end_y() const; + + Optional m_x1; + Optional m_y1; + Optional m_x2; + Optional m_y2; + + mutable RefPtr m_paint_style; +}; + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.idl b/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.idl new file mode 100644 index 0000000000..49a233a9e4 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.idl @@ -0,0 +1,9 @@ +#import + +[Exposed=Window] +interface SVGLinearGradientElement : SVGGradientElement { + [SameObject] readonly attribute SVGAnimatedLength x1; + [SameObject] readonly attribute SVGAnimatedLength y1; + [SameObject] readonly attribute SVGAnimatedLength x2; + [SameObject] readonly attribute SVGAnimatedLength y2; +}; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 11a8a0bc68..ac0653a92e 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -203,6 +203,7 @@ libweb_js_bindings(SVG/SVGEllipseElement) libweb_js_bindings(SVG/SVGForeignObjectElement) libweb_js_bindings(SVG/SVGLength) libweb_js_bindings(SVG/SVGLineElement) +libweb_js_bindings(SVG/SVGLinearGradientElement) libweb_js_bindings(SVG/SVGPathElement) libweb_js_bindings(SVG/SVGPolygonElement) libweb_js_bindings(SVG/SVGPolylineElement)