mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-18 09:54:36 -04:00
webapi: add live SVG resource elements
This commit is contained in:
1 parent
302fc79a59
commit
bb063c77bd
23 files changed
+1052
-35
No files matched your search
@@ -64,6 +64,7 @@ const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind;
|
||||
|
||||
const PointList = @import("webapi/svg/PointList.zig");
|
||||
const StringList = @import("webapi/svg/StringList.zig");
|
||||
const AnimatedEnumeration = @import("webapi/svg/AnimatedEnumeration.zig");
|
||||
const AnimatedLength = @import("webapi/svg/AnimatedLength.zig");
|
||||
const AnimatedNumber = @import("webapi/svg/AnimatedNumber.zig");
|
||||
const AnimatedString = @import("webapi/svg/AnimatedString.zig");
|
||||
@@ -150,6 +151,7 @@ _element_shadow_roots: Element.ShadowRootLookup = .empty,
|
||||
_node_owner_documents: Node.OwnerDocumentLookup = .empty,
|
||||
_element_scroll_positions: Element.ScrollPositionLookup = .empty,
|
||||
_element_namespace_uris: Element.NamespaceUriLookup = .empty,
|
||||
_svg_animated_enumerations: AnimatedEnumeration.Lookup = .empty,
|
||||
_svg_animated_lengths: AnimatedLength.Lookup = .empty,
|
||||
_svg_animated_numbers: AnimatedNumber.Lookup = .empty,
|
||||
_svg_animated_preserve_aspect_ratios: AnimatedPreserveAspectRatio.Lookup = .empty,
|
||||
|
||||
@@ -902,7 +902,9 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons
|
||||
4 => switch (@as(u32, @bitCast(name[0..4].*))) {
|
||||
asUint("defs") => return createSvgElementT(frame, Graphics.Defs, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("desc") => return createSvgElementT(frame, Element.Svg.Desc, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("mask") => return createSvgElementT(frame, Element.Svg.Mask, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("rect") => return createSvgElementT(frame, Geometry.Rect, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("stop") => return createSvgElementT(frame, Element.Svg.Stop, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("line") => return createSvgElementT(frame, Geometry.Line, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("path") => return createSvgElementT(frame, Geometry.Path, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("view") => return createSvgElementT(frame, Element.Svg.View, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
@@ -915,16 +917,19 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons
|
||||
},
|
||||
6 => switch (@as(u48, @bitCast(name[0..6].*))) {
|
||||
asUint("circle") => return createSvgElementT(frame, Geometry.Circle, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("marker") => return createSvgElementT(frame, Element.Svg.Marker, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("switch") => return createSvgElementT(frame, Graphics.Switch, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("symbol") => return createSvgElementT(frame, Graphics.Symbol, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
else => {},
|
||||
},
|
||||
7 => switch (@as(u56, @bitCast(name[0..7].*))) {
|
||||
asUint("ellipse") => return createSvgElementT(frame, Geometry.Ellipse, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("pattern") => return createSvgElementT(frame, Element.Svg.Pattern, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("polygon") => return createSvgElementT(frame, Geometry.Polygon, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
else => {},
|
||||
},
|
||||
8 => switch (@as(u64, @bitCast(name[0..8].*))) {
|
||||
asUint("clipPath") => return createSvgElementT(frame, Element.Svg.ClipPath, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("metadata") => return createSvgElementT(frame, Element.Svg.Metadata, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
asUint("polyline") => return createSvgElementT(frame, Geometry.Polyline, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
else => {},
|
||||
@@ -933,6 +938,14 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons
|
||||
asUint("foreignObject") => return createSvgElementT(frame, Graphics.ForeignObject, name, attribute_iterator, .{ ._proto = undefined }),
|
||||
else => {},
|
||||
},
|
||||
14 => {
|
||||
if (std.mem.eql(u8, name, "linearGradient")) {
|
||||
return createSvgElementT(frame, Element.Svg.GradientElement.LinearGradient, name, attribute_iterator, .{ ._proto = undefined });
|
||||
}
|
||||
if (std.mem.eql(u8, name, "radialGradient")) {
|
||||
return createSvgElementT(frame, Element.Svg.GradientElement.RadialGradient, name, attribute_iterator, .{ ._proto = undefined });
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
|
||||
@@ -1068,6 +1068,14 @@ pub const PageJsApis = flattenTypes(&.{
|
||||
@import("../webapi/element/svg/Title.zig"),
|
||||
@import("../webapi/element/svg/Desc.zig"),
|
||||
@import("../webapi/element/svg/Metadata.zig"),
|
||||
@import("../webapi/element/svg/GradientElement.zig"),
|
||||
@import("../webapi/element/svg/LinearGradient.zig"),
|
||||
@import("../webapi/element/svg/RadialGradient.zig"),
|
||||
@import("../webapi/element/svg/ClipPath.zig"),
|
||||
@import("../webapi/element/svg/Marker.zig"),
|
||||
@import("../webapi/element/svg/Mask.zig"),
|
||||
@import("../webapi/element/svg/Pattern.zig"),
|
||||
@import("../webapi/element/svg/Stop.zig"),
|
||||
@import("../webapi/element/svg/Rect.zig"),
|
||||
@import("../webapi/element/svg/Circle.zig"),
|
||||
@import("../webapi/element/svg/Ellipse.zig"),
|
||||
@@ -1082,6 +1090,7 @@ pub const PageJsApis = flattenTypes(&.{
|
||||
@import("../webapi/svg/Transform.zig"),
|
||||
@import("../webapi/svg/AnimatedLength.zig"),
|
||||
@import("../webapi/svg/AnimatedNumber.zig"),
|
||||
@import("../webapi/svg/AnimatedEnumeration.zig"),
|
||||
@import("../webapi/svg/PreserveAspectRatio.zig"),
|
||||
@import("../webapi/svg/AnimatedPreserveAspectRatio.zig"),
|
||||
@import("../webapi/svg/PointList.zig"),
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
testing.expectEqual('function', typeof SVGAnimatedString);
|
||||
testing.expectEqual('function', typeof SVGNumber);
|
||||
testing.expectEqual('function', typeof SVGAnimatedNumber);
|
||||
testing.expectEqual('function', typeof SVGAnimatedEnumeration);
|
||||
testing.expectEqual('function', typeof SVGSymbolElement);
|
||||
testing.expectEqual('function', typeof SVGSwitchElement);
|
||||
testing.expectEqual('function', typeof SVGForeignObjectElement);
|
||||
@@ -46,6 +47,14 @@
|
||||
testing.expectEqual('function', typeof SVGTitleElement);
|
||||
testing.expectEqual('function', typeof SVGDescElement);
|
||||
testing.expectEqual('function', typeof SVGMetadataElement);
|
||||
testing.expectEqual('function', typeof SVGGradientElement);
|
||||
testing.expectEqual('function', typeof SVGLinearGradientElement);
|
||||
testing.expectEqual('function', typeof SVGRadialGradientElement);
|
||||
testing.expectEqual('function', typeof SVGClipPathElement);
|
||||
testing.expectEqual('function', typeof SVGMarkerElement);
|
||||
testing.expectEqual('function', typeof SVGMaskElement);
|
||||
testing.expectEqual('function', typeof SVGPatternElement);
|
||||
testing.expectEqual('function', typeof SVGStopElement);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../../testing.js"></script>
|
||||
|
||||
<svg id=root width="200" height="100">
|
||||
<defs>
|
||||
<linearGradient id=linear x1="10%" gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="reflect" gradientTransform="translate(4 5)" transform="translate(90)" href="#base">
|
||||
<stop id=stop offset="25%"></stop>
|
||||
</linearGradient>
|
||||
<radialGradient id=radial cx="20%" cy="30%"></radialGradient>
|
||||
<clipPath id=clip clipPathUnits="objectBoundingBox" transform="scale(2)"></clipPath>
|
||||
<mask id=mask></mask>
|
||||
<marker id=marker></marker>
|
||||
<pattern id=pattern patternTransform="translate(7 8)" href="#tile"></pattern>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<script id=resourceIdentities>
|
||||
{
|
||||
testing.expectEqual(true, $('#linear') instanceof SVGLinearGradientElement);
|
||||
testing.expectEqual(true, $('#linear') instanceof SVGGradientElement);
|
||||
testing.expectEqual(true, $('#linear') instanceof SVGElement);
|
||||
testing.expectEqual(true, $('#radial') instanceof SVGRadialGradientElement);
|
||||
testing.expectEqual(true, $('#radial') instanceof SVGGradientElement);
|
||||
testing.expectEqual(true, $('#clip') instanceof SVGClipPathElement);
|
||||
testing.expectEqual(true, $('#mask') instanceof SVGMaskElement);
|
||||
testing.expectEqual(true, $('#marker') instanceof SVGMarkerElement);
|
||||
testing.expectEqual(true, $('#pattern') instanceof SVGPatternElement);
|
||||
testing.expectEqual(true, $('#stop') instanceof SVGStopElement);
|
||||
|
||||
const ns = 'http://www.w3.org/2000/svg';
|
||||
testing.expectEqual(true, document.createElementNS(ns, 'linearGradient') instanceof SVGLinearGradientElement);
|
||||
testing.expectEqual(false, document.createElementNS(ns, 'lineargradient') instanceof SVGLinearGradientElement);
|
||||
testing.expectEqual(false, document.createElementNS(ns, 'clippath') instanceof SVGClipPathElement);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=gradientReflection>
|
||||
{
|
||||
const linear = $('#linear');
|
||||
testing.expectEqual(true, linear.gradientUnits instanceof SVGAnimatedEnumeration);
|
||||
testing.expectEqual(true, linear.gradientUnits === linear.gradientUnits);
|
||||
testing.expectEqual(1, linear.gradientUnits.baseVal);
|
||||
testing.expectEqual(1, linear.gradientUnits.animVal);
|
||||
linear.gradientUnits.baseVal = 2;
|
||||
testing.expectEqual('objectBoundingBox', linear.getAttribute('gradientUnits'));
|
||||
testing.expectEqual(2, linear.gradientUnits.animVal);
|
||||
testing.expectError('TypeError', () => { linear.gradientUnits.baseVal = 99; });
|
||||
|
||||
testing.expectEqual(2, linear.spreadMethod.baseVal);
|
||||
linear.setAttribute('spreadMethod', ' \treflect\n');
|
||||
testing.expectEqual(2, linear.spreadMethod.baseVal);
|
||||
testing.expectEqual('#base', linear.href.baseVal);
|
||||
linear.href.baseVal = '#other';
|
||||
testing.expectEqual('#other', linear.getAttribute('href'));
|
||||
|
||||
testing.expectEqual(1, linear.gradientTransform.baseVal.numberOfItems);
|
||||
testing.expectEqual(4, linear.gradientTransform.baseVal.getItem(0).matrix.e);
|
||||
testing.expectEqual(5, linear.gradientTransform.baseVal.getItem(0).matrix.f);
|
||||
testing.expectEqual(true, linear.gradientTransform === linear.gradientTransform);
|
||||
const gradientMatrix = linear.gradientTransform.baseVal.getItem(0).matrix;
|
||||
gradientMatrix.e = 12;
|
||||
testing.expectEqual(true, linear.getAttribute('gradientTransform').startsWith('matrix('));
|
||||
testing.expectEqual('translate(90)', linear.getAttribute('transform'));
|
||||
testing.expectEqual(12, linear.gradientTransform.animVal.getItem(0).matrix.e);
|
||||
testing.expectError('NoModificationAllowedError', () => {
|
||||
linear.gradientTransform.animVal.getItem(0).matrix.e = 99;
|
||||
});
|
||||
|
||||
testing.expectEqual(2, linear.x1.baseVal.unitType);
|
||||
testing.expectEqual(true, linear.x2 === linear.x2);
|
||||
testing.expectEqual(10, linear.x1.baseVal.valueInSpecifiedUnits);
|
||||
testing.expectEqual('100%', linear.x2.baseVal.valueAsString);
|
||||
linear.x2.baseVal.valueInSpecifiedUnits = 80;
|
||||
testing.expectEqual('80%', linear.getAttribute('x2'));
|
||||
testing.expectEqual(80, linear.x2.animVal.valueInSpecifiedUnits);
|
||||
testing.expectError('NoModificationAllowedError', () => { linear.x2.animVal.value = 1; });
|
||||
linear.setAttribute('x2', 'invalid');
|
||||
testing.expectEqual('100%', linear.x2.baseVal.valueAsString);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=radialFallbacks>
|
||||
{
|
||||
const radial = $('#radial');
|
||||
testing.expectEqual(true, radial.fx === radial.fx);
|
||||
testing.expectEqual('20%', radial.fx.baseVal.valueAsString);
|
||||
testing.expectEqual('30%', radial.fy.baseVal.valueAsString);
|
||||
radial.setAttribute('cx', '40%');
|
||||
testing.expectEqual('40%', radial.fx.baseVal.valueAsString);
|
||||
radial.fx.baseVal.valueAsString = '15%';
|
||||
radial.setAttribute('cx', '60%');
|
||||
testing.expectEqual('15%', radial.fx.baseVal.valueAsString);
|
||||
radial.removeAttribute('fx');
|
||||
testing.expectEqual('60%', radial.fx.baseVal.valueAsString);
|
||||
testing.expectEqual('50%', radial.r.baseVal.valueAsString);
|
||||
testing.expectEqual('0%', radial.fr.baseVal.valueAsString);
|
||||
radial.setAttribute('r', 'invalid');
|
||||
testing.expectEqual('50%', radial.r.baseVal.valueAsString);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=resourceDefaultsAndAttributes>
|
||||
{
|
||||
const clip = $('#clip');
|
||||
testing.expectEqual(2, clip.clipPathUnits.baseVal);
|
||||
testing.expectEqual(2, clip.transform.baseVal.getItem(0).matrix.a);
|
||||
|
||||
const mask = $('#mask');
|
||||
testing.expectEqual(true, mask.x === mask.x);
|
||||
testing.expectEqual('-10%', mask.x.baseVal.valueAsString);
|
||||
testing.expectEqual('-10%', mask.y.baseVal.valueAsString);
|
||||
testing.expectEqual('120%', mask.width.baseVal.valueAsString);
|
||||
testing.expectEqual('120%', mask.height.baseVal.valueAsString);
|
||||
mask.setAttribute('width', 'invalid');
|
||||
testing.expectEqual('120%', mask.width.baseVal.valueAsString);
|
||||
testing.expectEqual(2, mask.maskUnits.baseVal);
|
||||
testing.expectEqual(1, mask.maskContentUnits.baseVal);
|
||||
|
||||
const marker = $('#marker');
|
||||
testing.expectEqual(true, marker.markerUnits === marker.markerUnits);
|
||||
testing.expectEqual(3, marker.markerWidth.baseVal.value);
|
||||
testing.expectEqual(3, marker.markerHeight.baseVal.value);
|
||||
marker.setAttribute('markerWidth', 'invalid');
|
||||
testing.expectEqual(3, marker.markerWidth.baseVal.value);
|
||||
testing.expectEqual(2, marker.markerUnits.baseVal);
|
||||
|
||||
const pattern = $('#pattern');
|
||||
testing.expectEqual(true, pattern.patternTransform === pattern.patternTransform);
|
||||
testing.expectEqual(2, pattern.patternUnits.baseVal);
|
||||
testing.expectEqual(1, pattern.patternContentUnits.baseVal);
|
||||
testing.expectEqual(7, pattern.patternTransform.baseVal.getItem(0).matrix.e);
|
||||
testing.expectEqual(8, pattern.patternTransform.baseVal.getItem(0).matrix.f);
|
||||
const patternMatrix = pattern.patternTransform.baseVal.getItem(0).matrix;
|
||||
patternMatrix.f = 11;
|
||||
testing.expectEqual(true, pattern.getAttribute('patternTransform').startsWith('matrix('));
|
||||
testing.expectEqual(11, pattern.patternTransform.animVal.getItem(0).matrix.f);
|
||||
testing.expectEqual('#tile', pattern.href.baseVal);
|
||||
|
||||
const stop = $('#stop');
|
||||
testing.expectEqual(true, stop.offset === stop.offset);
|
||||
testing.expectEqual('number', typeof stop.offset.baseVal);
|
||||
testing.expectEqual(0.25, stop.offset.baseVal);
|
||||
stop.offset.baseVal = 0.5;
|
||||
testing.expectEqual('0.5', stop.getAttribute('offset'));
|
||||
testing.expectEqual(0.5, stop.offset.animVal);
|
||||
testing.expectEqual(false, Reflect.set(stop.offset, 'animVal', 1));
|
||||
testing.expectEqual(0.5, stop.offset.animVal);
|
||||
stop.setAttribute('offset', 'invalid');
|
||||
testing.expectEqual(0, stop.offset.baseVal);
|
||||
}
|
||||
</script>
|
||||
@@ -178,6 +178,10 @@
|
||||
testing.expectError('NoModificationAllowedError', () => { aspect.animVal.meetOrSlice = 2; });
|
||||
|
||||
const empty = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
testing.expectEqual('100%', empty.width.baseVal.valueAsString);
|
||||
testing.expectEqual('100%', empty.height.baseVal.valueAsString);
|
||||
empty.setAttribute('width', 'invalid');
|
||||
testing.expectEqual('100%', empty.width.baseVal.valueAsString);
|
||||
testing.expectEqual(SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID, empty.preserveAspectRatio.baseVal.align);
|
||||
testing.expectEqual(SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, empty.preserveAspectRatio.baseVal.meetOrSlice);
|
||||
testing.expectEqual(null, empty.getAttribute('preserveAspectRatio'));
|
||||
|
||||
@@ -31,6 +31,12 @@ pub const View = @import("svg/View.zig");
|
||||
pub const Title = @import("svg/Title.zig");
|
||||
pub const Desc = @import("svg/Desc.zig");
|
||||
pub const Metadata = @import("svg/Metadata.zig");
|
||||
pub const GradientElement = @import("svg/GradientElement.zig");
|
||||
pub const ClipPath = @import("svg/ClipPath.zig");
|
||||
pub const Marker = @import("svg/Marker.zig");
|
||||
pub const Mask = @import("svg/Mask.zig");
|
||||
pub const Pattern = @import("svg/Pattern.zig");
|
||||
pub const Stop = @import("svg/Stop.zig");
|
||||
|
||||
const String = lp.String;
|
||||
|
||||
@@ -45,6 +51,12 @@ pub const Type = union(enum) {
|
||||
title: *Title,
|
||||
desc: *Desc,
|
||||
metadata: *Metadata,
|
||||
gradient: *GradientElement,
|
||||
clip_path: *ClipPath,
|
||||
marker: *Marker,
|
||||
mask: *Mask,
|
||||
pattern: *Pattern,
|
||||
stop: *Stop,
|
||||
generic: *Generic,
|
||||
};
|
||||
|
||||
@@ -59,6 +71,9 @@ pub fn is(self: *Svg, comptime T: type) ?*T {
|
||||
if (self._type == .graphics) {
|
||||
return self._type.graphics.is(T);
|
||||
}
|
||||
if (self._type == .gradient) {
|
||||
return self._type.gradient.is(T);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -82,7 +97,7 @@ pub fn getTag(self: *const Svg) Element.Tag {
|
||||
},
|
||||
.generic => |g| g._tag,
|
||||
.title => .title,
|
||||
.view, .desc, .metadata => .unknown,
|
||||
.view, .desc, .metadata, .gradient, .clip_path, .marker, .mask, .pattern, .stop => .unknown,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Svg = @import("../Svg.zig");
|
||||
const AnimatedEnumeration = @import("../../svg/AnimatedEnumeration.zig");
|
||||
const AnimatedTransformList = @import("../../svg/AnimatedTransformList.zig");
|
||||
|
||||
const ClipPath = @This();
|
||||
_proto: *Svg,
|
||||
|
||||
pub fn asElement(self: *ClipPath) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *ClipPath) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getClipPathUnits(self: *ClipPath, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .clip_path_units, frame);
|
||||
}
|
||||
|
||||
fn getTransform(self: *ClipPath, frame: *Frame) !*AnimatedTransformList {
|
||||
return AnimatedTransformList.getOrCreate(self.asElement(), .transform, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(ClipPath);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGClipPathElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const SVG_UNIT_TYPE_UNKNOWN = bridge.property(0, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_USERSPACEONUSE = bridge.property(1, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = bridge.property(2, .{ .template = true });
|
||||
pub const clipPathUnits = bridge.accessor(ClipPath.getClipPathUnits, null, .{});
|
||||
pub const transform = bridge.accessor(ClipPath.getTransform, null, .{});
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Svg = @import("../Svg.zig");
|
||||
const AnimatedEnumeration = @import("../../svg/AnimatedEnumeration.zig");
|
||||
const AnimatedString = @import("../../svg/AnimatedString.zig");
|
||||
const AnimatedTransformList = @import("../../svg/AnimatedTransformList.zig");
|
||||
|
||||
pub const LinearGradient = @import("LinearGradient.zig");
|
||||
pub const RadialGradient = @import("RadialGradient.zig");
|
||||
|
||||
const GradientElement = @This();
|
||||
_proto: *Svg,
|
||||
_type: Type,
|
||||
|
||||
pub const Type = union(enum) {
|
||||
linear: *LinearGradient,
|
||||
radial: *RadialGradient,
|
||||
};
|
||||
|
||||
pub fn is(self: *GradientElement, comptime T: type) ?*T {
|
||||
inline for (@typeInfo(Type).@"union".fields) |field| {
|
||||
if (@field(Type, field.name) == self._type) {
|
||||
if (field.type == *T) {
|
||||
return @field(self._type, field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn asElement(self: *GradientElement) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *GradientElement) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getGradientUnits(self: *GradientElement, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .gradient_units, frame);
|
||||
}
|
||||
|
||||
fn getSpreadMethod(self: *GradientElement, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .spread_method, frame);
|
||||
}
|
||||
|
||||
fn getGradientTransform(self: *GradientElement, frame: *Frame) !*AnimatedTransformList {
|
||||
return AnimatedTransformList.getOrCreate(self.asElement(), .gradient_transform, frame);
|
||||
}
|
||||
|
||||
fn getHref(self: *GradientElement, frame: *Frame) !*AnimatedString {
|
||||
return AnimatedString.getOrCreate(self.asElement(), .href, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(GradientElement);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGGradientElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const SVG_UNIT_TYPE_UNKNOWN = bridge.property(0, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_USERSPACEONUSE = bridge.property(1, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = bridge.property(2, .{ .template = true });
|
||||
pub const SVG_SPREADMETHOD_UNKNOWN = bridge.property(0, .{ .template = true });
|
||||
pub const SVG_SPREADMETHOD_PAD = bridge.property(1, .{ .template = true });
|
||||
pub const SVG_SPREADMETHOD_REFLECT = bridge.property(2, .{ .template = true });
|
||||
pub const SVG_SPREADMETHOD_REPEAT = bridge.property(3, .{ .template = true });
|
||||
|
||||
pub const gradientUnits = bridge.accessor(GradientElement.getGradientUnits, null, .{});
|
||||
pub const spreadMethod = bridge.accessor(GradientElement.getSpreadMethod, null, .{});
|
||||
pub const gradientTransform = bridge.accessor(GradientElement.getGradientTransform, null, .{});
|
||||
pub const href = bridge.accessor(GradientElement.getHref, null, .{});
|
||||
};
|
||||
@@ -204,7 +204,7 @@ fn transformMatrix(element: *Element) PathData.Matrix {
|
||||
}
|
||||
|
||||
pub fn getTransform(self: *Graphics, frame: *Frame) !*AnimatedTransformList {
|
||||
return AnimatedTransformList.getOrCreate(self.asElement(), frame);
|
||||
return AnimatedTransformList.getOrCreate(self.asElement(), .transform, frame);
|
||||
}
|
||||
|
||||
pub fn getRequiredExtensions(self: *Graphics, frame: *Frame) !*StringList {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const AnimatedLength = @import("../../svg/AnimatedLength.zig");
|
||||
const GradientElement = @import("GradientElement.zig");
|
||||
|
||||
const LinearGradient = @This();
|
||||
_proto: *GradientElement,
|
||||
|
||||
pub fn asElement(self: *LinearGradient) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *LinearGradient) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getX1(self: *LinearGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .linear_gradient_x1, frame);
|
||||
}
|
||||
fn getY1(self: *LinearGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .linear_gradient_y1, frame);
|
||||
}
|
||||
fn getX2(self: *LinearGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .linear_gradient_x2, frame);
|
||||
}
|
||||
fn getY2(self: *LinearGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .linear_gradient_y2, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(LinearGradient);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGLinearGradientElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const x1 = bridge.accessor(LinearGradient.getX1, null, .{});
|
||||
pub const y1 = bridge.accessor(LinearGradient.getY1, null, .{});
|
||||
pub const x2 = bridge.accessor(LinearGradient.getX2, null, .{});
|
||||
pub const y2 = bridge.accessor(LinearGradient.getY2, null, .{});
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Svg = @import("../Svg.zig");
|
||||
const AnimatedEnumeration = @import("../../svg/AnimatedEnumeration.zig");
|
||||
const AnimatedLength = @import("../../svg/AnimatedLength.zig");
|
||||
|
||||
const Marker = @This();
|
||||
_proto: *Svg,
|
||||
|
||||
pub fn asElement(self: *Marker) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *Marker) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getRefX(self: *Marker, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .marker_ref_x, frame);
|
||||
}
|
||||
fn getRefY(self: *Marker, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .marker_ref_y, frame);
|
||||
}
|
||||
fn getMarkerWidth(self: *Marker, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .marker_width, frame);
|
||||
}
|
||||
fn getMarkerHeight(self: *Marker, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .marker_height, frame);
|
||||
}
|
||||
fn getMarkerUnits(self: *Marker, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .marker_units, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Marker);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGMarkerElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const SVG_MARKERUNITS_UNKNOWN = bridge.property(0, .{ .template = true });
|
||||
pub const SVG_MARKERUNITS_USERSPACEONUSE = bridge.property(1, .{ .template = true });
|
||||
pub const SVG_MARKERUNITS_STROKEWIDTH = bridge.property(2, .{ .template = true });
|
||||
pub const refX = bridge.accessor(Marker.getRefX, null, .{});
|
||||
pub const refY = bridge.accessor(Marker.getRefY, null, .{});
|
||||
pub const markerWidth = bridge.accessor(Marker.getMarkerWidth, null, .{});
|
||||
pub const markerHeight = bridge.accessor(Marker.getMarkerHeight, null, .{});
|
||||
pub const markerUnits = bridge.accessor(Marker.getMarkerUnits, null, .{});
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Svg = @import("../Svg.zig");
|
||||
const AnimatedEnumeration = @import("../../svg/AnimatedEnumeration.zig");
|
||||
const AnimatedLength = @import("../../svg/AnimatedLength.zig");
|
||||
|
||||
const Mask = @This();
|
||||
_proto: *Svg,
|
||||
|
||||
pub fn asElement(self: *Mask) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *Mask) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getX(self: *Mask, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .mask_x, frame);
|
||||
}
|
||||
fn getY(self: *Mask, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .mask_y, frame);
|
||||
}
|
||||
fn getWidth(self: *Mask, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .mask_width, frame);
|
||||
}
|
||||
fn getHeight(self: *Mask, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .mask_height, frame);
|
||||
}
|
||||
fn getMaskUnits(self: *Mask, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .mask_units, frame);
|
||||
}
|
||||
fn getMaskContentUnits(self: *Mask, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .mask_content_units, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Mask);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGMaskElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const SVG_UNIT_TYPE_UNKNOWN = bridge.property(0, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_USERSPACEONUSE = bridge.property(1, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = bridge.property(2, .{ .template = true });
|
||||
pub const x = bridge.accessor(Mask.getX, null, .{});
|
||||
pub const y = bridge.accessor(Mask.getY, null, .{});
|
||||
pub const width = bridge.accessor(Mask.getWidth, null, .{});
|
||||
pub const height = bridge.accessor(Mask.getHeight, null, .{});
|
||||
pub const maskUnits = bridge.accessor(Mask.getMaskUnits, null, .{});
|
||||
pub const maskContentUnits = bridge.accessor(Mask.getMaskContentUnits, null, .{});
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Svg = @import("../Svg.zig");
|
||||
const AnimatedEnumeration = @import("../../svg/AnimatedEnumeration.zig");
|
||||
const AnimatedLength = @import("../../svg/AnimatedLength.zig");
|
||||
const AnimatedString = @import("../../svg/AnimatedString.zig");
|
||||
const AnimatedTransformList = @import("../../svg/AnimatedTransformList.zig");
|
||||
|
||||
const Pattern = @This();
|
||||
_proto: *Svg,
|
||||
|
||||
pub fn asElement(self: *Pattern) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *Pattern) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getX(self: *Pattern, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .pattern_x, frame);
|
||||
}
|
||||
fn getY(self: *Pattern, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .pattern_y, frame);
|
||||
}
|
||||
fn getWidth(self: *Pattern, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .pattern_width, frame);
|
||||
}
|
||||
fn getHeight(self: *Pattern, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .pattern_height, frame);
|
||||
}
|
||||
fn getPatternUnits(self: *Pattern, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .pattern_units, frame);
|
||||
}
|
||||
fn getPatternContentUnits(self: *Pattern, frame: *Frame) !*AnimatedEnumeration {
|
||||
return AnimatedEnumeration.getOrCreate(self.asElement(), .pattern_content_units, frame);
|
||||
}
|
||||
fn getPatternTransform(self: *Pattern, frame: *Frame) !*AnimatedTransformList {
|
||||
return AnimatedTransformList.getOrCreate(self.asElement(), .pattern_transform, frame);
|
||||
}
|
||||
fn getHref(self: *Pattern, frame: *Frame) !*AnimatedString {
|
||||
return AnimatedString.getOrCreate(self.asElement(), .href, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Pattern);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGPatternElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const SVG_UNIT_TYPE_UNKNOWN = bridge.property(0, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_USERSPACEONUSE = bridge.property(1, .{ .template = true });
|
||||
pub const SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = bridge.property(2, .{ .template = true });
|
||||
pub const x = bridge.accessor(Pattern.getX, null, .{});
|
||||
pub const y = bridge.accessor(Pattern.getY, null, .{});
|
||||
pub const width = bridge.accessor(Pattern.getWidth, null, .{});
|
||||
pub const height = bridge.accessor(Pattern.getHeight, null, .{});
|
||||
pub const patternUnits = bridge.accessor(Pattern.getPatternUnits, null, .{});
|
||||
pub const patternContentUnits = bridge.accessor(Pattern.getPatternContentUnits, null, .{});
|
||||
pub const patternTransform = bridge.accessor(Pattern.getPatternTransform, null, .{});
|
||||
pub const href = bridge.accessor(Pattern.getHref, null, .{});
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const AnimatedLength = @import("../../svg/AnimatedLength.zig");
|
||||
const GradientElement = @import("GradientElement.zig");
|
||||
|
||||
const RadialGradient = @This();
|
||||
_proto: *GradientElement,
|
||||
|
||||
pub fn asElement(self: *RadialGradient) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *RadialGradient) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getCx(self: *RadialGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .radial_gradient_cx, frame);
|
||||
}
|
||||
fn getCy(self: *RadialGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .radial_gradient_cy, frame);
|
||||
}
|
||||
fn getR(self: *RadialGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .radial_gradient_r, frame);
|
||||
}
|
||||
fn getFx(self: *RadialGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .radial_gradient_fx, frame);
|
||||
}
|
||||
fn getFy(self: *RadialGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .radial_gradient_fy, frame);
|
||||
}
|
||||
fn getFr(self: *RadialGradient, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .radial_gradient_fr, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(RadialGradient);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGRadialGradientElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const cx = bridge.accessor(RadialGradient.getCx, null, .{});
|
||||
pub const cy = bridge.accessor(RadialGradient.getCy, null, .{});
|
||||
pub const r = bridge.accessor(RadialGradient.getR, null, .{});
|
||||
pub const fx = bridge.accessor(RadialGradient.getFx, null, .{});
|
||||
pub const fy = bridge.accessor(RadialGradient.getFy, null, .{});
|
||||
pub const fr = bridge.accessor(RadialGradient.getFr, null, .{});
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Frame = @import("../../../Frame.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const Svg = @import("../Svg.zig");
|
||||
const AnimatedNumber = @import("../../svg/AnimatedNumber.zig");
|
||||
|
||||
const Stop = @This();
|
||||
_proto: *Svg,
|
||||
|
||||
pub fn asElement(self: *Stop) *Element {
|
||||
return self._proto.asElement();
|
||||
}
|
||||
pub fn asNode(self: *Stop) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
|
||||
fn getOffset(self: *Stop, frame: *Frame) !*AnimatedNumber {
|
||||
return AnimatedNumber.getOrCreate(self.asElement(), .offset, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Stop);
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGStopElement";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
pub const offset = bridge.accessor(Stop.getOffset, null, .{});
|
||||
};
|
||||
@@ -70,11 +70,11 @@ pub fn getY(self: *Svg, frame: *Frame) !*AnimatedLength {
|
||||
}
|
||||
|
||||
pub fn getWidth(self: *Svg, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .width, frame);
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .svg_width, frame);
|
||||
}
|
||||
|
||||
pub fn getHeight(self: *Svg, frame: *Frame) !*AnimatedLength {
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .height, frame);
|
||||
return AnimatedLength.getOrCreate(self.asElement(), .svg_height, frame);
|
||||
}
|
||||
|
||||
pub fn getPreserveAspectRatio(self: *Svg, frame: *Frame) !*AnimatedPreserveAspectRatio {
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
|
||||
const AnimatedEnumeration = @This();
|
||||
|
||||
_element: *Element,
|
||||
_attr_name: lp.String,
|
||||
_entries: []const Entry,
|
||||
_default_value: u16,
|
||||
|
||||
pub const Entry = struct {
|
||||
keyword: []const u8,
|
||||
value: u16,
|
||||
};
|
||||
|
||||
const unit_entries = [_]Entry{
|
||||
.{ .keyword = "userSpaceOnUse", .value = 1 },
|
||||
.{ .keyword = "objectBoundingBox", .value = 2 },
|
||||
};
|
||||
const marker_unit_entries = [_]Entry{
|
||||
.{ .keyword = "userSpaceOnUse", .value = 1 },
|
||||
.{ .keyword = "strokeWidth", .value = 2 },
|
||||
};
|
||||
const spread_entries = [_]Entry{
|
||||
.{ .keyword = "pad", .value = 1 },
|
||||
.{ .keyword = "reflect", .value = 2 },
|
||||
.{ .keyword = "repeat", .value = 3 },
|
||||
};
|
||||
|
||||
pub const Kind = enum {
|
||||
clip_path_units,
|
||||
gradient_units,
|
||||
spread_method,
|
||||
marker_units,
|
||||
mask_units,
|
||||
mask_content_units,
|
||||
pattern_units,
|
||||
pattern_content_units,
|
||||
|
||||
fn attributeName(self: Kind, frame: *Frame) !lp.String {
|
||||
const name = switch (self) {
|
||||
.clip_path_units => "clipPathUnits",
|
||||
.gradient_units => "gradientUnits",
|
||||
.spread_method => "spreadMethod",
|
||||
.marker_units => "markerUnits",
|
||||
.mask_units => "maskUnits",
|
||||
.mask_content_units => "maskContentUnits",
|
||||
.pattern_units => "patternUnits",
|
||||
.pattern_content_units => "patternContentUnits",
|
||||
};
|
||||
return lp.String.init(frame.arena, name, .{ .dupe = false });
|
||||
}
|
||||
|
||||
fn entries(self: Kind) []const Entry {
|
||||
return switch (self) {
|
||||
.marker_units => &marker_unit_entries,
|
||||
.spread_method => &spread_entries,
|
||||
else => &unit_entries,
|
||||
};
|
||||
}
|
||||
|
||||
fn defaultValue(self: Kind) u16 {
|
||||
return switch (self) {
|
||||
.clip_path_units,
|
||||
.mask_content_units,
|
||||
.pattern_content_units,
|
||||
.spread_method,
|
||||
=> 1,
|
||||
else => 2,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Key = struct {
|
||||
element: *Element,
|
||||
kind: Kind,
|
||||
};
|
||||
|
||||
pub const Lookup = std.AutoHashMapUnmanaged(Key, *AnimatedEnumeration);
|
||||
|
||||
pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedEnumeration {
|
||||
const key: Key = .{ .element = element, .kind = kind };
|
||||
const gop = try frame._svg_animated_enumerations.getOrPut(frame.arena, key);
|
||||
if (!gop.found_existing) {
|
||||
errdefer _ = frame._svg_animated_enumerations.remove(key);
|
||||
gop.value_ptr.* = try create(
|
||||
element,
|
||||
try kind.attributeName(frame),
|
||||
kind.entries(),
|
||||
kind.defaultValue(),
|
||||
frame,
|
||||
);
|
||||
}
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
element: *Element,
|
||||
attr_name: lp.String,
|
||||
entries: []const Entry,
|
||||
default_value: u16,
|
||||
frame: *Frame,
|
||||
) !*AnimatedEnumeration {
|
||||
return frame._factory.create(AnimatedEnumeration{
|
||||
._element = element,
|
||||
._attr_name = attr_name,
|
||||
._entries = entries,
|
||||
._default_value = default_value,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getBaseVal(self: *const AnimatedEnumeration) u16 {
|
||||
const raw = self._element.getAttributeSafe(self._attr_name) orelse return self._default_value;
|
||||
const trimmed = std.mem.trim(u8, raw, " \t\r\n\x0c");
|
||||
for (self._entries) |entry| {
|
||||
if (std.mem.eql(u8, trimmed, entry.keyword)) return entry.value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn setBaseVal(self: *AnimatedEnumeration, value: u16, frame: *Frame) !void {
|
||||
for (self._entries) |entry| {
|
||||
if (entry.value == value) {
|
||||
try self._element.setAttributeSafe(self._attr_name, lp.String.wrap(entry.keyword), frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
pub fn getAnimVal(self: *const AnimatedEnumeration) u16 {
|
||||
return self.getBaseVal();
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(AnimatedEnumeration);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "SVGAnimatedEnumeration";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const baseVal = bridge.accessor(AnimatedEnumeration.getBaseVal, AnimatedEnumeration.setBaseVal, .{});
|
||||
pub const animVal = bridge.accessor(AnimatedEnumeration.getAnimVal, null, .{});
|
||||
};
|
||||
@@ -34,6 +34,8 @@ pub const Kind = enum {
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
svg_width,
|
||||
svg_height,
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
@@ -43,30 +45,141 @@ pub const Kind = enum {
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
linear_gradient_x1,
|
||||
linear_gradient_y1,
|
||||
linear_gradient_x2,
|
||||
linear_gradient_y2,
|
||||
radial_gradient_cx,
|
||||
radial_gradient_cy,
|
||||
radial_gradient_r,
|
||||
radial_gradient_fx,
|
||||
radial_gradient_fy,
|
||||
radial_gradient_fr,
|
||||
marker_ref_x,
|
||||
marker_ref_y,
|
||||
marker_width,
|
||||
marker_height,
|
||||
mask_x,
|
||||
mask_y,
|
||||
mask_width,
|
||||
mask_height,
|
||||
pattern_x,
|
||||
pattern_y,
|
||||
pattern_width,
|
||||
pattern_height,
|
||||
|
||||
fn attributeName(self: Kind) lp.String {
|
||||
return switch (self) {
|
||||
.x => comptime .wrap("x"),
|
||||
.y => comptime .wrap("y"),
|
||||
.width => comptime .wrap("width"),
|
||||
.height => comptime .wrap("height"),
|
||||
.cx => comptime .wrap("cx"),
|
||||
.cy => comptime .wrap("cy"),
|
||||
.r => comptime .wrap("r"),
|
||||
.x, .mask_x, .pattern_x => comptime .wrap("x"),
|
||||
.y, .mask_y, .pattern_y => comptime .wrap("y"),
|
||||
.width, .svg_width, .mask_width, .pattern_width => comptime .wrap("width"),
|
||||
.height, .svg_height, .mask_height, .pattern_height => comptime .wrap("height"),
|
||||
.cx, .radial_gradient_cx => comptime .wrap("cx"),
|
||||
.cy, .radial_gradient_cy => comptime .wrap("cy"),
|
||||
.r, .radial_gradient_r => comptime .wrap("r"),
|
||||
.rx => comptime .wrap("rx"),
|
||||
.ry => comptime .wrap("ry"),
|
||||
.x1 => comptime .wrap("x1"),
|
||||
.y1 => comptime .wrap("y1"),
|
||||
.x2 => comptime .wrap("x2"),
|
||||
.y2 => comptime .wrap("y2"),
|
||||
.x1, .linear_gradient_x1 => comptime .wrap("x1"),
|
||||
.y1, .linear_gradient_y1 => comptime .wrap("y1"),
|
||||
.x2, .linear_gradient_x2 => comptime .wrap("x2"),
|
||||
.y2, .linear_gradient_y2 => comptime .wrap("y2"),
|
||||
.radial_gradient_fx => comptime .wrap("fx"),
|
||||
.radial_gradient_fy => comptime .wrap("fy"),
|
||||
.radial_gradient_fr => comptime .wrap("fr"),
|
||||
.marker_ref_x => comptime .wrap("refX"),
|
||||
.marker_ref_y => comptime .wrap("refY"),
|
||||
.marker_width => comptime .wrap("markerWidth"),
|
||||
.marker_height => comptime .wrap("markerHeight"),
|
||||
};
|
||||
}
|
||||
|
||||
fn direction(self: Kind) Length.Direction {
|
||||
return switch (self) {
|
||||
.x, .width, .cx, .rx, .x1, .x2 => .horizontal,
|
||||
.y, .height, .cy, .ry, .y1, .y2 => .vertical,
|
||||
.r => .unspecified,
|
||||
.x,
|
||||
.width,
|
||||
.svg_width,
|
||||
.cx,
|
||||
.rx,
|
||||
.x1,
|
||||
.x2,
|
||||
.linear_gradient_x1,
|
||||
.linear_gradient_x2,
|
||||
.radial_gradient_cx,
|
||||
.radial_gradient_fx,
|
||||
.marker_ref_x,
|
||||
.marker_width,
|
||||
.mask_x,
|
||||
.mask_width,
|
||||
.pattern_x,
|
||||
.pattern_width,
|
||||
=> .horizontal,
|
||||
.y,
|
||||
.height,
|
||||
.svg_height,
|
||||
.cy,
|
||||
.ry,
|
||||
.y1,
|
||||
.y2,
|
||||
.linear_gradient_y1,
|
||||
.linear_gradient_y2,
|
||||
.radial_gradient_cy,
|
||||
.radial_gradient_fy,
|
||||
.marker_ref_y,
|
||||
.marker_height,
|
||||
.mask_y,
|
||||
.mask_height,
|
||||
.pattern_y,
|
||||
.pattern_height,
|
||||
=> .vertical,
|
||||
.r, .radial_gradient_r, .radial_gradient_fr => .unspecified,
|
||||
};
|
||||
}
|
||||
|
||||
fn defaultValue(self: Kind) f64 {
|
||||
return switch (self) {
|
||||
.svg_width, .svg_height => 100,
|
||||
.linear_gradient_x2 => 100,
|
||||
.radial_gradient_cx,
|
||||
.radial_gradient_cy,
|
||||
.radial_gradient_r,
|
||||
.radial_gradient_fx,
|
||||
.radial_gradient_fy,
|
||||
=> 50,
|
||||
.marker_width, .marker_height => 3,
|
||||
.mask_x, .mask_y => -10,
|
||||
.mask_width, .mask_height => 120,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn defaultUnit(self: Kind) Length.Unit {
|
||||
return switch (self) {
|
||||
.svg_width,
|
||||
.svg_height,
|
||||
.linear_gradient_x1,
|
||||
.linear_gradient_y1,
|
||||
.linear_gradient_x2,
|
||||
.linear_gradient_y2,
|
||||
.radial_gradient_cx,
|
||||
.radial_gradient_cy,
|
||||
.radial_gradient_r,
|
||||
.radial_gradient_fx,
|
||||
.radial_gradient_fy,
|
||||
.radial_gradient_fr,
|
||||
.mask_x,
|
||||
.mask_y,
|
||||
.mask_width,
|
||||
.mask_height,
|
||||
=> .percentage,
|
||||
else => .number,
|
||||
};
|
||||
}
|
||||
|
||||
fn fallbackAttributeName(self: Kind) ?lp.String {
|
||||
return switch (self) {
|
||||
.radial_gradient_fx => comptime .wrap("cx"),
|
||||
.radial_gradient_fy => comptime .wrap("cy"),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -86,7 +199,15 @@ pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedLengt
|
||||
const gop = try frame._svg_animated_lengths.getOrPut(frame.arena, key);
|
||||
if (!gop.found_existing) {
|
||||
errdefer _ = frame._svg_animated_lengths.remove(key);
|
||||
gop.value_ptr.* = try create(element, kind.attributeName(), kind.direction(), frame);
|
||||
gop.value_ptr.* = try createConfigured(
|
||||
element,
|
||||
kind.attributeName(),
|
||||
kind.direction(),
|
||||
kind.defaultValue(),
|
||||
kind.defaultUnit(),
|
||||
kind.fallbackAttributeName(),
|
||||
frame,
|
||||
);
|
||||
}
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
@@ -100,6 +221,41 @@ pub fn create(element: *Element, attr_name: lp.String, direction: Length.Directi
|
||||
});
|
||||
}
|
||||
|
||||
pub fn createConfigured(
|
||||
element: *Element,
|
||||
attr_name: lp.String,
|
||||
direction: Length.Direction,
|
||||
default_value: f64,
|
||||
default_unit: Length.Unit,
|
||||
fallback_attr_name: ?lp.String,
|
||||
frame: *Frame,
|
||||
) !*AnimatedLength {
|
||||
const base_val = try Length.reflectedConfigured(
|
||||
element,
|
||||
attr_name,
|
||||
direction,
|
||||
default_value,
|
||||
default_unit,
|
||||
fallback_attr_name,
|
||||
false,
|
||||
frame,
|
||||
);
|
||||
const anim_val = try Length.reflectedConfigured(
|
||||
element,
|
||||
attr_name,
|
||||
direction,
|
||||
default_value,
|
||||
default_unit,
|
||||
fallback_attr_name,
|
||||
true,
|
||||
frame,
|
||||
);
|
||||
return frame._factory.create(AnimatedLength{
|
||||
._base_val = base_val,
|
||||
._anim_val = anim_val,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getBaseVal(self: *AnimatedLength) *Length {
|
||||
return self._base_val;
|
||||
}
|
||||
|
||||
@@ -27,13 +27,23 @@ const AnimatedNumber = @This();
|
||||
|
||||
_element: *Element,
|
||||
_attr_name: lp.String,
|
||||
_allow_percentage: bool,
|
||||
|
||||
pub const Kind = enum {
|
||||
path_length,
|
||||
offset,
|
||||
|
||||
fn attributeName(self: Kind) lp.String {
|
||||
return switch (self) {
|
||||
.path_length => comptime .wrap("pathLength"),
|
||||
.offset => comptime .wrap("offset"),
|
||||
};
|
||||
}
|
||||
|
||||
fn allowsPercentage(self: Kind) bool {
|
||||
return switch (self) {
|
||||
.path_length => false,
|
||||
.offset => true,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -53,6 +63,7 @@ pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedNumbe
|
||||
gop.value_ptr.* = try frame._factory.create(AnimatedNumber{
|
||||
._element = element,
|
||||
._attr_name = kind.attributeName(),
|
||||
._allow_percentage = kind.allowsPercentage(),
|
||||
});
|
||||
}
|
||||
return gop.value_ptr.*;
|
||||
@@ -77,7 +88,10 @@ pub fn getAnimVal(self: *const AnimatedNumber) f32 {
|
||||
fn currentValue(self: *const AnimatedNumber) f32 {
|
||||
const raw = self._element.getAttributeSafe(self._attr_name) orelse return 0;
|
||||
const trimmed = std.mem.trim(u8, raw, " \t\r\n\x0c");
|
||||
const value = std.fmt.parseFloat(f32, trimmed) catch return 0;
|
||||
const value = if (self._allow_percentage and std.mem.endsWith(u8, trimmed, "%"))
|
||||
(std.fmt.parseFloat(f32, trimmed[0 .. trimmed.len - 1]) catch return 0) / 100
|
||||
else
|
||||
std.fmt.parseFloat(f32, trimmed) catch return 0;
|
||||
return if (std.math.isFinite(value)) value else 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// (at your option) any later version.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
@@ -18,20 +19,45 @@ const AnimatedTransformList = @This();
|
||||
_base_val: *TransformList,
|
||||
_anim_val: *TransformList,
|
||||
|
||||
pub const Lookup = std.AutoHashMapUnmanaged(*Element, *AnimatedTransformList);
|
||||
pub const Kind = enum {
|
||||
transform,
|
||||
gradient_transform,
|
||||
pattern_transform,
|
||||
|
||||
pub fn getOrCreate(element: *Element, frame: *Frame) !*AnimatedTransformList {
|
||||
const gop = try frame._svg_animated_transform_lists.getOrPut(frame.arena, element);
|
||||
fn attributeName(self: Kind, frame: *Frame) !lp.String {
|
||||
const name = switch (self) {
|
||||
.transform => "transform",
|
||||
.gradient_transform => "gradientTransform",
|
||||
.pattern_transform => "patternTransform",
|
||||
};
|
||||
return lp.String.init(frame.arena, name, .{ .dupe = false });
|
||||
}
|
||||
};
|
||||
|
||||
pub const Key = struct {
|
||||
element: *Element,
|
||||
kind: Kind,
|
||||
};
|
||||
|
||||
pub const Lookup = std.AutoHashMapUnmanaged(Key, *AnimatedTransformList);
|
||||
|
||||
pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedTransformList {
|
||||
const key: Key = .{ .element = element, .kind = kind };
|
||||
const gop = try frame._svg_animated_transform_lists.getOrPut(frame.arena, key);
|
||||
if (!gop.found_existing) {
|
||||
errdefer _ = frame._svg_animated_transform_lists.remove(element);
|
||||
gop.value_ptr.* = try create(element, frame);
|
||||
errdefer _ = frame._svg_animated_transform_lists.remove(key);
|
||||
gop.value_ptr.* = try createForAttribute(element, try kind.attributeName(frame), frame);
|
||||
}
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn create(element: *Element, frame: *Frame) !*AnimatedTransformList {
|
||||
const base_val = try TransformList.create(element, false, frame);
|
||||
const anim_val = try TransformList.create(element, true, frame);
|
||||
return createForAttribute(element, comptime .wrap("transform"), frame);
|
||||
}
|
||||
|
||||
pub fn createForAttribute(element: *Element, attr_name: lp.String, frame: *Frame) !*AnimatedTransformList {
|
||||
const base_val = try TransformList.createForAttribute(element, attr_name, false, frame);
|
||||
const anim_val = try TransformList.createForAttribute(element, attr_name, true, frame);
|
||||
return frame._factory.create(AnimatedTransformList{
|
||||
._base_val = base_val,
|
||||
._anim_val = anim_val,
|
||||
|
||||
@@ -32,6 +32,9 @@ _element: ?*Element = null,
|
||||
_attr_name: String = .empty,
|
||||
_direction: Direction = .unspecified,
|
||||
_read_only: bool = false,
|
||||
_default_value: f64 = 0,
|
||||
_default_unit: Unit = .number,
|
||||
_fallback_attr_name: ?String = null,
|
||||
|
||||
pub const Direction = enum {
|
||||
horizontal,
|
||||
@@ -39,7 +42,7 @@ pub const Direction = enum {
|
||||
unspecified,
|
||||
};
|
||||
|
||||
const Unit = enum(u16) {
|
||||
pub const Unit = enum(u16) {
|
||||
unknown = 0,
|
||||
number = 1,
|
||||
percentage = 2,
|
||||
@@ -68,6 +71,28 @@ pub fn reflected(element: *Element, attr_name: String, direction: Direction, rea
|
||||
});
|
||||
}
|
||||
|
||||
pub fn reflectedConfigured(
|
||||
element: *Element,
|
||||
attr_name: String,
|
||||
direction: Direction,
|
||||
default_value: f64,
|
||||
default_unit: Unit,
|
||||
fallback_attr_name: ?String,
|
||||
read_only: bool,
|
||||
frame: *Frame,
|
||||
) !*Length {
|
||||
try ensureFinite(default_value);
|
||||
return frame._factory.create(Length{
|
||||
._element = element,
|
||||
._attr_name = attr_name,
|
||||
._direction = direction,
|
||||
._read_only = read_only,
|
||||
._default_value = default_value,
|
||||
._default_unit = default_unit,
|
||||
._fallback_attr_name = fallback_attr_name,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getUnitType(self: *Length) u16 {
|
||||
self.syncFromAttribute();
|
||||
return @intFromEnum(self._unit);
|
||||
@@ -155,14 +180,17 @@ fn ensureFinite(value: f64) !void {
|
||||
|
||||
fn syncFromAttribute(self: *Length) void {
|
||||
const element = self._element orelse return;
|
||||
const raw = element.getAttributeSafe(self._attr_name) orelse {
|
||||
self._value = 0;
|
||||
self._unit = .number;
|
||||
const raw = element.getAttributeSafe(self._attr_name) orelse raw: {
|
||||
if (self._fallback_attr_name) |fallback_attr_name| {
|
||||
if (element.getAttributeSafe(fallback_attr_name)) |fallback| break :raw fallback;
|
||||
}
|
||||
self._value = self._default_value;
|
||||
self._unit = self._default_unit;
|
||||
return;
|
||||
};
|
||||
const parsed = parse(raw) catch {
|
||||
self._value = 0;
|
||||
self._unit = .unknown;
|
||||
self._value = self._default_value;
|
||||
self._unit = self._default_unit;
|
||||
return;
|
||||
};
|
||||
self._value = parsed.value;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// (at your option) any later version.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
@@ -17,6 +18,7 @@ const Transform = @import("Transform.zig");
|
||||
const TransformList = @This();
|
||||
|
||||
_frame: *Frame,
|
||||
_attr_name: lp.String,
|
||||
_read_only: bool,
|
||||
_element: *Element,
|
||||
_synced: bool = false,
|
||||
@@ -25,9 +27,14 @@ _items: std.ArrayList(*Transform) = .empty,
|
||||
_retired: std.ArrayList(*Transform) = .empty,
|
||||
|
||||
pub fn create(element: *Element, read_only: bool, frame: *Frame) !*TransformList {
|
||||
return createForAttribute(element, comptime .wrap("transform"), read_only, frame);
|
||||
}
|
||||
|
||||
pub fn createForAttribute(element: *Element, attr_name: lp.String, read_only: bool, frame: *Frame) !*TransformList {
|
||||
return frame._factory.create(TransformList{
|
||||
._frame = frame,
|
||||
._element = element,
|
||||
._attr_name = attr_name,
|
||||
._read_only = read_only,
|
||||
});
|
||||
}
|
||||
@@ -202,7 +209,7 @@ fn mutateTransform(context: *anyopaque, transform: *Transform, state: Transform.
|
||||
fn sync(self: *TransformList, frame: *Frame) !void {
|
||||
self.releaseRetired(frame._page);
|
||||
|
||||
const raw = self._element.getAttributeSafe(comptime .wrap("transform")) orelse "";
|
||||
const raw = self._element.getAttributeSafe(self._attr_name) orelse "";
|
||||
if (self._synced and std.mem.eql(u8, self._snapshot.items, raw)) return;
|
||||
|
||||
self._synced = false;
|
||||
@@ -285,7 +292,7 @@ fn setAttributeWithOverride(self: *TransformList, index: usize, state: Transform
|
||||
|
||||
fn commitAttribute(self: *TransformList, serialized: []const u8, frame: *Frame) !void {
|
||||
self._synced = false;
|
||||
try self._element.setAttributeSafe(comptime .wrap("transform"), .wrap(serialized), frame);
|
||||
try self._element.setAttributeSafe(self._attr_name, .wrap(serialized), frame);
|
||||
self._snapshot.clearRetainingCapacity();
|
||||
try self._snapshot.appendSlice(frame.arena, serialized);
|
||||
self._synced = true;
|
||||
|
||||
Reference in new issue
Block a user