LibWeb/SVG: Implement SVGAnimatedInteger
This is basically the same as SVGAnimatedNumber, but stores an i32 instead.
This commit is contained in:
parent
66e9e2de4b
commit
a89b02e5c3
10 changed files with 280 additions and 15 deletions
|
|
@ -931,6 +931,7 @@ set(SOURCES
|
|||
SVG/Path.cpp
|
||||
SVG/SVGAElement.cpp
|
||||
SVG/SVGAnimatedEnumeration.cpp
|
||||
SVG/SVGAnimatedInteger.cpp
|
||||
SVG/SVGAnimatedLength.cpp
|
||||
SVG/SVGAnimatedLengthList.cpp
|
||||
SVG/SVGAnimatedNumber.cpp
|
||||
|
|
|
|||
|
|
@ -1125,6 +1125,7 @@ namespace Web::SVG {
|
|||
|
||||
class Path;
|
||||
class SVGAnimatedEnumeration;
|
||||
class SVGAnimatedInteger;
|
||||
class SVGAnimatedLength;
|
||||
class SVGAnimatedLengthList;
|
||||
class SVGAnimatedNumber;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,20 @@ Optional<float> AttributeParser::parse_length(StringView input)
|
|||
return {};
|
||||
}
|
||||
|
||||
Optional<i32> AttributeParser::parse_integer(StringView input)
|
||||
{
|
||||
AttributeParser parser { input };
|
||||
parser.parse_whitespace();
|
||||
auto result_or_error = parser.parse_integer();
|
||||
if (result_or_error.is_error())
|
||||
return {};
|
||||
parser.parse_whitespace();
|
||||
if (parser.done())
|
||||
return result_or_error.value();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
float NumberPercentage::resolve_relative_to(float length) const
|
||||
{
|
||||
if (!m_is_percentage)
|
||||
|
|
@ -302,6 +316,20 @@ ErrorOr<float> AttributeParser::parse_coordinate()
|
|||
return parse_length();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/SVG11/types.html#DataTypeInteger
|
||||
ErrorOr<i32> AttributeParser::parse_integer()
|
||||
{
|
||||
if (!match_integer())
|
||||
return Error::from_string_literal("Expected integer");
|
||||
|
||||
auto parse_result = AK::parse_first_number<i32>(m_lexer.remaining(), TrimWhitespace::No);
|
||||
if (!parse_result.has_value())
|
||||
return Error::from_string_literal("Integer out of range");
|
||||
|
||||
m_lexer.ignore(parse_result->characters_parsed);
|
||||
return parse_result->value;
|
||||
}
|
||||
|
||||
ErrorOr<Vector<float>> AttributeParser::parse_coordinate_pair()
|
||||
{
|
||||
Vector<float> coordinates;
|
||||
|
|
@ -779,15 +807,20 @@ bool AttributeParser::match_comma_whitespace() const
|
|||
|
||||
bool AttributeParser::match_coordinate() const
|
||||
{
|
||||
return match_length();
|
||||
return match_length(AllowDot::Yes);
|
||||
}
|
||||
|
||||
bool AttributeParser::match_number() const
|
||||
{
|
||||
return match_length();
|
||||
return match_length(AllowDot::Yes);
|
||||
}
|
||||
|
||||
bool AttributeParser::match_length() const
|
||||
bool AttributeParser::match_integer() const
|
||||
{
|
||||
return match_length(AllowDot::No);
|
||||
}
|
||||
|
||||
bool AttributeParser::match_length(AllowDot allow_dot) const
|
||||
{
|
||||
if (done())
|
||||
return false;
|
||||
|
|
@ -796,7 +829,7 @@ bool AttributeParser::match_length() const
|
|||
if (ch() == '-' || ch() == '+')
|
||||
offset++;
|
||||
|
||||
if (ch(offset) == '.')
|
||||
if (allow_dot == AllowDot::Yes && ch(offset) == '.')
|
||||
offset++;
|
||||
|
||||
return !done() && is_ascii_digit(ch(offset));
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ public:
|
|||
|
||||
static Optional<float> parse_coordinate(StringView input);
|
||||
static Optional<float> parse_length(StringView input);
|
||||
static Optional<i32> parse_integer(StringView input);
|
||||
static Optional<NumberPercentage> parse_number_percentage(StringView input);
|
||||
static Optional<float> parse_positive_length(StringView input);
|
||||
static Vector<Gfx::FloatPoint> parse_points(StringView input);
|
||||
|
|
@ -171,6 +172,7 @@ private:
|
|||
|
||||
ErrorOr<float> parse_length();
|
||||
ErrorOr<float> parse_coordinate();
|
||||
ErrorOr<i32> parse_integer();
|
||||
ErrorOr<Vector<float>> parse_coordinate_pair();
|
||||
ErrorOr<Vector<float>> parse_coordinate_sequence();
|
||||
ErrorOr<Vector<Vector<float>>> parse_coordinate_pair_sequence();
|
||||
|
|
@ -185,11 +187,17 @@ private:
|
|||
// -1 if negative, +1 otherwise
|
||||
int parse_sign();
|
||||
|
||||
enum class AllowDot {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
bool match_whitespace() const;
|
||||
bool match_comma_whitespace() const;
|
||||
bool match_coordinate() const;
|
||||
bool match_length() const;
|
||||
bool match_length(AllowDot allow_dot) const;
|
||||
bool match_number() const;
|
||||
bool match_integer() const;
|
||||
bool match(char c) const { return !done() && ch() == c; }
|
||||
|
||||
bool done() const { return m_lexer.is_eof(); }
|
||||
|
|
|
|||
157
Libraries/LibWeb/SVG/SVGAnimatedInteger.cpp
Normal file
157
Libraries/LibWeb/SVG/SVGAnimatedInteger.cpp
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
* Copyright (c) 2026, Luke Wilde <luke@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/SVGAnimatedIntegerPrototype.h>
|
||||
#include <LibWeb/SVG/AttributeParser.h>
|
||||
#include <LibWeb/SVG/SVGAnimatedInteger.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(SVGAnimatedInteger);
|
||||
|
||||
GC::Ref<SVGAnimatedInteger> SVGAnimatedInteger::create(JS::Realm& realm, GC::Ref<SVGElement> element,
|
||||
DOM::QualifiedName reflected_attribute, WebIDL::Long initial_value, SupportsSecondValue supports_second_value,
|
||||
ValueRepresented value_represented)
|
||||
{
|
||||
return realm.create<SVGAnimatedInteger>(realm, element, move(reflected_attribute), initial_value,
|
||||
supports_second_value, value_represented);
|
||||
}
|
||||
|
||||
SVGAnimatedInteger::SVGAnimatedInteger(JS::Realm& realm, GC::Ref<SVGElement> element, DOM::QualifiedName reflected_attribute,
|
||||
WebIDL::Long initial_value, SupportsSecondValue supports_second_value, ValueRepresented value_represented)
|
||||
: PlatformObject(realm)
|
||||
, m_element(element)
|
||||
, m_reflected_attribute(move(reflected_attribute))
|
||||
, m_initial_value(initial_value)
|
||||
, m_supports_second_value(supports_second_value)
|
||||
, m_value_represented(value_represented)
|
||||
{
|
||||
}
|
||||
|
||||
SVGAnimatedInteger::~SVGAnimatedInteger() = default;
|
||||
|
||||
// https://svgwg.org/svg2-draft/types.html#__svg__SVGAnimatedInteger__baseVal
|
||||
WebIDL::Long SVGAnimatedInteger::base_val() const
|
||||
{
|
||||
// On getting baseVal or animVal, the following steps are run:
|
||||
return get_base_or_anim_value();
|
||||
}
|
||||
|
||||
// https://svgwg.org/svg2-draft/types.html#__svg__SVGAnimatedInteger__baseVal
|
||||
void SVGAnimatedInteger::set_base_val(WebIDL::Long new_value)
|
||||
{
|
||||
// 1. Let value be the value being assigned to baseVal.
|
||||
auto value = new_value;
|
||||
|
||||
// 2. Let new be a list of integers.
|
||||
Vector<WebIDL::Long, 2> new_;
|
||||
|
||||
// 3. If the reflected attribute is defined to take an integer followed by an optional second integer, then:
|
||||
if (m_supports_second_value == SupportsSecondValue::Yes) {
|
||||
// 1. Let current be the value of the reflected attribute (using the attribute's initial value if it is not
|
||||
// present or invalid).
|
||||
auto current = m_element->get_attribute_value(m_reflected_attribute.local_name(), m_reflected_attribute.namespace_());
|
||||
auto current_values = MUST(current.split(' '));
|
||||
|
||||
// 2. Let first be the first integer in current.
|
||||
auto first = current_values.size() > 0 ? parse_value_or_initial(current_values[0]) : m_initial_value;
|
||||
|
||||
// 3. Let second be the second integer in current if it has been explicitly specified, and if not, the implicit
|
||||
// value as described in the definition of the attribute.
|
||||
// NB: All known usages of <number-optional-number> specify that a missing second number defaults to the value
|
||||
// of the first number.
|
||||
auto second = current_values.size() > 1 && !current_values[1].is_empty()
|
||||
? parse_value_or_initial(current_values[1])
|
||||
: first;
|
||||
|
||||
// 4. If this SVGAnimatedInteger object reflects the first integer, then set first to value. Otherwise, set second
|
||||
// to value.
|
||||
if (m_value_represented == ValueRepresented::First)
|
||||
first = value;
|
||||
else
|
||||
second = value;
|
||||
|
||||
// 5. Append first to new.
|
||||
new_.unchecked_append(first);
|
||||
|
||||
// 6. Append second to new.
|
||||
new_.unchecked_append(second);
|
||||
}
|
||||
|
||||
// 4. Otherwise, the reflected attribute is defined to take a single integer value. Append value to new.
|
||||
else {
|
||||
new_.unchecked_append(value);
|
||||
}
|
||||
|
||||
// 5. Set the content attribute to a string consisting of each integer in new serialized to an implementation
|
||||
// specific string that, if parsed as an <number> using CSS syntax, would return that integer,
|
||||
// joined and separated by a single U+0020 SPACE character.
|
||||
auto new_attribute_value = MUST(String::join(' ', new_));
|
||||
m_element->set_attribute_value(m_reflected_attribute.local_name(), new_attribute_value, m_reflected_attribute.prefix(), m_reflected_attribute.namespace_());
|
||||
}
|
||||
|
||||
// https://svgwg.org/svg2-draft/types.html#__svg__SVGAnimatedInteger__animVal
|
||||
WebIDL::Long SVGAnimatedInteger::anim_val() const
|
||||
{
|
||||
// On getting baseVal or animVal, the following steps are run:
|
||||
return get_base_or_anim_value();
|
||||
}
|
||||
|
||||
WebIDL::Long SVGAnimatedInteger::parse_value_or_initial(StringView number_value) const
|
||||
{
|
||||
auto value = AttributeParser::parse_integer(number_value);
|
||||
if (!value.has_value())
|
||||
return m_initial_value;
|
||||
return value.release_value();
|
||||
}
|
||||
|
||||
// https://svgwg.org/svg2-draft/types.html#__svg__SVGAnimatedInteger__baseVal
|
||||
WebIDL::Long SVGAnimatedInteger::get_base_or_anim_value() const
|
||||
{
|
||||
// 1. Let value be the value of the reflected attribute (using the attribute's initial value if it is not present or
|
||||
// invalid).
|
||||
auto value = m_element->get_attribute_value(m_reflected_attribute.local_name(), m_reflected_attribute.namespace_());
|
||||
|
||||
// 2. If the reflected attribute is defined to take an integer followed by an optional second integer, then:
|
||||
if (m_supports_second_value == SupportsSecondValue::Yes) {
|
||||
// 1. If this SVGAnimatedInteger object reflects the first integer, then return the first value in value.
|
||||
auto values = MUST(value.split(' '));
|
||||
if (values.is_empty())
|
||||
return m_initial_value;
|
||||
if (m_value_represented == ValueRepresented::First)
|
||||
return parse_value_or_initial(values[0]);
|
||||
|
||||
// 2. Otherwise, this SVGAnimatedInteger object reflects the second integer. Return the second value in value if
|
||||
// it has been explicitly specified, and if not, return the implicit value as described in the definition of
|
||||
// the attribute.
|
||||
// NB: All known usages of <number-optional-number> specify that a missing second number defaults to the value
|
||||
// of the first number.
|
||||
VERIFY(m_value_represented == ValueRepresented::Second);
|
||||
if (values.size() > 1 && !values[1].is_empty())
|
||||
return parse_value_or_initial(values[1]);
|
||||
return parse_value_or_initial(values[0]);
|
||||
}
|
||||
|
||||
// 3. Otherwise, the reflected attribute is defined to take a single integer value. Return value.
|
||||
return parse_value_or_initial(value);
|
||||
}
|
||||
|
||||
void SVGAnimatedInteger::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGAnimatedInteger);
|
||||
Base::initialize(realm);
|
||||
}
|
||||
|
||||
void SVGAnimatedInteger::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_element);
|
||||
}
|
||||
|
||||
}
|
||||
57
Libraries/LibWeb/SVG/SVGAnimatedInteger.h
Normal file
57
Libraries/LibWeb/SVG/SVGAnimatedInteger.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
* Copyright (c) 2026, Luke Wilde <luke@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/SVG/SVGElement.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
// https://svgwg.org/svg2-draft/types.html#InterfaceSVGAnimatedInteger
|
||||
class SVGAnimatedInteger final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(SVGAnimatedInteger, Bindings::PlatformObject);
|
||||
GC_DECLARE_ALLOCATOR(SVGAnimatedInteger);
|
||||
|
||||
public:
|
||||
enum class SupportsSecondValue : u8 {
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
enum class ValueRepresented : u8 {
|
||||
First,
|
||||
Second,
|
||||
};
|
||||
|
||||
[[nodiscard]] static GC::Ref<SVGAnimatedInteger> create(JS::Realm&, GC::Ref<SVGElement>,
|
||||
DOM::QualifiedName reflected_attribute, WebIDL::Long initial_value, SupportsSecondValue = SupportsSecondValue::No,
|
||||
ValueRepresented = ValueRepresented::First);
|
||||
virtual ~SVGAnimatedInteger() override;
|
||||
|
||||
WebIDL::Long base_val() const;
|
||||
void set_base_val(WebIDL::Long);
|
||||
|
||||
WebIDL::Long anim_val() const;
|
||||
|
||||
private:
|
||||
SVGAnimatedInteger(JS::Realm&, GC::Ref<SVGElement>, DOM::QualifiedName, WebIDL::Long, SupportsSecondValue, ValueRepresented);
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
WebIDL::Long parse_value_or_initial(StringView) const;
|
||||
WebIDL::Long get_base_or_anim_value() const;
|
||||
|
||||
GC::Ref<SVGElement> m_element;
|
||||
DOM::QualifiedName m_reflected_attribute;
|
||||
WebIDL::Long m_initial_value;
|
||||
SupportsSecondValue m_supports_second_value;
|
||||
ValueRepresented m_value_represented;
|
||||
};
|
||||
|
||||
}
|
||||
6
Libraries/LibWeb/SVG/SVGAnimatedInteger.idl
Normal file
6
Libraries/LibWeb/SVG/SVGAnimatedInteger.idl
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// https://svgwg.org/svg2-draft/types.html#InterfaceSVGAnimatedInteger
|
||||
[Exposed=Window]
|
||||
interface SVGAnimatedInteger {
|
||||
attribute long baseVal;
|
||||
readonly attribute long animVal;
|
||||
};
|
||||
|
|
@ -386,6 +386,7 @@ libweb_js_bindings(TrustedTypes/TrustedTypePolicy)
|
|||
libweb_js_bindings(TrustedTypes/TrustedTypePolicyFactory)
|
||||
libweb_js_bindings(SVG/SVGAElement)
|
||||
libweb_js_bindings(SVG/SVGAnimatedEnumeration)
|
||||
libweb_js_bindings(SVG/SVGAnimatedInteger)
|
||||
libweb_js_bindings(SVG/SVGAnimatedLength)
|
||||
libweb_js_bindings(SVG/SVGAnimatedLengthList)
|
||||
libweb_js_bindings(SVG/SVGAnimatedNumber)
|
||||
|
|
|
|||
|
|
@ -369,6 +369,7 @@ ResizeObserverSize
|
|||
Response
|
||||
SVGAElement
|
||||
SVGAnimatedEnumeration
|
||||
SVGAnimatedInteger
|
||||
SVGAnimatedLength
|
||||
SVGAnimatedLengthList
|
||||
SVGAnimatedNumber
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ Harness status: OK
|
|||
|
||||
Found 1781 tests
|
||||
|
||||
1068 Pass
|
||||
713 Fail
|
||||
1076 Pass
|
||||
705 Fail
|
||||
Pass idl_test setup
|
||||
Pass idl_test validation
|
||||
Pass Partial interface Document: original interface defined
|
||||
|
|
@ -290,14 +290,14 @@ Fail SVGAnimatedEnumeration must be primary interface of objects.text.lengthAdju
|
|||
Fail Stringification of objects.text.lengthAdjust
|
||||
Fail SVGAnimatedEnumeration interface: objects.text.lengthAdjust must inherit property "baseVal" with the proper type
|
||||
Fail SVGAnimatedEnumeration interface: objects.text.lengthAdjust must inherit property "animVal" with the proper type
|
||||
Fail SVGAnimatedInteger interface: existence and properties of interface object
|
||||
Fail SVGAnimatedInteger interface object length
|
||||
Fail SVGAnimatedInteger interface object name
|
||||
Fail SVGAnimatedInteger interface: existence and properties of interface prototype object
|
||||
Fail SVGAnimatedInteger interface: existence and properties of interface prototype object's "constructor" property
|
||||
Fail SVGAnimatedInteger interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Fail SVGAnimatedInteger interface: attribute baseVal
|
||||
Fail SVGAnimatedInteger interface: attribute animVal
|
||||
Pass SVGAnimatedInteger interface: existence and properties of interface object
|
||||
Pass SVGAnimatedInteger interface object length
|
||||
Pass SVGAnimatedInteger interface object name
|
||||
Pass SVGAnimatedInteger interface: existence and properties of interface prototype object
|
||||
Pass SVGAnimatedInteger interface: existence and properties of interface prototype object's "constructor" property
|
||||
Pass SVGAnimatedInteger interface: existence and properties of interface prototype object's @@unscopables property
|
||||
Pass SVGAnimatedInteger interface: attribute baseVal
|
||||
Pass SVGAnimatedInteger interface: attribute animVal
|
||||
Fail SVGAnimatedInteger must be primary interface of objects.feConvolveMatrix.orderX
|
||||
Fail Stringification of objects.feConvolveMatrix.orderX
|
||||
Fail SVGAnimatedInteger interface: objects.feConvolveMatrix.orderX must inherit property "baseVal" with the proper type
|
||||
|
|
|
|||
Loading…
Reference in a new issue