fix(svg): implement marker orientation angle APIs

This commit is contained in:
ldm0
2026-09-27 19:23:03 +08:00
parent fbcd9ee5cb
commit 162f21a1bd
11 changed files with 1164 additions and 0 deletions
@@ -103,6 +103,7 @@ pub fn svg_element_interface_name(local_name: &str) -> &'static str {
"image" => "SVGImageElement",
"line" => "SVGLineElement",
"linearGradient" => "SVGLinearGradientElement",
"marker" => "SVGMarkerElement",
"mask" => "SVGMaskElement",
"metadata" => "SVGMetadataElement",
"path" => "SVGPathElement",
+1
View File
@@ -277,6 +277,7 @@ fn svg_element_interface_name_specializes_standard_elements() {
"SVGLinearGradientElement"
);
assert_eq!(svg_element_interface_name("metadata"), "SVGMetadataElement");
assert_eq!(svg_element_interface_name("marker"), "SVGMarkerElement");
assert_eq!(svg_element_interface_name("mask"), "SVGMaskElement");
assert_eq!(svg_element_interface_name("path"), "SVGPathElement");
assert_eq!(svg_element_interface_name("pattern"), "SVGPatternElement");
@@ -179,6 +179,7 @@ const NODE_BRIDGE_DESCRIPTORS: &[BridgeDescriptor] = &[
ELEMENT_GROUPS,
),
descriptor(web_api_interfaces::SVGMaskElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGMarkerElement::DESCRIPTOR, ELEMENT_GROUPS),
specialized_descriptor(
web_api_interfaces::SVGPathElement::DESCRIPTOR,
ELEMENT_GROUPS,
@@ -1725,10 +1725,12 @@ pub(crate) fn finish_context_bootstrap(
("XPathEvaluator", "XPathEvaluator"),
("XPathResult", "XPathResult"),
("SVGLength", "SVGLength"),
("SVGAngle", "SVGAngle"),
("SVGNumber", "SVGNumber"),
("SVGRect", "SVGRect"),
("SVGAnimatedString", "SVGAnimatedString"),
("SVGAnimatedLength", "SVGAnimatedLength"),
("SVGAnimatedAngle", "SVGAnimatedAngle"),
("SVGLengthList", "SVGLengthList"),
("SVGAnimatedLengthList", "SVGAnimatedLengthList"),
("SVGAnimatedNumber", "SVGAnimatedNumber"),
@@ -88,6 +88,10 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
interface: web_api_interfaces::SVGLength::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SVGAngle::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SVGNumber::DESCRIPTOR,
kind: ConstructorKind::Illegal,
@@ -104,6 +108,10 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
interface: web_api_interfaces::SVGAnimatedLength::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SVGAnimatedAngle::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SVGLengthList::DESCRIPTOR,
kind: ConstructorKind::Illegal,
@@ -58,6 +58,42 @@ struct SvgLengthTemplateMethodsDeclaration {
convert_to_specified_units: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGAngle, enumerable)]
struct SvgAngleTemplateMethodsDeclaration {
#[webapi(constant = "SVG_ANGLETYPE_UNKNOWN", value = SVG_ANGLE_TYPE_UNKNOWN)]
angle_type_unknown: (),
#[webapi(
constant = "SVG_ANGLETYPE_UNSPECIFIED",
value = SVG_ANGLE_TYPE_UNSPECIFIED
)]
angle_type_unspecified: (),
#[webapi(constant = "SVG_ANGLETYPE_DEG", value = SVG_ANGLE_TYPE_DEG)]
angle_type_deg: (),
#[webapi(constant = "SVG_ANGLETYPE_RAD", value = SVG_ANGLE_TYPE_RAD)]
angle_type_rad: (),
#[webapi(constant = "SVG_ANGLETYPE_GRAD", value = SVG_ANGLE_TYPE_GRAD)]
angle_type_grad: (),
#[webapi(
method = "newValueSpecifiedUnits",
length = 2,
callback = svg_angle_new_value_specified_units_callback
)]
new_value_specified_units: (),
#[webapi(
method = "convertToSpecifiedUnits",
length = 1,
callback = svg_angle_convert_to_specified_units_callback
)]
convert_to_specified_units: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGLengthList, enumerable)]
struct SvgLengthListTemplateMethodsDeclaration {
@@ -525,6 +561,66 @@ struct SvgGradientElementTemplateConstantsDeclaration {
spread_method_repeat: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGMarkerElement, enumerable)]
struct SvgMarkerElementTemplateMethodsDeclaration {
#[webapi(
constant = "SVG_MARKERUNITS_UNKNOWN",
value = SVG_MARKER_UNITS_UNKNOWN
)]
marker_units_unknown: (),
#[webapi(
constant = "SVG_MARKERUNITS_USERSPACEONUSE",
value = SVG_MARKER_UNITS_USER_SPACE_ON_USE
)]
marker_units_user_space_on_use: (),
#[webapi(
constant = "SVG_MARKERUNITS_STROKEWIDTH",
value = SVG_MARKER_UNITS_STROKE_WIDTH
)]
marker_units_stroke_width: (),
#[webapi(
constant = "SVG_MARKER_ORIENT_UNKNOWN",
value = SVG_MARKER_ORIENT_UNKNOWN
)]
marker_orient_unknown: (),
#[webapi(
constant = "SVG_MARKER_ORIENT_AUTO",
value = SVG_MARKER_ORIENT_AUTO
)]
marker_orient_auto: (),
#[webapi(
constant = "SVG_MARKER_ORIENT_ANGLE",
value = SVG_MARKER_ORIENT_ANGLE
)]
marker_orient_angle: (),
#[webapi(
constant = "SVG_MARKER_ORIENT_AUTO_START_REVERSE",
value = SVG_MARKER_ORIENT_AUTO_START_REVERSE
)]
marker_orient_auto_start_reverse: (),
#[webapi(
method = "setOrientToAuto",
length = 0,
callback = svg_marker_set_orient_to_auto_callback
)]
set_orient_to_auto: (),
#[webapi(
method = "setOrientToAngle",
length = 1,
callback = svg_marker_set_orient_to_angle_callback
)]
set_orient_to_angle: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGSVGElement, enumerable)]
struct SvgSvgElementTemplateMethodsDeclaration {
@@ -542,6 +638,13 @@ struct SvgSvgElementTemplateMethodsDeclaration {
)]
deselect_all: (),
#[webapi(
method = "createSVGAngle",
length = 0,
callback = svg_svg_element_create_angle_callback
)]
create_svg_angle: (),
#[webapi(
method = "createSVGMatrix",
length = 0,
@@ -599,6 +702,41 @@ struct SvgLengthTemplateAccessorsDeclaration {
value_as_string: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGAngle, enumerable)]
struct SvgAngleTemplateAccessorsDeclaration {
#[webapi(
accessor_property = "unitType",
getter = svg_angle_getter,
data = callback_data_index_value(scope, 0)
)]
unit_type: (),
#[webapi(
accessor_property = "value",
getter = svg_angle_getter,
setter = svg_angle_setter,
data = callback_data_index_value(scope, 1)
)]
value: (),
#[webapi(
accessor_property = "valueInSpecifiedUnits",
getter = svg_angle_getter,
setter = svg_angle_setter,
data = callback_data_index_value(scope, 2)
)]
value_in_specified_units: (),
#[webapi(
accessor_property = "valueAsString",
getter = svg_angle_getter,
setter = svg_angle_setter,
data = callback_data_index_value(scope, 3)
)]
value_as_string: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGAnimatedString, enumerable)]
struct SvgAnimatedStringTemplateAccessorsDeclaration {
@@ -655,6 +793,24 @@ struct SvgAnimatedLengthTemplateAccessorsDeclaration {
anim_val: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGAnimatedAngle, enumerable)]
struct SvgAnimatedAngleTemplateAccessorsDeclaration {
#[webapi(
accessor_property = "baseVal",
getter = svg_animated_angle_getter,
data = callback_data_index_value(scope, 0)
)]
base_val: (),
#[webapi(
accessor_property = "animVal",
getter = svg_animated_angle_getter,
data = callback_data_index_value(scope, 1)
)]
anim_val: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGNumber, enumerable)]
struct SvgNumberTemplateAccessorsDeclaration {
@@ -1140,6 +1296,30 @@ struct SvgGraphicsBoxElementPrototypeAccessorsDeclaration {
height: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGMarkerElement, enumerable)]
struct SvgMarkerElementPrototypeAccessorsDeclaration {
#[webapi(
accessor_property = "markerUnits",
getter = svg_element_animated_enumeration_getter,
data = callback_data_index_value(scope, 10)
)]
marker_units: (),
#[webapi(
accessor_property = "orientType",
getter = svg_element_animated_enumeration_getter,
data = callback_data_index_value(scope, 11)
)]
orient_type: (),
#[webapi(
accessor_property = "orientAngle",
getter = svg_marker_orient_angle_getter
)]
orient_angle: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(name = "SVGElement", enumerable)]
struct SvgElementPrototypeAccessorsDeclaration {
@@ -1173,6 +1353,16 @@ pub(super) fn install_svg_length_bindings<'s>(
SvgLengthTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_angle_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
) {
let proto = template.prototype_template(scope);
SvgAngleTemplateMethodsDeclaration::initialize_template(scope, template);
SvgAngleTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
SvgAngleTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_animated_length_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
@@ -1181,6 +1371,14 @@ pub(super) fn install_svg_animated_length_bindings<'s>(
SvgAnimatedLengthTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_animated_angle_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
) {
let proto = template.prototype_template(scope);
SvgAnimatedAngleTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_number_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
@@ -1359,6 +1557,15 @@ pub(super) fn install_svg_text_content_element_bindings<'s>(
SvgTextContentElementTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_marker_element_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
) {
let proto = template.prototype_template(scope);
SvgMarkerElementTemplateMethodsDeclaration::initialize_template(scope, template);
SvgMarkerElementTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_svg_element_bindings(
scope: &mut v8::PinScope<'_, '_, ()>,
template: v8::Local<'_, v8::FunctionTemplate>,
@@ -1452,6 +1659,11 @@ pub(super) fn install_svg_element_accessor_bindings<'s>(
scope, prototype,
);
}
"SVGMarkerElement" => {
SvgMarkerElementPrototypeAccessorsDeclaration::initialize_prototype_template(
scope, prototype,
);
}
"SVGElement" => {
SvgElementPrototypeAccessorsDeclaration::initialize_prototype_template(
scope, prototype,
@@ -118,6 +118,18 @@ struct SvgAnimatedLengthObjectDeclaration<'scope> {
anim_val: v8::Local<'scope, v8::Object>,
}
#[derive(WebApiObject)]
#[webapi(
interface = web_api_interfaces::SVGAnimatedAngle,
fallback_to_string_tag = "SVGAnimatedAngle"
)]
struct SvgAnimatedAngleObjectDeclaration<'scope> {
#[webapi(slot = SVG_ANIMATED_ANGLE_BASE_VAL_SLOT)]
base_val: v8::Local<'scope, v8::Object>,
#[webapi(slot = SVG_ANIMATED_ANGLE_ANIM_VAL_SLOT)]
anim_val: v8::Local<'scope, v8::Object>,
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::SVGLength, fallback_to_string_tag = "SVGLength")]
struct SvgLengthObjectDeclaration {
@@ -129,6 +141,21 @@ struct SvgLengthObjectDeclaration {
value_as_string: String,
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::SVGAngle, fallback_to_string_tag = "SVGAngle")]
struct SvgAngleObjectDeclaration {
#[webapi(slot = SVG_ANGLE_UNIT_TYPE_SLOT)]
unit_type: u32,
#[webapi(slot = SVG_ANGLE_VALUE_SLOT)]
value: f64,
#[webapi(slot = SVG_ANGLE_VALUE_IN_SPECIFIED_UNITS_SLOT)]
value_in_specified_units: f64,
#[webapi(slot = SVG_ANGLE_VALUE_AS_STRING_SLOT)]
value_as_string: String,
#[webapi(slot = SVG_ANGLE_READ_ONLY_SLOT)]
read_only: bool,
}
#[derive(WebApiObject)]
#[webapi(
interface = web_api_interfaces::SVGAnimatedLengthList,
@@ -633,6 +660,21 @@ pub(super) fn parse_svg_animated_enumeration(
(SvgAnimatedEnumerationKind::LengthAdjust, "spacingAndGlyphs") => {
Some(SVG_LENGTH_ADJUST_SPACING_AND_GLYPHS)
}
(SvgAnimatedEnumerationKind::MarkerUnits, "userSpaceOnUse") => {
Some(SVG_MARKER_UNITS_USER_SPACE_ON_USE)
}
(SvgAnimatedEnumerationKind::MarkerUnits, "strokeWidth") => {
Some(SVG_MARKER_UNITS_STROKE_WIDTH)
}
(SvgAnimatedEnumerationKind::MarkerOrient, "auto") => Some(SVG_MARKER_ORIENT_AUTO),
(SvgAnimatedEnumerationKind::MarkerOrient, "auto-start-reverse") => {
Some(SVG_MARKER_ORIENT_AUTO_START_REVERSE)
}
(SvgAnimatedEnumerationKind::MarkerOrient, value)
if parse_svg_angle_value(value).is_some() =>
{
Some(SVG_MARKER_ORIENT_ANGLE)
}
_ => None,
}
}
@@ -655,6 +697,17 @@ pub(super) fn serialize_svg_animated_enumeration(
(SvgAnimatedEnumerationKind::LengthAdjust, SVG_LENGTH_ADJUST_SPACING_AND_GLYPHS) => {
Some("spacingAndGlyphs")
}
(SvgAnimatedEnumerationKind::MarkerUnits, SVG_MARKER_UNITS_USER_SPACE_ON_USE) => {
Some("userSpaceOnUse")
}
(SvgAnimatedEnumerationKind::MarkerUnits, SVG_MARKER_UNITS_STROKE_WIDTH) => {
Some("strokeWidth")
}
(SvgAnimatedEnumerationKind::MarkerOrient, SVG_MARKER_ORIENT_AUTO) => Some("auto"),
(SvgAnimatedEnumerationKind::MarkerOrient, SVG_MARKER_ORIENT_ANGLE) => Some("0"),
(SvgAnimatedEnumerationKind::MarkerOrient, SVG_MARKER_ORIENT_AUTO_START_REVERSE) => {
Some("auto-start-reverse")
}
_ => None,
}
}
@@ -919,6 +972,44 @@ pub(super) fn build_svg_animated_length_for_attribute<'s>(
.expect("SVGAnimatedLength declaration should bind")
}
pub(super) fn build_svg_animated_angle_for_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
owner: v8::Local<'s, v8::Object>,
attribute: &str,
) -> v8::Local<'s, v8::Object> {
let parsed = svg_owner_attribute_value(scope, owner, attribute)
.as_deref()
.and_then(parse_svg_orient_angle_value)
.unwrap_or_default();
let base_val = build_svg_angle_from_parsed(scope, &parsed, false);
set_svg_angle_owner_attribute(scope, base_val, owner, attribute);
let anim_val = build_svg_angle_from_parsed(scope, &parsed, true);
set_svg_angle_owner_attribute(scope, anim_val, owner, attribute);
SvgAnimatedAngleObjectDeclaration::new(base_val, anim_val)
.bind(scope)
.expect("SVGAnimatedAngle declaration should bind")
}
pub(super) fn build_svg_angle<'s>(scope: &mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Object> {
build_svg_angle_from_parsed(scope, &SvgParsedAngle::default(), false)
}
pub(super) fn build_svg_angle_from_parsed<'s>(
scope: &mut v8::PinScope<'s, '_>,
parsed: &SvgParsedAngle,
read_only: bool,
) -> v8::Local<'s, v8::Object> {
SvgAngleObjectDeclaration::new(
parsed.unit_type,
parsed.value,
parsed.value_in_specified_units,
parsed.value_as_string.clone(),
read_only,
)
.bind(scope)
.expect("SVGAngle declaration should bind")
}
pub(super) fn build_svg_length<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: f64,
@@ -2150,6 +2241,309 @@ pub(super) fn svg_animated_length_attribute_slot(name: &str) -> &'static str {
}
}
#[derive(Clone, Debug)]
pub(super) struct SvgParsedAngle {
value: f64,
value_in_specified_units: f64,
unit_type: u32,
value_as_string: String,
}
impl Default for SvgParsedAngle {
fn default() -> Self {
Self {
value: 0.0,
value_in_specified_units: 0.0,
unit_type: SVG_ANGLE_TYPE_UNSPECIFIED,
value_as_string: "0".to_owned(),
}
}
}
pub(super) fn parse_svg_orient_angle_value(raw: &str) -> Option<SvgParsedAngle> {
if matches!(
raw.trim().to_ascii_lowercase().as_str(),
"auto" | "auto-start-reverse"
) {
return Some(SvgParsedAngle::default());
}
parse_svg_angle_value(raw)
}
pub(super) fn parse_svg_angle_value(raw: &str) -> Option<SvgParsedAngle> {
let raw = raw.trim();
if raw.is_empty() {
return None;
}
let lowercase = raw.to_ascii_lowercase();
let (number_raw, unit_type, degrees_per_unit) = if lowercase.ends_with("deg") {
(&raw[..raw.len() - 3], SVG_ANGLE_TYPE_DEG, 1.0)
} else if lowercase.ends_with("grad") {
(&raw[..raw.len() - 4], SVG_ANGLE_TYPE_GRAD, 0.9)
} else if lowercase.ends_with("rad") {
(
&raw[..raw.len() - 3],
SVG_ANGLE_TYPE_RAD,
180.0 / std::f64::consts::PI,
)
} else if lowercase.ends_with("turn") {
(&raw[..raw.len() - 4], SVG_ANGLE_TYPE_UNKNOWN, 360.0)
} else {
(raw, SVG_ANGLE_TYPE_UNSPECIFIED, 1.0)
};
let value_in_specified_units = moli_css_parse::parse_number(number_raw.trim())?;
Some(SvgParsedAngle {
value: value_in_specified_units * degrees_per_unit,
value_in_specified_units,
unit_type,
value_as_string: raw.to_owned(),
})
}
pub(super) fn svg_angle_number_slot<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
slot: &str,
) -> Option<f64> {
get_private_value(scope, object, slot)?.number_value(scope)
}
pub(super) fn svg_angle_string_slot<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
slot: &str,
) -> Option<String> {
get_private_value(scope, object, slot)
.and_then(|value| value.to_string(scope))
.map(|value| value.to_rust_string_lossy(scope))
}
pub(super) fn svg_angle_is_read_only<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
) -> bool {
get_private_value(scope, object, SVG_ANGLE_READ_ONLY_SLOT).is_some_and(|value| value.is_true())
}
pub(super) fn set_svg_angle_parsed_value(
scope: &mut v8::PinScope<'_, '_>,
object: v8::Local<'_, v8::Object>,
parsed: &SvgParsedAngle,
) {
set_private_value(
scope,
object,
SVG_ANGLE_UNIT_TYPE_SLOT,
v8::Integer::new_from_unsigned(scope, parsed.unit_type).into(),
);
set_private_value(
scope,
object,
SVG_ANGLE_VALUE_SLOT,
v8::Number::new(scope, parsed.value).into(),
);
set_private_value(
scope,
object,
SVG_ANGLE_VALUE_IN_SPECIFIED_UNITS_SLOT,
v8::Number::new(scope, parsed.value_in_specified_units).into(),
);
set_private_value(
scope,
object,
SVG_ANGLE_VALUE_AS_STRING_SLOT,
v8_string(scope, &parsed.value_as_string)
.unwrap_or_else(|| v8str(scope, "0"))
.into(),
);
}
fn svg_angle_current_unit<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
) -> (u32, bool) {
let unit_type = svg_angle_number_slot(scope, object, SVG_ANGLE_UNIT_TYPE_SLOT)
.unwrap_or(SVG_ANGLE_TYPE_UNSPECIFIED as f64) as u32;
let is_turn = unit_type == SVG_ANGLE_TYPE_UNKNOWN
&& svg_angle_string_slot(scope, object, SVG_ANGLE_VALUE_AS_STRING_SLOT)
.is_some_and(|value| value.trim().to_ascii_lowercase().ends_with("turn"));
(unit_type, is_turn)
}
fn svg_angle_degrees_per_unit(unit_type: u32, is_turn: bool) -> f64 {
match unit_type {
SVG_ANGLE_TYPE_RAD => 180.0 / std::f64::consts::PI,
SVG_ANGLE_TYPE_GRAD => 0.9,
SVG_ANGLE_TYPE_UNKNOWN if is_turn => 360.0,
_ => 1.0,
}
}
fn svg_angle_unit_suffix(unit_type: u32, is_turn: bool) -> &'static str {
match unit_type {
SVG_ANGLE_TYPE_DEG => "deg",
SVG_ANGLE_TYPE_RAD => "rad",
SVG_ANGLE_TYPE_GRAD => "grad",
SVG_ANGLE_TYPE_UNKNOWN if is_turn => "turn",
_ => "",
}
}
fn svg_angle_from_specified_value(
value_in_specified_units: f64,
unit_type: u32,
is_turn: bool,
) -> SvgParsedAngle {
SvgParsedAngle {
value: value_in_specified_units * svg_angle_degrees_per_unit(unit_type, is_turn),
value_in_specified_units,
unit_type,
value_as_string: format!(
"{}{}",
svg_geometry::serialize_number(value_in_specified_units),
svg_angle_unit_suffix(unit_type, is_turn)
),
}
}
pub(super) fn set_svg_angle_value_degrees<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
value: f64,
) {
let (unit_type, is_turn) = svg_angle_current_unit(scope, object);
let specified = value / svg_angle_degrees_per_unit(unit_type, is_turn);
let parsed = svg_angle_from_specified_value(specified, unit_type, is_turn);
set_svg_angle_parsed_value(scope, object, &parsed);
}
pub(super) fn set_svg_angle_value_in_specified_units<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
value: f64,
) {
let (unit_type, is_turn) = svg_angle_current_unit(scope, object);
let parsed = svg_angle_from_specified_value(value, unit_type, is_turn);
set_svg_angle_parsed_value(scope, object, &parsed);
}
pub(super) fn set_svg_angle_new_value<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
unit_type: u32,
value: f64,
) -> bool {
if !(SVG_ANGLE_TYPE_UNSPECIFIED..=SVG_ANGLE_TYPE_GRAD).contains(&unit_type) {
return false;
}
let parsed = svg_angle_from_specified_value(value, unit_type, false);
set_svg_angle_parsed_value(scope, object, &parsed);
true
}
pub(super) fn convert_svg_angle_to_unit<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
unit_type: u32,
) -> bool {
if !(SVG_ANGLE_TYPE_UNSPECIFIED..=SVG_ANGLE_TYPE_GRAD).contains(&unit_type) {
return false;
}
let value = svg_angle_number_slot(scope, object, SVG_ANGLE_VALUE_SLOT).unwrap_or(0.0);
let specified = value / svg_angle_degrees_per_unit(unit_type, false);
let parsed = svg_angle_from_specified_value(specified, unit_type, false);
set_svg_angle_parsed_value(scope, object, &parsed);
true
}
pub(super) fn set_svg_angle_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
angle: v8::Local<'s, v8::Object>,
owner: v8::Local<'s, v8::Object>,
attribute: &str,
) {
set_private_value(scope, angle, SVG_ANGLE_OWNER_ELEMENT_SLOT, owner.into());
set_private_value(
scope,
angle,
SVG_ANGLE_OWNER_ATTRIBUTE_SLOT,
v8_string(scope, attribute)
.unwrap_or_else(|| v8str(scope, ""))
.into(),
);
}
pub(super) fn sync_svg_angle_from_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
angle: v8::Local<'s, v8::Object>,
) {
let Some(owner) = get_private_value(scope, angle, SVG_ANGLE_OWNER_ELEMENT_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let Some(attribute) = svg_angle_string_slot(scope, angle, SVG_ANGLE_OWNER_ATTRIBUTE_SLOT)
.filter(|value| !value.is_empty())
else {
return;
};
let parsed = svg_owner_attribute_value(scope, owner, &attribute)
.as_deref()
.and_then(parse_svg_orient_angle_value)
.unwrap_or_default();
set_svg_angle_parsed_value(scope, angle, &parsed);
}
pub(super) fn sync_svg_animated_angle_from_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
owner: v8::Local<'s, v8::Object>,
attribute: &str,
) {
let parsed = svg_owner_attribute_value(scope, owner, attribute)
.as_deref()
.and_then(parse_svg_orient_angle_value)
.unwrap_or_default();
if let Some(base_val) = get_private_value(scope, animated, SVG_ANIMATED_ANGLE_BASE_VAL_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
{
set_svg_angle_parsed_value(scope, base_val, &parsed);
set_svg_angle_owner_attribute(scope, base_val, owner, attribute);
}
if let Some(anim_val) = get_private_value(scope, animated, SVG_ANIMATED_ANGLE_ANIM_VAL_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
{
set_svg_angle_parsed_value(scope, anim_val, &parsed);
set_svg_angle_owner_attribute(scope, anim_val, owner, attribute);
}
}
pub(super) fn reflect_svg_angle_to_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
angle: v8::Local<'s, v8::Object>,
) {
let Some(owner) = get_private_value(scope, angle, SVG_ANGLE_OWNER_ELEMENT_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let Some(attribute) = svg_angle_string_slot(scope, angle, SVG_ANGLE_OWNER_ATTRIBUTE_SLOT)
.filter(|value| !value.is_empty())
else {
return;
};
let Some(value) = svg_angle_string_slot(scope, angle, SVG_ANGLE_VALUE_AS_STRING_SLOT) else {
return;
};
let Ok((runtime_ptr, handle)) =
crate::native_bridge::node_runtime_and_handle_from_object(scope, owner)
else {
return;
};
let runtime = unsafe { &mut *runtime_ptr };
let _ = runtime.set_attribute(scope, runtime_ptr, handle, &attribute, &value);
}
#[derive(Clone, Copy)]
pub(super) struct SvgParsedLength {
value: f64,
@@ -388,6 +388,112 @@ fn svg_element_animated_length_getter<'s>(
rv.set(value.into());
}
fn svg_marker_runtime_and_handle<'s>(
scope: &mut v8::PinScope<'s, '_>,
receiver: v8::Local<'s, v8::Object>,
member: &str,
) -> Option<(
*mut crate::native_bridge::JsContextHost,
crate::document_runtime::DomHandle,
)> {
let Ok((runtime_ptr, handle)) =
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, receiver)
else {
webidl::throw_type_error(
scope,
&format!("SVGMarkerElement.{member} called on incompatible receiver."),
);
return None;
};
let is_marker = unsafe { &*runtime_ptr }
.dom_host()
.node(handle)
.and_then(|node| node.as_element())
.is_some_and(|element| element.is_svg_element("marker"));
if !is_marker {
webidl::throw_type_error(
scope,
&format!("SVGMarkerElement.{member} called on incompatible receiver."),
);
return None;
}
Some((runtime_ptr, handle))
}
pub(super) fn svg_marker_orient_angle_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let owner = args.this();
if svg_marker_runtime_and_handle(scope, owner, "orientAngle").is_none() {
return;
}
if let Some(value) = get_private_value(scope, owner, SVG_MARKER_ORIENT_ANGLE_SLOT) {
if let Ok(animated) = v8::Local::<v8::Object>::try_from(value) {
sync_svg_animated_angle_from_owner_attribute(scope, animated, owner, "orient");
}
rv.set(value);
return;
}
let value = build_svg_animated_angle_for_attribute(scope, owner, "orient");
set_private_value(scope, owner, SVG_MARKER_ORIENT_ANGLE_SLOT, value.into());
rv.set(value.into());
}
fn set_svg_marker_orient_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
marker: v8::Local<'s, v8::Object>,
member: &str,
value: &str,
) -> bool {
let Some((runtime_ptr, handle)) = svg_marker_runtime_and_handle(scope, marker, member) else {
return false;
};
let runtime = unsafe { &mut *runtime_ptr };
let _ = runtime.set_attribute(scope, runtime_ptr, handle, "orient", value);
true
}
pub(super) fn svg_marker_set_orient_to_auto_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if set_svg_marker_orient_attribute(scope, args.this(), "setOrientToAuto", "auto") {
rv.set_undefined();
}
}
pub(super) fn svg_marker_set_orient_to_angle_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Ok(angle) = v8::Local::<v8::Object>::try_from(args.get(0)) else {
webidl::throw_type_error(
scope,
"SVGMarkerElement.setOrientToAngle requires an SVGAngle.",
);
return;
};
if !require_svg_receiver(
scope,
angle,
SVG_ANGLE_UNIT_TYPE_SLOT,
"SVGAngle",
"setOrientToAngle argument",
) {
return;
}
sync_svg_angle_from_owner_attribute(scope, angle);
let value = svg_angle_string_slot(scope, angle, SVG_ANGLE_VALUE_AS_STRING_SLOT)
.unwrap_or_else(|| "0".to_owned());
if set_svg_marker_orient_attribute(scope, args.this(), "setOrientToAngle", &value) {
rv.set_undefined();
}
}
pub(super) fn svg_graphics_transform_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -791,6 +897,42 @@ pub(super) fn svg_animated_length_getter<'s>(
);
}
pub(super) fn svg_animated_angle_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(name) = callback_data_item(
scope,
&args,
SVG_ANIMATED_ACCESSOR_NAMES,
"SVGAnimatedAngle attributes",
) else {
rv.set_undefined();
return;
};
if !require_svg_receiver(
scope,
args.this(),
SVG_ANIMATED_ANGLE_BASE_VAL_SLOT,
"SVGAnimatedAngle",
&format!("{name} getter"),
) {
return;
}
let slot = match name {
"baseVal" => SVG_ANIMATED_ANGLE_BASE_VAL_SLOT,
"animVal" => SVG_ANIMATED_ANGLE_ANIM_VAL_SLOT,
_ => {
rv.set_undefined();
return;
}
};
rv.set(
get_private_value(scope, args.this(), slot).unwrap_or_else(|| v8::undefined(scope).into()),
);
}
pub(super) fn svg_animated_length_list_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -864,6 +1006,129 @@ pub(super) fn svg_length_getter<'s>(
}
}
pub(super) fn svg_angle_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(name) = callback_data_item(
scope,
&args,
SVG_LENGTH_ACCESSOR_NAMES,
"SVGAngle attributes",
) else {
rv.set_undefined();
return;
};
if !require_svg_receiver(
scope,
args.this(),
SVG_ANGLE_UNIT_TYPE_SLOT,
"SVGAngle",
&format!("{name} getter"),
) {
return;
}
sync_svg_angle_from_owner_attribute(scope, args.this());
match name {
"unitType" => {
let value = svg_angle_number_slot(scope, args.this(), SVG_ANGLE_UNIT_TYPE_SLOT)
.unwrap_or(SVG_ANGLE_TYPE_UNSPECIFIED as f64);
rv.set(v8::Integer::new_from_unsigned(scope, value as u32).into());
}
"value" => {
let value =
svg_angle_number_slot(scope, args.this(), SVG_ANGLE_VALUE_SLOT).unwrap_or(0.0);
rv.set(v8::Number::new(scope, value).into());
}
"valueInSpecifiedUnits" => {
let value =
svg_angle_number_slot(scope, args.this(), SVG_ANGLE_VALUE_IN_SPECIFIED_UNITS_SLOT)
.unwrap_or(0.0);
rv.set(v8::Number::new(scope, value).into());
}
"valueAsString" => rv.set(
get_private_value(scope, args.this(), SVG_ANGLE_VALUE_AS_STRING_SLOT)
.unwrap_or_else(|| v8str(scope, "0").into()),
),
_ => rv.set_undefined(),
}
}
pub(super) fn svg_angle_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(name) = callback_data_item(
scope,
&args,
SVG_LENGTH_ACCESSOR_NAMES,
"SVGAngle attributes",
) else {
return;
};
if !require_svg_receiver(
scope,
args.this(),
SVG_ANGLE_UNIT_TYPE_SLOT,
"SVGAngle",
&format!("{name} setter"),
) {
return;
}
if svg_angle_is_read_only(scope, args.this()) {
throw_dom_exception(
scope,
"NoModificationAllowedError",
7,
"The SVG angle is read-only.",
);
return;
}
sync_svg_angle_from_owner_attribute(scope, args.this());
match name {
"value" | "valueInSpecifiedUnits" => {
let value = match webidl::convert::<webidl::Double>(
scope,
args.get(0),
webidl::Context::member("SVGAngle", name),
) {
Ok(value) => value.0,
Err(error) => {
webidl::throw_error(scope, &error);
return;
}
};
if name == "value" {
set_svg_angle_value_degrees(scope, args.this(), value);
} else {
set_svg_angle_value_in_specified_units(scope, args.this(), value);
}
}
"valueAsString" => {
let value = match webidl::convert::<webidl::DomString>(
scope,
args.get(0),
webidl::Context::member("SVGAngle", "valueAsString"),
) {
Ok(value) => value.0,
Err(error) => {
webidl::throw_error(scope, &error);
return;
}
};
let Some(parsed) = parse_svg_angle_value(&value) else {
throw_dom_exception(scope, "SyntaxError", 12, "Invalid SVG angle value.");
return;
};
set_svg_angle_parsed_value(scope, args.this(), &parsed);
}
_ => return,
}
reflect_svg_angle_to_owner_attribute(scope, args.this());
}
pub(super) fn svg_length_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -2118,6 +2383,14 @@ pub(super) fn svg_svg_element_create_matrix_callback<'s>(
rv.set(super::super::geometry_runtime::build_dom_matrix_identity_object(scope).into());
}
pub(super) fn svg_svg_element_create_angle_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
_args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(build_svg_angle(scope).into());
}
pub(super) fn svg_svg_element_deselect_all_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -2483,6 +2756,46 @@ pub(super) fn svg_length_new_value_specified_units_callback<'s>(
rv.set_undefined();
}
pub(super) fn svg_angle_new_value_specified_units_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !require_svg_receiver(
scope,
args.this(),
SVG_ANGLE_UNIT_TYPE_SLOT,
"SVGAngle",
"newValueSpecifiedUnits",
) {
return;
}
if svg_angle_is_read_only(scope, args.this()) {
throw_dom_exception(
scope,
"NoModificationAllowedError",
7,
"The SVG angle is read-only.",
);
return;
}
let Some(parsed) = webidl::parse_args::<SvgAngleNewValueSpecifiedUnitsArgs>(scope, &args)
else {
return;
};
if !set_svg_angle_new_value(scope, args.this(), parsed.unit_type as u32, parsed.value) {
throw_dom_exception(
scope,
"NotSupportedError",
9,
"The SVG angle unit type is not supported.",
);
return;
}
reflect_svg_angle_to_owner_attribute(scope, args.this());
rv.set_undefined();
}
pub(super) fn svg_length_convert_to_specified_units_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -2498,6 +2811,47 @@ pub(super) fn svg_length_convert_to_specified_units_callback<'s>(
rv.set_undefined();
}
pub(super) fn svg_angle_convert_to_specified_units_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !require_svg_receiver(
scope,
args.this(),
SVG_ANGLE_UNIT_TYPE_SLOT,
"SVGAngle",
"convertToSpecifiedUnits",
) {
return;
}
if svg_angle_is_read_only(scope, args.this()) {
throw_dom_exception(
scope,
"NoModificationAllowedError",
7,
"The SVG angle is read-only.",
);
return;
}
sync_svg_angle_from_owner_attribute(scope, args.this());
let Some(parsed) = webidl::parse_args::<SvgAngleConvertToSpecifiedUnitsArgs>(scope, &args)
else {
return;
};
if !convert_svg_angle_to_unit(scope, args.this(), parsed.unit_type as u32) {
throw_dom_exception(
scope,
"NotSupportedError",
9,
"The SVG angle unit type is not supported.",
);
return;
}
reflect_svg_angle_to_owner_attribute(scope, args.this());
rv.set_undefined();
}
pub(super) fn svg_graphics_get_bbox_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -38,6 +38,18 @@ const SVG_ANIMATED_BOOLEAN_INITIAL_VALUE_SLOT: &str = "__moliSvgAnimatedBooleanI
const SVG_FE_CONVOLVE_MATRIX_PRESERVE_ALPHA_SLOT: &str = "__moliSvgFeConvolveMatrixPreserveAlpha";
const SVG_ANIMATED_LENGTH_BASE_VAL_SLOT: &str = "__moliSvgAnimatedLengthBaseVal";
const SVG_ANIMATED_LENGTH_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedLengthAnimVal";
const SVG_MARKER_ORIENT_ANGLE_SLOT: &str = "__moliSvgMarkerOrientAngle";
const SVG_MARKER_UNITS_SLOT: &str = "__moliSvgMarkerUnits";
const SVG_MARKER_ORIENT_TYPE_SLOT: &str = "__moliSvgMarkerOrientType";
const SVG_ANIMATED_ANGLE_BASE_VAL_SLOT: &str = "__moliSvgAnimatedAngleBaseVal";
const SVG_ANIMATED_ANGLE_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedAngleAnimVal";
const SVG_ANGLE_OWNER_ELEMENT_SLOT: &str = "__moliSvgAngleOwnerElement";
const SVG_ANGLE_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgAngleOwnerAttribute";
const SVG_ANGLE_UNIT_TYPE_SLOT: &str = "__moliSvgAngleUnitType";
const SVG_ANGLE_VALUE_SLOT: &str = "__moliSvgAngleValue";
const SVG_ANGLE_VALUE_IN_SPECIFIED_UNITS_SLOT: &str = "__moliSvgAngleValueInSpecifiedUnits";
const SVG_ANGLE_VALUE_AS_STRING_SLOT: &str = "__moliSvgAngleValueAsString";
const SVG_ANGLE_READ_ONLY_SLOT: &str = "__moliSvgAngleReadOnly";
const SVG_ANIMATED_LENGTH_LIST_BASE_VAL_SLOT: &str = "__moliSvgAnimatedLengthListBaseVal";
const SVG_ANIMATED_LENGTH_LIST_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedLengthListAnimVal";
const SVG_LENGTH_LIST_ITEMS_SLOT: &str = "__moliSvgLengthListItems";
@@ -120,6 +132,8 @@ enum SvgAnimatedEnumerationKind {
UnitType,
SpreadMethod,
LengthAdjust,
MarkerUnits,
MarkerOrient,
}
#[derive(Clone, Copy)]
@@ -247,6 +261,22 @@ struct SvgLengthConvertToSpecifiedUnitsArgs {
unit_type: u16,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "SVG angle newValueSpecifiedUnits")]
struct SvgAngleNewValueSpecifiedUnitsArgs {
#[webidl(required, converter = "unsigned_short")]
unit_type: u16,
#[webidl(required, converter = "double")]
value: f64,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "SVG angle convertToSpecifiedUnits")]
struct SvgAngleConvertToSpecifiedUnitsArgs {
#[webidl(required, converter = "unsigned_short")]
unit_type: u16,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "SVGGeometryElement.getPointAtLength")]
struct SvgGeometryPointAtLengthArgs {
@@ -282,6 +312,21 @@ const SVG_LENGTH_TYPE_IN: u32 = 8;
const SVG_LENGTH_TYPE_PT: u32 = 9;
const SVG_LENGTH_TYPE_PC: u32 = 10;
const SVG_ANGLE_TYPE_UNKNOWN: u32 = 0;
const SVG_ANGLE_TYPE_UNSPECIFIED: u32 = 1;
const SVG_ANGLE_TYPE_DEG: u32 = 2;
const SVG_ANGLE_TYPE_RAD: u32 = 3;
const SVG_ANGLE_TYPE_GRAD: u32 = 4;
const SVG_MARKER_UNITS_UNKNOWN: u32 = 0;
const SVG_MARKER_UNITS_USER_SPACE_ON_USE: u32 = 1;
const SVG_MARKER_UNITS_STROKE_WIDTH: u32 = 2;
const SVG_MARKER_ORIENT_UNKNOWN: u32 = 0;
const SVG_MARKER_ORIENT_AUTO: u32 = 1;
const SVG_MARKER_ORIENT_ANGLE: u32 = 2;
const SVG_MARKER_ORIENT_AUTO_START_REVERSE: u32 = 3;
const SVG_TRANSFORM_TYPE_UNKNOWN: u32 = 0;
const SVG_TRANSFORM_TYPE_MATRIX: u32 = 1;
const SVG_TRANSFORM_TYPE_TRANSLATE: u32 = 2;
@@ -374,6 +419,20 @@ const SVG_ANIMATED_ENUMERATION_PROPERTIES: &[SvgAnimatedEnumerationProperty] = &
initial_value: SVG_LENGTH_ADJUST_SPACING,
kind: SvgAnimatedEnumerationKind::LengthAdjust,
},
SvgAnimatedEnumerationProperty {
index: 10,
attribute: "markerUnits",
cache_slot: SVG_MARKER_UNITS_SLOT,
initial_value: SVG_MARKER_UNITS_STROKE_WIDTH,
kind: SvgAnimatedEnumerationKind::MarkerUnits,
},
SvgAnimatedEnumerationProperty {
index: 11,
attribute: "orient",
cache_slot: SVG_MARKER_ORIENT_TYPE_SLOT,
initial_value: SVG_MARKER_ORIENT_ANGLE,
kind: SvgAnimatedEnumerationKind::MarkerOrient,
},
];
pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
@@ -384,11 +443,13 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
bindings::install_svg_element_accessor_bindings(scope, template, name);
match name {
"SVGLength" => bindings::install_svg_length_bindings(scope, template),
"SVGAngle" => bindings::install_svg_angle_bindings(scope, template),
"SVGNumber" => bindings::install_svg_number_bindings(scope, template),
"SVGRect" => rect::install_bindings(scope, template),
"SVGAnimatedString" => bindings::install_svg_animated_string_bindings(scope, template),
"SVGAnimatedBoolean" => bindings::install_svg_animated_boolean_bindings(scope, template),
"SVGAnimatedLength" => bindings::install_svg_animated_length_bindings(scope, template),
"SVGAnimatedAngle" => bindings::install_svg_animated_angle_bindings(scope, template),
"SVGLengthList" => bindings::install_svg_length_list_bindings(scope, template),
"SVGAnimatedLengthList" => {
bindings::install_svg_animated_length_list_bindings(scope, template)
@@ -415,6 +476,7 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
bindings::install_svg_text_content_element_bindings(scope, template)
}
"SVGGradientElement" => bindings::install_svg_gradient_element_bindings(scope, template),
"SVGMarkerElement" => bindings::install_svg_marker_element_bindings(scope, template),
"SVGSVGElement" => bindings::install_svg_svg_element_bindings(scope, template),
_ => {}
}
@@ -1968,6 +1968,132 @@ fn svg_animated_boolean_reflects_preserve_alpha() {
assert_eq!(result, "ok");
}
#[test]
fn svg_marker_orient_angle_is_a_live_animated_angle() {
let mut vm = new_parsed_test_vm(
"https://svg-animated-angle.test/",
"<!doctype html><html><body></body></html>",
);
let result = vm
.eval(
r#"
(() => {
const assert = (condition, message) => {
if (!condition) throw new Error(message);
};
const close = (actual, expected) => Math.abs(actual - expected) < 1e-9;
const ns = "http://www.w3.org/2000/svg";
const marker = document.createElementNS(ns, "marker");
for (const constructor of [SVGMarkerElement, SVGAnimatedAngle, SVGAngle]) {
assert(typeof constructor === "function", `${constructor.name} constructor`);
let illegalConstructor = false;
try {
new constructor();
} catch (error) {
illegalConstructor = error instanceof TypeError;
}
assert(illegalConstructor, `${constructor.name} illegal constructor`);
}
assert(marker instanceof SVGMarkerElement, "marker interface");
assert(Object.getPrototypeOf(SVGMarkerElement.prototype) === SVGElement.prototype,
"marker prototype parent");
const markerDescriptor = Object.getOwnPropertyDescriptor(
SVGMarkerElement.prototype,
"orientAngle",
);
assert(typeof markerDescriptor.get === "function" &&
markerDescriptor.set === undefined, "orientAngle readonly descriptor");
assert(markerDescriptor.enumerable && markerDescriptor.configurable,
"orientAngle descriptor flags");
const animated = marker.orientAngle;
const base = animated.baseVal;
const anim = animated.animVal;
assert(animated instanceof SVGAnimatedAngle, "animated angle interface");
assert(base instanceof SVGAngle && anim instanceof SVGAngle, "angle interfaces");
assert(base !== anim, "base and animated values are distinct");
assert(marker.orientAngle === animated, "orientAngle SameObject");
assert(base.value === 0 && anim.value === 0, "initial values");
assert(base.unitType === SVGAngle.SVG_ANGLETYPE_UNSPECIFIED, "initial unit");
assert(!marker.hasAttribute("orient"), "getter does not create attribute");
assert(marker.orientType instanceof SVGAnimatedEnumeration, "orientType interface");
assert(marker.orientType.baseVal === SVGMarkerElement.SVG_MARKER_ORIENT_ANGLE,
"initial orientType");
assert(marker.markerUnits instanceof SVGAnimatedEnumeration,
"markerUnits interface");
assert(marker.markerUnits.baseVal ===
SVGMarkerElement.SVG_MARKERUNITS_STROKEWIDTH, "initial markerUnits");
marker.markerUnits.baseVal = SVGMarkerElement.SVG_MARKERUNITS_USERSPACEONUSE;
assert(marker.getAttribute("markerUnits") === "userSpaceOnUse",
"markerUnits reflection");
base.value = 100;
assert(base.value === 100 && marker.orientAngle.baseVal.value === 100,
"cached base value is live");
assert(marker.getAttribute("orient") === "100", "value reflects to orient");
marker.orientAngle.baseVal = -1;
assert(marker.orientAngle.baseVal === base && base.value === 100,
"baseVal assignment is ignored");
marker.setAttribute("orient", "1.5707963267948966rad");
assert(close(base.value, 90), "content attribute converts to degrees");
assert(base.unitType === SVGAngle.SVG_ANGLETYPE_RAD, "content attribute unit");
assert(close(base.valueInSpecifiedUnits, Math.PI / 2), "specified value");
assert(anim.unitType === SVGAngle.SVG_ANGLETYPE_RAD && close(anim.value, 90),
"animVal tracks content attribute");
assert(marker.orientType.baseVal === SVGMarkerElement.SVG_MARKER_ORIENT_ANGLE,
"angle orientType");
marker.setOrientToAuto();
assert(marker.getAttribute("orient") === "auto", "setOrientToAuto reflection");
assert(marker.orientType.baseVal === SVGMarkerElement.SVG_MARKER_ORIENT_AUTO,
"automatic orientType");
assert(base.value === 0 && base.unitType === SVGAngle.SVG_ANGLETYPE_UNSPECIFIED,
"automatic orient angle");
marker.setAttribute("orient", "400grad");
assert(base.value === 360 && base.unitType === SVGAngle.SVG_ANGLETYPE_GRAD,
"grad content attribute");
const svg = document.createElementNS(ns, "svg");
const standalone = svg.createSVGAngle();
assert(standalone instanceof SVGAngle, "createSVGAngle result");
standalone.newValueSpecifiedUnits(SVGAngle.SVG_ANGLETYPE_RAD, Math.PI);
assert(close(standalone.value, 180), "newValueSpecifiedUnits conversion");
standalone.convertToSpecifiedUnits(SVGAngle.SVG_ANGLETYPE_GRAD);
assert(close(standalone.valueInSpecifiedUnits, 200), "unit conversion");
assert(standalone.valueAsString === "200grad", "unit serialization");
marker.setOrientToAngle(standalone);
assert(marker.getAttribute("orient") === "200grad", "setOrientToAngle reflection");
assert(marker.orientAngle.baseVal.value === 180, "setOrientToAngle value");
let readonlyAnim = false;
try {
anim.value = 1;
} catch (error) {
readonlyAnim = error.name === "NoModificationAllowedError";
}
assert(readonlyAnim, "animVal is read-only");
let incompatibleReceiver = false;
try {
markerDescriptor.get.call(document.createElementNS(ns, "rect"));
} catch (error) {
incompatibleReceiver = error instanceof TypeError;
}
assert(incompatibleReceiver, "marker receiver brand");
return "ok";
})()
"#,
)
.expect("SVG animated angle probe should evaluate");
assert_eq!(result, "ok");
}
#[test]
fn svg_string_lists_reflect_conditional_processing_attributes() {
let mut vm = new_parsed_test_vm(
@@ -372,6 +372,9 @@ interfaces! {
SVGAnimatedLengthList;
SVGAnimatedBoolean;
SVGFEConvolveMatrixElement: SVGElement;
SVGAngle;
SVGAnimatedAngle;
SVGMarkerElement: SVGElement;
SVGAnimatedNumber;
SVGAnimatedNumberList;
SVGAnimatedTransformList;