2020-07-22 19:17:39 -03:00
/*
2021-04-22 20:53:07 -03:00
* Copyright ( c ) 2020 , Matthew Olsson < mattco @ serenityos . org >
2022-01-24 12:43:44 -03:00
* Copyright ( c ) 2021 - 2022 , Sam Atkins < atkinssj @ serenityos . org >
2023-04-10 08:25:40 -03:00
* Copyright ( c ) 2023 , MacDue < macdue @ dueutil . tech >
2024-10-04 08:19:50 -03:00
* Copyright ( c ) 2023 , Andreas Kling < andreas @ ladybird . org >
2020-07-22 19:17:39 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-07-22 19:17:39 -03:00
*/
2022-09-30 20:16:16 -03:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-04-26 21:09:58 -03:00
# include <LibWeb/Bindings/SVGGraphicsElementPrototype.h>
2022-01-24 12:43:44 -03:00
# include <LibWeb/CSS/Parser/Parser.h>
2023-04-22 21:31:17 -03:00
# include <LibWeb/DOM/Document.h>
2022-04-10 15:16:47 -03:00
# include <LibWeb/Layout/Node.h>
2024-06-13 09:22:46 -03:00
# include <LibWeb/Painting/PaintStyle.h>
2024-04-20 19:10:26 -03:00
# include <LibWeb/Painting/PaintableBox.h>
# include <LibWeb/Painting/SVGGraphicsPaintable.h>
2023-06-20 07:57:54 -03:00
# include <LibWeb/SVG/AttributeNames.h>
2023-04-10 08:25:40 -03:00
# include <LibWeb/SVG/AttributeParser.h>
2024-03-26 21:06:35 -03:00
# include <LibWeb/SVG/SVGClipPathElement.h>
2023-04-22 21:31:17 -03:00
# include <LibWeb/SVG/SVGGradientElement.h>
2020-07-23 13:44:42 -03:00
# include <LibWeb/SVG/SVGGraphicsElement.h>
2023-09-10 10:10:55 -03:00
# include <LibWeb/SVG/SVGMaskElement.h>
2022-04-10 15:16:47 -03:00
# include <LibWeb/SVG/SVGSVGElement.h>
2023-07-31 13:37:44 -03:00
# include <LibWeb/SVG/SVGSymbolElement.h>
2020-07-22 19:17:39 -03:00
2020-07-23 13:44:42 -03:00
namespace Web : : SVG {
2020-07-22 19:17:39 -03:00
2022-02-18 17:00:52 -03:00
SVGGraphicsElement : : SVGGraphicsElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 07:20:15 -03:00
: SVGElement ( document , move ( qualified_name ) )
2020-07-22 19:17:39 -03:00
{
2023-01-10 08:28:20 -03:00
}
2023-08-07 03:41:28 -03:00
void SVGGraphicsElement : : initialize ( JS : : Realm & realm )
2023-01-10 08:28:20 -03:00
{
2024-03-16 09:13:08 -03:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( SVGGraphicsElement ) ;
2025-04-20 11:22:57 -03:00
Base : : initialize ( realm ) ;
2020-07-22 19:17:39 -03:00
}
2024-11-14 10:14:16 -03:00
void SVGGraphicsElement : : attribute_changed ( FlyString const & name , Optional < String > const & old_value , Optional < String > const & value , Optional < FlyString > const & namespace_ )
2023-04-10 08:25:00 -03:00
{
2024-11-14 10:14:16 -03:00
Base : : attribute_changed ( name , old_value , value , namespace_ ) ;
2023-05-19 16:35:39 -03:00
if ( name = = " transform " sv ) {
2023-11-19 02:10:36 -03:00
auto transform_list = AttributeParser : : parse_transform ( value . value_or ( String { } ) ) ;
2023-04-10 08:25:40 -03:00
if ( transform_list . has_value ( ) )
m_transform = transform_from_transform_list ( * transform_list ) ;
2025-04-19 05:49:33 -03:00
set_needs_layout_tree_update ( true , DOM : : SetNeedsLayoutTreeUpdateReason : : SVGGraphicsElementTransformChange ) ;
2023-04-10 08:25:00 -03:00
}
}
2024-06-13 09: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-22 21:31:17 -03:00
{
// FIXME: This entire function is an ad-hoc hack:
2023-06-06 16:40:10 -03:00
if ( ! paint_value . has_value ( ) | | ! paint_value - > is_url ( ) )
2023-04-22 21:31:17 -03:00
return { } ;
2023-09-10 10:10:55 -03: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-22 21:31:17 -03:00
return { } ;
}
2024-06-13 09:22:46 -03:00
Optional < Painting : : PaintStyle > SVGGraphicsElement : : fill_paint_style ( SVGPaintContext const & paint_context ) const
2023-06-06 16:40:10 -03:00
{
if ( ! layout_node ( ) )
return { } ;
return svg_paint_computed_value_to_gfx_paint_style ( paint_context , layout_node ( ) - > computed_values ( ) . fill ( ) ) ;
}
2024-06-13 09:22:46 -03:00
Optional < Painting : : PaintStyle > SVGGraphicsElement : : stroke_paint_style ( SVGPaintContext const & paint_context ) const
2023-06-06 16:40:10 -03:00
{
if ( ! layout_node ( ) )
return { } ;
return svg_paint_computed_value_to_gfx_paint_style ( paint_context , layout_node ( ) - > computed_values ( ) . stroke ( ) ) ;
}
2024-11-14 12:01:23 -03:00
GC : : Ptr < SVG : : SVGMaskElement const > SVGGraphicsElement : : mask ( ) const
2023-09-10 10:10:55 -03: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-14 12:01:23 -03:00
GC : : Ptr < SVG : : SVGClipPathElement const > SVGGraphicsElement : : clip_path ( ) const
2024-03-26 21:06:35 -03:00
{
auto const & clip_path_reference = layout_node ( ) - > computed_values ( ) . clip_path ( ) ;
2024-05-25 20:25:05 -03:00
if ( ! clip_path_reference . has_value ( ) | | ! clip_path_reference - > is_url ( ) )
2024-03-26 21:06:35 -03:00
return { } ;
return try_resolve_url_to < SVG : : SVGClipPathElement const > ( clip_path_reference - > url ( ) ) ;
}
2023-04-12 14:32:21 -03:00
Gfx : : AffineTransform transform_from_transform_list ( ReadonlySpan < Transform > transform_list )
2023-04-10 08:25:40 -03:00
{
Gfx : : AffineTransform affine_transform ;
2023-04-12 14:32:21 -03:00
for ( auto & transform : transform_list ) {
transform . operation . visit (
2023-04-10 08:25:40 -03: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 09:43:39 -03:00
. rotate_radians ( AK : : to_radians ( rotate . a ) )
2023-04-10 08:25:40 -03:00
. translate ( { - rotate . x , - rotate . y } ) ) ;
} ,
[ & ] ( Transform : : SkewX const & skew_x ) {
2023-09-09 09:43:39 -03:00
affine_transform . multiply ( Gfx : : AffineTransform { } . skew_radians ( AK : : to_radians ( skew_x . a ) , 0 ) ) ;
2023-04-10 08:25:40 -03:00
} ,
[ & ] ( Transform : : SkewY const & skew_y ) {
2023-09-09 09:43:39 -03:00
affine_transform . multiply ( Gfx : : AffineTransform { } . skew_radians ( 0 , AK : : to_radians ( skew_y . a ) ) ) ;
2023-04-10 08:25:40 -03: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 14:27:35 -03: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 04:58:41 -03:00
transform = Gfx : : AffineTransform { svg_ancestor - > element_transform ( ) } . multiply ( transform ) ;
2023-04-10 08:25:40 -03:00
}
return transform ;
}
2024-05-12 16:19:43 -03:00
static FillRule to_svg_fill_rule ( CSS : : FillRule fill_rule )
2023-06-11 12:43:46 -03:00
{
2024-05-12 16:19:43 -03:00
switch ( fill_rule ) {
2023-06-11 12:43:46 -03:00
case CSS : : FillRule : : Nonzero :
return FillRule : : Nonzero ;
case CSS : : FillRule : : Evenodd :
return FillRule : : Evenodd ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2024-05-12 16:19:43 -03: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 08:28:14 -03:00
Optional < Gfx : : Color > SVGGraphicsElement : : fill_color ( ) const
{
if ( ! layout_node ( ) )
return { } ;
2025-08-20 16:31:56 -03: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 08:28:14 -03:00
}
Optional < Gfx : : Color > SVGGraphicsElement : : stroke_color ( ) const
{
if ( ! layout_node ( ) )
return { } ;
2025-08-20 16:41:17 -03: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 08:28:14 -03:00
}
2023-05-19 16:35:39 -03:00
Optional < float > SVGGraphicsElement : : fill_opacity ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . fill_opacity ( ) ;
}
2025-08-24 11:05:57 -03:00
CSS : : PaintOrderList SVGGraphicsElement : : paint_order ( ) const
{
if ( ! layout_node ( ) )
return CSS : : InitialValues : : paint_order ( ) ;
return layout_node ( ) - > computed_values ( ) . paint_order ( ) ;
}
2024-10-10 11:15:49 -03:00
Optional < CSS : : StrokeLinecap > SVGGraphicsElement : : stroke_linecap ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . stroke_linecap ( ) ;
}
2024-10-28 21:51:16 -03: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 16:35:39 -03:00
Optional < float > SVGGraphicsElement : : stroke_opacity ( ) const
{
if ( ! layout_node ( ) )
return { } ;
return layout_node ( ) - > computed_values ( ) . stroke_opacity ( ) ;
}
2024-11-18 23:21:22 -03:00
float SVGGraphicsElement : : resolve_relative_to_viewport_size ( CSS : : LengthPercentage const & length_percentage ) const
2021-09-16 08:28:14 -03: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 12:51:17 -03: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 07:32:44 -03:00
if ( auto svg_svg_layout_node = svg_svg_element - > layout_node ( ) ) {
2023-06-15 12:51:17 -03: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 15:16:47 -03:00
}
2022-01-14 13:52:14 -03:00
}
2023-08-26 11:03:04 -03:00
auto scaled_viewport_size = ( viewport_width + viewport_height ) * CSSPixels ( 0.5 ) ;
2024-11-18 23:21:22 -03:00
return length_percentage . to_px ( * layout_node ( ) , scaled_viewport_size ) . to_double ( ) ;
}
2024-11-20 21:23:10 -03: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 12:00:13 -03: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 21:23:10 -03: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 23:21:22 -03: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 08:28:14 -03:00
}
2024-09-06 19:38:13 -03:00
// https://svgwg.org/svg2-draft/types.html#__svg__SVGGraphicsElement__getBBox
2024-11-14 12:01:23 -03:00
GC : : Ref < Geometry : : DOMRect > SVGGraphicsElement : : get_b_box ( Optional < SVGBoundingBoxOptions > )
2024-03-31 20:49:02 -03:00
{
2024-04-20 19:10:26 -03: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 16:50:05 -03:00
const_cast < DOM : : Document & > ( document ( ) ) . update_layout ( DOM : : UpdateLayoutReason : : SVGGraphicsElementGetBBox ) ;
2024-04-20 19:10:26 -03:00
if ( ! layout_node ( ) )
return Geometry : : DOMRect : : create ( realm ( ) ) ;
// Invert the SVG -> screen space transform.
2024-09-06 19:38:13 -03: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 19:10:26 -03:00
auto inverse_transform = static_cast < Painting : : SVGGraphicsPaintable & > ( * paintable_box ( ) ) . computed_transforms ( ) . svg_to_css_pixels_transform ( ) . inverse ( ) ;
2024-07-21 19:29:06 -03: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-03-31 20:49:02 -03:00
}
2024-11-14 12:01:23 -03:00
GC : : Ref < SVGAnimatedTransformList > SVGGraphicsElement : : transform ( ) const
2024-03-31 20:49:02 -03: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-14 12:01:23 -03:00
GC : : Ptr < Geometry : : DOMMatrix > SVGGraphicsElement : : get_screen_ctm ( )
2024-11-09 14:30:38 -03:00
{
dbgln ( " (STUBBED) SVGGraphicsElement::get_screen_ctm(). Called on: {} " , debug_description ( ) ) ;
return Geometry : : DOMMatrix : : create ( realm ( ) ) ;
}
2025-05-09 07:30:56 -03: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 19:17:39 -03:00
}