2020-07-22 15:17:39 -07:00
/*
2021-04-22 16:53:07 -07:00
* Copyright ( c ) 2020 , Matthew Olsson < mattco @ serenityos . org >
2022-01-24 15:43:44 +00:00
* Copyright ( c ) 2021 - 2022 , Sam Atkins < atkinssj @ serenityos . org >
2023-04-10 12:25:40 +01:00
* Copyright ( c ) 2023 , MacDue < macdue @ dueutil . tech >
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2023 , Andreas Kling < andreas @ ladybird . org >
2020-07-22 15:17:39 -07:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-07-22 15:17:39 -07:00
*/
2022-09-30 17:16:16 -06:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/SVGGraphicsElementPrototype.h>
2022-01-24 15:43:44 +00:00
# include <LibWeb/CSS/Parser/Parser.h>
2023-04-23 01:31:17 +01:00
# include <LibWeb/DOM/Document.h>
2022-04-10 20:16:47 +02:00
# include <LibWeb/Layout/Node.h>
2024-06-13 15:22:46 +03:00
# include <LibWeb/Painting/PaintStyle.h>
2024-04-20 23:10:26 +01:00
# include <LibWeb/Painting/PaintableBox.h>
# include <LibWeb/Painting/SVGGraphicsPaintable.h>
2023-06-20 12:57:54 +02:00
# include <LibWeb/SVG/AttributeNames.h>
2023-04-10 12:25:40 +01:00
# include <LibWeb/SVG/AttributeParser.h>
2024-03-27 00:06:35 +00:00
# include <LibWeb/SVG/SVGClipPathElement.h>
2023-04-23 01:31:17 +01:00
# include <LibWeb/SVG/SVGGradientElement.h>
2020-07-23 09:44:42 -07:00
# include <LibWeb/SVG/SVGGraphicsElement.h>
2023-09-10 14:10:55 +01:00
# include <LibWeb/SVG/SVGMaskElement.h>
2022-04-10 20:16:47 +02:00
# include <LibWeb/SVG/SVGSVGElement.h>
2023-07-31 18:37:44 +02:00
# include <LibWeb/SVG/SVGSymbolElement.h>
2020-07-22 15:17:39 -07:00
2020-07-23 09:44:42 -07:00
namespace Web : : SVG {
2020-07-22 15:17:39 -07:00
2022-02-18 21:00:52 +01:00
SVGGraphicsElement : : SVGGraphicsElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 11:20:15 +01:00
: SVGElement ( document , move ( qualified_name ) )
2020-07-22 15:17:39 -07:00
{
2023-01-10 06:28:20 -05:00
}
2023-08-07 08:41:28 +02:00
void SVGGraphicsElement : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( SVGGraphicsElement ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2020-07-22 15:17:39 -07:00
}
2024-11-14 08:14:16 -05:00
void SVGGraphicsElement : : attribute_changed ( FlyString const & name , Optional < String > const & old_value , Optional < String > const & value , Optional < FlyString > const & namespace_ )
2023-04-10 12:25:00 +01:00
{
2024-11-14 08:14:16 -05:00
Base : : attribute_changed ( name , old_value , value , namespace_ ) ;
2023-05-19 20:35:39 +01:00
if ( name = = " transform " sv ) {
2023-11-19 18:10:36 +13:00
auto transform_list = AttributeParser : : parse_transform ( value . value_or ( String { } ) ) ;
2023-04-10 12:25:40 +01:00
if ( transform_list . has_value ( ) )
m_transform = transform_from_transform_list ( * transform_list ) ;
2025-04-19 10:49:33 +02:00
set_needs_layout_tree_update ( true , DOM : : SetNeedsLayoutTreeUpdateReason : : SVGGraphicsElementTransformChange ) ;
2023-04-10 12:25:00 +01:00
}
}
2024-06-13 15:22:46 +03:00
Optional < Painting : : PaintStyle > SVGGraphicsElement : : svg_paint_computed_value_to_gfx_paint_style ( SVGPaintContext const & paint_context , Optional < CSS : : SVGPaint > const & paint_value ) const
2023-04-23 01:31:17 +01:00
{
// FIXME: This entire function is an ad-hoc hack:
2023-06-06 20:40:10 +01:00
if ( ! paint_value . has_value ( ) | | ! paint_value - > is_url ( ) )
2023-04-23 01:31:17 +01:00
return { } ;
2023-09-10 14:10:55 +01:00
if ( auto gradient = try_resolve_url_to < SVG : : SVGGradientElement const > ( paint_value - > as_url ( ) ) )
return gradient - > to_gfx_paint_style ( paint_context ) ;
2023-04-23 01:31:17 +01:00
return { } ;
}
2024-06-13 15:22:46 +03:00
Optional < Painting : : PaintStyle > SVGGraphicsElement : : fill_paint_style ( SVGPaintContext const & paint_context ) const
2023-06-06 20:40:10 +01:00
{
if ( ! layout_node ( ) )
return { } ;
return svg_paint_computed_value_to_gfx_paint_style ( paint_context , layout_node ( ) - > computed_values ( ) . fill ( ) ) ;
}
2024-06-13 15:22:46 +03:00
Optional < Painting : : PaintStyle > SVGGraphicsElement : : stroke_paint_style ( SVGPaintContext const & paint_context ) const
2023-06-06 20:40:10 +01:00
{
if ( ! layout_node ( ) )
return { } ;
return svg_paint_computed_value_to_gfx_paint_style ( paint_context , layout_node ( ) - > computed_values ( ) . stroke ( ) ) ;
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < SVG : : SVGMaskElement const > SVGGraphicsElement : : mask ( ) const
2023-09-10 14:10:55 +01:00
{
auto const & mask_reference = layout_node ( ) - > computed_values ( ) . mask ( ) ;
if ( ! mask_reference . has_value ( ) )
return { } ;
return try_resolve_url_to < SVG : : SVGMaskElement const > ( mask_reference - > url ( ) ) ;
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < SVG : : SVGClipPathElement const > SVGGraphicsElement : : clip_path ( ) const
2024-03-27 00:06:35 +00:00
{
auto const & clip_path_reference = layout_node ( ) - > computed_values ( ) . clip_path ( ) ;
2024-05-26 00:25:05 +01:00
if ( ! clip_path_reference . has_value ( ) | | ! clip_path_reference - > is_url ( ) )
2024-03-27 00:06:35 +00:00
return { } ;
return try_resolve_url_to < SVG : : SVGClipPathElement const > ( clip_path_reference - > url ( ) ) ;
}
2023-04-12 13:32:21 -04:00
Gfx : : AffineTransform transform_from_transform_list ( ReadonlySpan < Transform > transform_list )
2023-04-10 12:25:40 +01:00
{
Gfx : : AffineTransform affine_transform ;
2023-04-12 13:32:21 -04:00
for ( auto & transform : transform_list ) {
transform . operation . visit (
2023-04-10 12:25:40 +01:00
[ & ] ( Transform : : Translate const & translate ) {
affine_transform . multiply ( Gfx : : AffineTransform { } . translate ( { translate . x , translate . y } ) ) ;
} ,
[ & ] ( Transform : : Scale const & scale ) {
affine_transform . multiply ( Gfx : : AffineTransform { } . scale ( { scale . x , scale . y } ) ) ;
} ,
[ & ] ( Transform : : Rotate const & rotate ) {
Gfx : : AffineTransform translate_transform ;
affine_transform . multiply (
Gfx : : AffineTransform { }
. translate ( { rotate . x , rotate . y } )
2023-09-09 14:43:39 +02:00
. rotate_radians ( AK : : to_radians ( rotate . a ) )
2023-04-10 12:25:40 +01:00
. translate ( { - rotate . x , - rotate . y } ) ) ;
} ,
[ & ] ( Transform : : SkewX const & skew_x ) {
2023-09-09 14:43:39 +02:00
affine_transform . multiply ( Gfx : : AffineTransform { } . skew_radians ( AK : : to_radians ( skew_x . a ) , 0 ) ) ;
2023-04-10 12:25:40 +01:00
} ,
[ & ] ( Transform : : SkewY const & skew_y ) {
2023-09-09 14:43:39 +02:00
affine_transform . multiply ( Gfx : : AffineTransform { } . skew_radians ( 0 , AK : : to_radians ( skew_y . a ) ) ) ;
2023-04-10 12:25:40 +01:00
} ,
[ & ] ( Transform : : Matrix const & matrix ) {
affine_transform . multiply ( Gfx : : AffineTransform {
matrix . a , matrix . b , matrix . c , matrix . d , matrix . e , matrix . f } ) ;
} ) ;
}
return affine_transform ;
}
Gfx : : AffineTransform SVGGraphicsElement : : get_transform ( ) const
{
Gfx : : AffineTransform transform = m_transform ;
2023-05-28 18:27:35 +01:00
for ( auto * svg_ancestor = shadow_including_first_ancestor_of_type < SVGGraphicsElement > ( ) ; svg_ancestor ; svg_ancestor = svg_ancestor - > shadow_including_first_ancestor_of_type < SVGGraphicsElement > ( ) ) {
2023-08-31 08:58:41 +01:00
transform = Gfx : : AffineTransform { svg_ancestor - > element_transform ( ) } . multiply ( transform ) ;
2023-04-10 12:25:40 +01:00
}
return transform ;
}
2024-05-12 20:19:43 +01:00
static FillRule to_svg_fill_rule ( CSS : : FillRule fill_rule )
2023-06-11 16:43:46 +01:00
{
2024-05-12 20:19:43 +01:00
switch ( fill_rule ) {
2023-06-11 16:43:46 +01:00
case CSS : : FillRule : : Nonzero :
return FillRule : : Nonzero ;
case CSS : : FillRule : : Evenodd :
return FillRule : : Evenodd ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2024-05-12 20:19:43 +01:00
Optional < FillRule > SVGGraphicsElement : : fill_rule ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return to_svg_fill_rule ( layout_node ( ) - > computed_values ( ) . fill_rule ( ) ) ;
}
Optional < ClipRule > SVGGraphicsElement : : clip_rule ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return to_svg_fill_rule ( layout_node ( ) - > computed_values ( ) . clip_rule ( ) ) ;
}
2021-09-16 12:28:14 +01:00
Optional < Gfx : : Color > SVGGraphicsElement : : fill_color ( ) const
{
if ( ! layout_node ( ) )
return { } ;
2025-08-20 20:31:56 +01:00
auto paint = layout_node ( ) - > computed_values ( ) . fill ( ) ;
if ( ! paint . has_value ( ) )
return { } ;
if ( paint - > is_url ( ) ) {
if ( auto referenced_element = try_resolve_url_to < SVGGraphicsElement const > ( paint - > as_url ( ) ) )
return referenced_element - > fill_color ( ) ;
return { } ;
}
return paint - > as_color ( ) ;
2021-09-16 12:28:14 +01:00
}
Optional < Gfx : : Color > SVGGraphicsElement : : stroke_color ( ) const
{
if ( ! layout_node ( ) )
return { } ;
2025-08-20 20:41:17 +01:00
auto paint = layout_node ( ) - > computed_values ( ) . stroke ( ) ;
if ( ! paint . has_value ( ) )
return { } ;
if ( paint - > is_url ( ) ) {
if ( auto referenced_element = try_resolve_url_to < SVGGraphicsElement const > ( paint - > as_url ( ) ) )
return referenced_element - > stroke_color ( ) ;
return { } ;
}
return paint - > as_color ( ) ;
2021-09-16 12:28:14 +01:00
}
2023-05-19 20:35:39 +01:00
Optional < float > SVGGraphicsElement : : fill_opacity ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . fill_opacity ( ) ;
}
2024-10-10 10:15:49 -04:00
Optional < CSS : : StrokeLinecap > SVGGraphicsElement : : stroke_linecap ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . stroke_linecap ( ) ;
}
2024-10-28 20:51:16 -04:00
Optional < CSS : : StrokeLinejoin > SVGGraphicsElement : : stroke_linejoin ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . stroke_linejoin ( ) ;
}
Optional < CSS : : NumberOrCalculated > SVGGraphicsElement : : stroke_miterlimit ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . stroke_miterlimit ( ) ;
}
2023-05-19 20:35:39 +01:00
Optional < float > SVGGraphicsElement : : stroke_opacity ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . stroke_opacity ( ) ;
}
2024-11-18 21:21:22 -05:00
float SVGGraphicsElement : : resolve_relative_to_viewport_size ( CSS : : LengthPercentage const & length_percentage ) const
2021-09-16 12:28:14 +01:00
{
// FIXME: Converting to pixels isn't really correct - values should be in "user units"
// https://svgwg.org/svg2-draft/coords.html#TermUserUnits
2023-06-15 16:51:17 +01:00
// Resolved relative to the "Scaled viewport size": https://www.w3.org/TR/2017/WD-fill-stroke-3-20170413/#scaled-viewport-size
// FIXME: This isn't right, but it's something.
CSSPixels viewport_width = 0 ;
CSSPixels viewport_height = 0 ;
if ( auto * svg_svg_element = shadow_including_first_ancestor_of_type < SVGSVGElement > ( ) ) {
2024-05-02 22:32:44 +12:00
if ( auto svg_svg_layout_node = svg_svg_element - > layout_node ( ) ) {
2023-06-15 16:51:17 +01:00
viewport_width = svg_svg_layout_node - > computed_values ( ) . width ( ) . to_px ( * svg_svg_layout_node , 0 ) ;
viewport_height = svg_svg_layout_node - > computed_values ( ) . height ( ) . to_px ( * svg_svg_layout_node , 0 ) ;
2022-04-10 20:16:47 +02:00
}
2022-01-14 16:52:14 +00:00
}
2023-08-26 15:03:04 +01:00
auto scaled_viewport_size = ( viewport_width + viewport_height ) * CSSPixels ( 0.5 ) ;
2024-11-18 21:21:22 -05:00
return length_percentage . to_px ( * layout_node ( ) , scaled_viewport_size ) . to_double ( ) ;
}
2024-11-20 19:23:10 -05:00
Vector < float > SVGGraphicsElement : : stroke_dasharray ( ) const
{
if ( ! layout_node ( ) )
return { } ;
Vector < float > dasharray ;
for ( auto const & value : layout_node ( ) - > computed_values ( ) . stroke_dasharray ( ) ) {
value . visit (
[ & ] ( CSS : : LengthPercentage const & length_percentage ) {
dasharray . append ( resolve_relative_to_viewport_size ( length_percentage ) ) ;
} ,
[ & ] ( CSS : : NumberOrCalculated const & number_or_calculated ) {
2025-01-22 15:00:13 +00:00
CSS : : CalculationResolutionContext calculation_context {
. length_resolution_context = CSS : : Length : : ResolutionContext : : for_layout_node ( * layout_node ( ) ) ,
} ;
dasharray . append ( number_or_calculated . resolved ( calculation_context ) . value_or ( 0 ) ) ;
2024-11-20 19:23:10 -05:00
} ) ;
}
// https://svgwg.org/svg2-draft/painting.html#StrokeDashing
// If the list has an odd number of values, then it is repeated to yield an even number of values.
if ( dasharray . size ( ) % 2 = = 1 )
dasharray . extend ( dasharray ) ;
// If any value in the list is negative, the <dasharray> value is invalid. If all of the values in the list are zero, then the stroke is rendered as a solid line without any dashing.
bool all_zero = true ;
for ( auto & value : dasharray ) {
if ( value < 0 )
return { } ;
if ( value ! = 0 )
all_zero = false ;
}
if ( all_zero )
return { } ;
return dasharray ;
}
2024-11-18 21:21:22 -05:00
Optional < float > SVGGraphicsElement : : stroke_dashoffset ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return resolve_relative_to_viewport_size ( layout_node ( ) - > computed_values ( ) . stroke_dashoffset ( ) ) ;
}
Optional < float > SVGGraphicsElement : : stroke_width ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return resolve_relative_to_viewport_size ( layout_node ( ) - > computed_values ( ) . stroke_width ( ) ) ;
2021-09-16 12:28:14 +01:00
}
2024-09-06 23:38:13 +01:00
// https://svgwg.org/svg2-draft/types.html#__svg__SVGGraphicsElement__getBBox
2024-11-15 04:01:23 +13:00
GC : : Ref < Geometry : : DOMRect > SVGGraphicsElement : : get_b_box ( Optional < SVGBoundingBoxOptions > )
2024-04-01 00:49:02 +01:00
{
2024-04-20 23:10:26 +01:00
// FIXME: It should be possible to compute this without layout updates. The bounding box is within the
// SVG coordinate space (before any viewbox or other transformations), so it should be possible to
// calculate this from SVG geometry without a full layout tree (at least for simple cases).
// See: https://svgwg.org/svg2-draft/coords.html#BoundingBoxes
2025-03-05 20:50:05 +01:00
const_cast < DOM : : Document & > ( document ( ) ) . update_layout ( DOM : : UpdateLayoutReason : : SVGGraphicsElementGetBBox ) ;
2024-04-20 23:10:26 +01:00
if ( ! layout_node ( ) )
return Geometry : : DOMRect : : create ( realm ( ) ) ;
// Invert the SVG -> screen space transform.
2024-09-06 23:38:13 +01:00
auto owner_svg_element = this - > owner_svg_element ( ) ;
if ( ! owner_svg_element )
return Geometry : : DOMRect : : create ( realm ( ) ) ;
auto svg_element_rect = owner_svg_element - > paintable_box ( ) - > absolute_rect ( ) ;
2024-04-20 23:10:26 +01:00
auto inverse_transform = static_cast < Painting : : SVGGraphicsPaintable & > ( * paintable_box ( ) ) . computed_transforms ( ) . svg_to_css_pixels_transform ( ) . inverse ( ) ;
2024-07-21 23:29:06 +01:00
auto translated_rect = paintable_box ( ) - > absolute_rect ( ) . to_type < float > ( ) . translated ( - svg_element_rect . location ( ) . to_type < float > ( ) ) ;
if ( inverse_transform . has_value ( ) )
translated_rect = inverse_transform - > map ( translated_rect ) ;
return Geometry : : DOMRect : : create ( realm ( ) , translated_rect ) ;
2024-04-01 00:49:02 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ref < SVGAnimatedTransformList > SVGGraphicsElement : : transform ( ) const
2024-04-01 00:49:02 +01:00
{
dbgln ( " (STUBBED) SVGGraphicsElement::transform(). Called on: {} " , debug_description ( ) ) ;
auto base_val = SVGTransformList : : create ( realm ( ) ) ;
auto anim_val = SVGTransformList : : create ( realm ( ) ) ;
return SVGAnimatedTransformList : : create ( realm ( ) , base_val , anim_val ) ;
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < Geometry : : DOMMatrix > SVGGraphicsElement : : get_screen_ctm ( )
2024-11-09 18:30:38 +01:00
{
dbgln ( " (STUBBED) SVGGraphicsElement::get_screen_ctm(). Called on: {} " , debug_description ( ) ) ;
return Geometry : : DOMMatrix : : create ( realm ( ) ) ;
}
2025-05-09 12:30:56 +02:00
GC : : Ptr < Geometry : : DOMMatrix > SVGGraphicsElement : : get_ctm ( )
{
dbgln ( " (STUBBED) SVGGraphicsElement::get_ctm(). Called on: {} " , debug_description ( ) ) ;
return Geometry : : DOMMatrix : : create ( realm ( ) ) ;
}
2020-07-22 15:17:39 -07:00
}