Merge pull request #2917 from lightpanda-io/svg

webapi: Add various SVG types
This commit is contained in:
Karl Seguin
2026-07-13 07:49:18 +08:00
committed by GitHub
27 changed files with 1341 additions and 41 deletions

View File

@@ -335,33 +335,49 @@ pub fn htmlMediaElement(self: *Factory, child: anytype) !*@TypeOf(child) {
}
pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
const ChildT = @TypeOf(child);
if (ChildT == Element.Svg) {
return self.element(child);
}
const chain = try PrototypeChain(
&.{ EventTarget, Node, Element, Element.Svg, ChildT },
).allocate(allocator);
&.{ EventTarget, Node, Element, Element.Svg, @TypeOf(child) },
).allocate(self._slab.allocator());
try self.setSvgChainBase(chain, tag_name);
chain.setLeaf(4, child);
return chain.get(4);
}
pub fn svgGraphicsElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) {
const chain = try PrototypeChain(
&.{ EventTarget, Node, Element, Element.Svg, Element.Svg.Graphics, @TypeOf(child) },
).allocate(self._slab.allocator());
try self.setSvgChainBase(chain, tag_name);
chain.setMiddle(4, Element.Svg.Graphics.Type);
chain.setLeaf(5, child);
return chain.get(5);
}
pub fn svgGeometryElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) {
const chain = try PrototypeChain(
&.{ EventTarget, Node, Element, Element.Svg, Element.Svg.Graphics, Element.Svg.Graphics.Geometry, @TypeOf(child) },
).allocate(self._slab.allocator());
try self.setSvgChainBase(chain, tag_name);
chain.setMiddle(4, Element.Svg.Graphics.Type);
chain.setMiddle(5, Element.Svg.Graphics.Geometry.Type);
chain.setLeaf(6, child);
return chain.get(6);
}
fn setSvgChainBase(self: *Factory, chain: anytype, tag_name: []const u8) !void {
chain.setRoot(EventTarget.Type);
chain.setMiddle(1, Node.Type);
chain.setMiddle(2, Element.Type);
// will never allocate, can't fail
const tag_name_str = String.init(self._arena, tag_name, .{}) catch unreachable;
// Manually set Element.Svg with the tag_name
chain.set(3, .{
._proto = chain.get(2),
._tag_name = tag_name_str,
._tag_name = try String.init(self._arena, tag_name, .{}),
._type = unionInit(Element.Svg.Type, chain.get(4)),
});
chain.setLeaf(4, child);
return chain.get(4);
}
pub fn xhrEventTarget(_: *const Factory, allocator: Allocator, child: anytype) !*@TypeOf(child) {

View File

@@ -42,6 +42,7 @@ const Event = @import("webapi/Event.zig");
const EventTarget = @import("webapi/EventTarget.zig");
const Element = @import("webapi/Element.zig");
const HtmlElement = @import("webapi/element/Html.zig");
const AnimatedString = @import("webapi/svg/AnimatedString.zig");
const Window = @import("webapi/Window.zig");
const Location = @import("webapi/Location.zig");
const Document = @import("webapi/Document.zig");
@@ -137,6 +138,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_strings: AnimatedString.Lookup = .empty,
// Same as above, but for Nodes (slot assigments apply to both Element AND
// Text nodes)

View File

@@ -879,16 +879,47 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons
return createHtmlElementT(frame, Element.Html.Unknown, namespace, attribute_iterator, .{ ._proto = undefined, ._tag_name = tag_name });
},
.svg => {
const tag_name = try String.init(frame.arena, name, .{});
if (std.ascii.eqlIgnoreCase(name, "svg")) {
return createSvgElementT(frame, Element.Svg, name, attribute_iterator, .{
._proto = undefined,
._type = .svg,
._tag_name = tag_name,
});
const Graphics = Element.Svg.Graphics;
const Geometry = Graphics.Geometry;
// SVG tag names are case-sensitive; no lowering before matching.
switch (name.len) {
1 => switch (name[0]) {
'g' => return createSvgGraphicsElementT(frame, Graphics.G, name, attribute_iterator),
'a' => return createSvgGraphicsElementT(frame, Graphics.A, name, attribute_iterator),
else => {},
},
3 => switch (@as(u24, @bitCast(name[0..3].*))) {
asUint("svg") => return createSvgGraphicsElementT(frame, Graphics.Svg, name, attribute_iterator),
asUint("use") => return createSvgGraphicsElementT(frame, Graphics.Use, name, attribute_iterator),
else => {},
},
4 => switch (@as(u32, @bitCast(name[0..4].*))) {
asUint("defs") => return createSvgGraphicsElementT(frame, Graphics.Defs, name, attribute_iterator),
asUint("rect") => return createSvgGeometryElementT(frame, Geometry.Rect, name, attribute_iterator),
asUint("line") => return createSvgGeometryElementT(frame, Geometry.Line, name, attribute_iterator),
asUint("path") => return createSvgGeometryElementT(frame, Geometry.Path, name, attribute_iterator),
else => {},
},
5 => switch (@as(u40, @bitCast(name[0..5].*))) {
asUint("image") => return createSvgGraphicsElementT(frame, Graphics.Image, name, attribute_iterator),
else => {},
},
6 => switch (@as(u48, @bitCast(name[0..6].*))) {
asUint("circle") => return createSvgGeometryElementT(frame, Geometry.Circle, name, attribute_iterator),
else => {},
},
7 => switch (@as(u56, @bitCast(name[0..7].*))) {
asUint("ellipse") => return createSvgGeometryElementT(frame, Geometry.Ellipse, name, attribute_iterator),
asUint("polygon") => return createSvgGeometryElementT(frame, Geometry.Polygon, name, attribute_iterator),
else => {},
},
8 => switch (@as(u64, @bitCast(name[0..8].*))) {
asUint("polyline") => return createSvgGeometryElementT(frame, Geometry.Polyline, name, attribute_iterator),
else => {},
},
else => {},
}
// Other SVG elements (rect, circle, text, g, etc.)
const lower = std.ascii.lowerString(&frame.buf, name);
const tag = std.meta.stringToEnum(Element.Tag, lower) orelse .unknown;
return createSvgElementT(frame, Element.Svg.Generic, name, attribute_iterator, .{ ._proto = undefined, ._tag = tag });
@@ -930,7 +961,20 @@ fn createHtmlMediaElementT(frame: *Frame, comptime E: type, namespace: Element.N
fn createSvgElementT(frame: *Frame, comptime E: type, tag_name: []const u8, attribute_iterator: anytype, svg_element: E) !*Node {
const svg_element_ptr = try frame._factory.svgElement(tag_name, svg_element);
var element = svg_element_ptr.asElement();
return initSvgElement(frame, svg_element_ptr.asElement(), attribute_iterator);
}
fn createSvgGraphicsElementT(frame: *Frame, comptime E: type, tag_name: []const u8, attribute_iterator: anytype) !*Node {
const svg_element_ptr = try frame._factory.svgGraphicsElement(tag_name, E{ ._proto = undefined });
return initSvgElement(frame, svg_element_ptr.asElement(), attribute_iterator);
}
fn createSvgGeometryElementT(frame: *Frame, comptime E: type, tag_name: []const u8, attribute_iterator: anytype) !*Node {
const svg_element_ptr = try frame._factory.svgGeometryElement(tag_name, E{ ._proto = undefined });
return initSvgElement(frame, svg_element_ptr.asElement(), attribute_iterator);
}
fn initSvgElement(frame: *Frame, element: *Element, attribute_iterator: anytype) !*Node {
element._namespace = .svg;
element._attributes.normalize = false;
try populateElementAttributes(frame, element, attribute_iterator);

View File

@@ -995,6 +995,23 @@ pub const PageJsApis = flattenTypes(&.{
@import("../webapi/element/html/ValidityState.zig"),
@import("../webapi/element/Svg.zig"),
@import("../webapi/element/svg/Generic.zig"),
@import("../webapi/element/svg/Graphics.zig"),
@import("../webapi/element/svg/Geometry.zig"),
@import("../webapi/element/svg/Svg.zig"),
@import("../webapi/element/svg/G.zig"),
@import("../webapi/element/svg/A.zig"),
@import("../webapi/element/svg/Use.zig"),
@import("../webapi/element/svg/Image.zig"),
@import("../webapi/element/svg/Defs.zig"),
@import("../webapi/element/svg/Rect.zig"),
@import("../webapi/element/svg/Circle.zig"),
@import("../webapi/element/svg/Ellipse.zig"),
@import("../webapi/element/svg/Line.zig"),
@import("../webapi/element/svg/Path.zig"),
@import("../webapi/element/svg/Polygon.zig"),
@import("../webapi/element/svg/Polyline.zig"),
@import("../webapi/svg/AnimatedString.zig"),
@import("../webapi/svg/Number.zig"),
@import("../webapi/encoding/TextDecoder.zig"),
@import("../webapi/encoding/TextEncoder.zig"),
@import("../webapi/encoding/TextEncoderStream.zig"),

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<svg id=root class="chart wide">
<use id=use1 xlink:href="#icon"></use>
<use id=use2 href="#modern" xlink:href="#legacy"></use>
<a id=link1></a>
</svg>
<div id=plain class="html-class"></div>
<script id=className>
{
const root = $('#root');
testing.expectEqual('object', typeof root.className);
testing.expectEqual(true, root.className instanceof SVGAnimatedString);
testing.expectEqual('chart wide', root.className.baseVal);
testing.expectEqual('chart wide', root.className.animVal);
// [SameObject]
testing.expectEqual(true, root.className === root.className);
root.className.baseVal = 'chart narrow';
testing.expectEqual('chart narrow', root.getAttribute('class'));
testing.expectEqual('chart narrow', root.className.animVal);
testing.expectEqual(true, root.classList.contains('narrow'));
root.setAttribute('class', 'other');
testing.expectEqual('other', root.className.baseVal);
// The SVG className accessor doesn't leak onto HTML elements.
testing.expectEqual('html-class', $('#plain').className);
}
</script>
<script id=href>
{
// The parser stores xlink:href under its local name, href.
const use1 = $('#use1');
testing.expectEqual(true, use1.href instanceof SVGAnimatedString);
testing.expectEqual('#icon', use1.href.baseVal);
testing.expectEqual('#icon', use1.href.animVal);
testing.expectEqual(true, use1.href === use1.href);
testing.expectEqual('#modern', $('#use2').href.baseVal);
// Writes land in xlink:href in browsers (it's the attribute that's set)
// but in href for us since the parser stored it there; assert the common
// observable.
use1.href.baseVal = '#other';
testing.expectEqual('#other', use1.href.baseVal);
// setAttributeNS stores under the local name, so a runtime xlink:href
// lands in href and reflects like the parsed variant. (Browsers keep the
// xlink:href attribute but reflect it the same way.)
const use3 = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use3.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#runtime');
testing.expectEqual('#runtime', use3.href.baseVal);
// A namespace-less setAttribute('xlink:href') is a literal attribute in no
// namespace: not reflected, in browsers or here.
const use4 = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use4.setAttribute('xlink:href', '#literal');
testing.expectEqual('', use4.href.baseVal);
use4.href.baseVal = '#written';
testing.expectEqual('#written', use4.getAttribute('href'));
testing.expectEqual(true, use4.hasAttribute('xlink:href'));
// No href attribute at all: set writes to href.
const link = $('#link1');
testing.expectEqual('', link.href.baseVal);
link.href.baseVal = '/docs';
testing.expectEqual('/docs', link.getAttribute('href'));
}
</script>

View File

@@ -0,0 +1,115 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<svg id=root width="200" height="100">
<g id=group>
<rect id=rect1 width="10" height="10"></rect>
<circle id=circle1 r="5"></circle>
<ellipse id=ellipse1></ellipse>
<line id=line1></line>
<polygon id=polygon1></polygon>
<polyline id=polyline1></polyline>
</g>
<path id=path1 d="M 0 0 L 10 10"></path>
<a id=link1></a>
<use id=use1></use>
<image id=image1></image>
<defs id=defs1></defs>
<filter id=filter1></filter>
</svg>
<script id=classes>
{
testing.expectEqual('function', typeof SVGElement);
testing.expectEqual('function', typeof SVGGraphicsElement);
testing.expectEqual('function', typeof SVGGeometryElement);
testing.expectEqual('function', typeof SVGSVGElement);
testing.expectEqual('function', typeof SVGGElement);
testing.expectEqual('function', typeof SVGAElement);
testing.expectEqual('function', typeof SVGUseElement);
testing.expectEqual('function', typeof SVGImageElement);
testing.expectEqual('function', typeof SVGDefsElement);
testing.expectEqual('function', typeof SVGRectElement);
testing.expectEqual('function', typeof SVGCircleElement);
testing.expectEqual('function', typeof SVGEllipseElement);
testing.expectEqual('function', typeof SVGLineElement);
testing.expectEqual('function', typeof SVGPathElement);
testing.expectEqual('function', typeof SVGPolygonElement);
testing.expectEqual('function', typeof SVGPolylineElement);
testing.expectEqual('function', typeof SVGAnimatedString);
testing.expectEqual('function', typeof SVGNumber);
}
</script>
<script id=instanceof>
{
const root = $('#root');
testing.expectEqual(true, root instanceof SVGSVGElement);
testing.expectEqual(true, root instanceof SVGGraphicsElement);
testing.expectEqual(true, root instanceof SVGElement);
testing.expectEqual(true, root instanceof Element);
testing.expectEqual(false, root instanceof SVGGeometryElement);
testing.expectEqual('svg', root.tagName);
const rect = $('#rect1');
testing.expectEqual(true, rect instanceof SVGRectElement);
testing.expectEqual(true, rect instanceof SVGGeometryElement);
testing.expectEqual(true, rect instanceof SVGGraphicsElement);
testing.expectEqual(true, rect instanceof SVGElement);
testing.expectEqual(false, rect instanceof SVGSVGElement);
testing.expectEqual('rect', rect.tagName);
testing.expectEqual('10', rect.getAttribute('width'));
testing.expectEqual(true, $('#group') instanceof SVGGElement);
testing.expectEqual(true, $('#circle1') instanceof SVGCircleElement);
testing.expectEqual(true, $('#ellipse1') instanceof SVGEllipseElement);
testing.expectEqual(true, $('#line1') instanceof SVGLineElement);
testing.expectEqual(true, $('#polygon1') instanceof SVGPolygonElement);
testing.expectEqual(true, $('#polyline1') instanceof SVGPolylineElement);
testing.expectEqual(true, $('#path1') instanceof SVGPathElement);
testing.expectEqual(true, $('#link1') instanceof SVGAElement);
testing.expectEqual(true, $('#use1') instanceof SVGUseElement);
testing.expectEqual(true, $('#image1') instanceof SVGImageElement);
testing.expectEqual(true, $('#defs1') instanceof SVGDefsElement);
// Elements without a dedicated type stay generic, but are still SVGElement.
const filter = $('#filter1');
testing.expectEqual(true, filter instanceof SVGElement);
testing.expectEqual(false, filter instanceof SVGGraphicsElement);
testing.expectEqual('filter', filter.tagName);
}
</script>
<script id=createElementNS>
{
const NS = 'http://www.w3.org/2000/svg';
const rect = document.createElementNS(NS, 'rect');
testing.expectEqual(true, rect instanceof SVGRectElement);
testing.expectEqual('rect', rect.tagName);
// SVG tag names are case-sensitive: 'RECT' isn't a rect element.
const notRect = document.createElementNS(NS, 'RECT');
testing.expectEqual(false, notRect instanceof SVGRectElement);
testing.expectEqual(true, notRect instanceof SVGElement);
testing.expectEqual('RECT', notRect.tagName);
const svg = document.createElementNS(NS, 'svg');
testing.expectEqual(true, svg instanceof SVGSVGElement);
// querySelector still matches both dedicated and generic types.
testing.expectEqual($('#rect1'), document.querySelector('#root rect'));
testing.expectEqual($('#use1'), document.querySelector('#root use'));
testing.expectEqual($('#filter1'), document.querySelector('#root filter'));
}
</script>
<script id=hasAttributeNS>
{
// Parser-created attributes have a null namespace. We ignore attribute
// namespaces, so only null/'' lookups behave like a real browser here.
const rect = $('#rect1');
testing.expectEqual(true, rect.hasAttributeNS(null, 'width'));
testing.expectEqual(true, rect.hasAttributeNS('', 'width'));
testing.expectEqual(false, rect.hasAttributeNS(null, 'nope'));
}
</script>

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<svg id=first>
<g>
<rect id=inner-rect></rect>
<svg id=nested>
<circle id=nested-circle></circle>
</svg>
</g>
</svg>
<svg id=second>
<rect id=other-rect></rect>
</svg>
<script id=getElementById>
{
const first = $('#first');
testing.expectEqual($('#inner-rect'), first.getElementById('inner-rect'));
testing.expectEqual($('#nested-circle'), first.getElementById('nested-circle'));
// Scoped to the subtree, unlike document.getElementById.
testing.expectEqual(null, first.getElementById('other-rect'));
testing.expectEqual(null, $('#second').getElementById('inner-rect'));
testing.expectEqual(null, first.getElementById(''));
testing.expectEqual(null, first.getElementById('missing'));
}
</script>
<script id=factories>
{
// SVG2 defines SVGPoint/SVGMatrix/SVGRect as aliases of the DOM geometry
// types, which is what we return. Browsers (Chrome and Firefox both) still
// ship distinct legacy interfaces, so the instanceof and DOM-geometry-only
// members (w, is2D) fail there. The DOM types are functional supersets of
// the legacy ones, so site-facing behavior only gains members.
const svg = $('#first');
const point = svg.createSVGPoint();
testing.expectEqual(true, point instanceof DOMPoint);
testing.expectEqual(0, point.x);
testing.expectEqual(0, point.y);
testing.expectEqual(1, point.w);
point.x = 5;
testing.expectEqual(5, point.x);
const matrix = svg.createSVGMatrix();
testing.expectEqual(true, matrix instanceof DOMMatrix);
testing.expectEqual(1, matrix.a);
testing.expectEqual(0, matrix.b);
testing.expectEqual(1, matrix.d);
testing.expectEqual(true, matrix.is2D);
const rect = svg.createSVGRect();
testing.expectEqual(true, rect instanceof DOMRect);
testing.expectEqual(0, rect.x);
testing.expectEqual(0, rect.width);
const number = svg.createSVGNumber();
testing.expectEqual(true, number instanceof SVGNumber);
testing.expectEqual(0, number.value);
number.value = 4.5;
testing.expectEqual(4.5, number.value);
}
</script>
<script id=owner>
{
const first = $('#first');
const nested = $('#nested');
testing.expectEqual(null, first.ownerSVGElement);
testing.expectEqual(null, first.viewportElement);
testing.expectEqual(first, $('#inner-rect').ownerSVGElement);
testing.expectEqual(first, $('#inner-rect').viewportElement);
testing.expectEqual(first, nested.ownerSVGElement);
testing.expectEqual(nested, $('#nested-circle').ownerSVGElement);
testing.expectEqual($('#second'), $('#other-rect').ownerSVGElement);
// A foreignObject starts a new SVG document fragment.
first.innerHTML += '<foreignObject><div><svg id=foreign-svg></svg></div></foreignObject>';
testing.expectEqual(null, $('#foreign-svg').ownerSVGElement);
}
</script>

View File

@@ -126,7 +126,7 @@ pub fn is(self: *Element, comptime T: type) ?*T {
if (T == Svg) {
return svg;
}
if (comptime std.mem.startsWith(u8, type_name, "webapi.element.svg.")) {
if (comptime std.mem.startsWith(u8, type_name, "browser.webapi.element.svg.")) {
return svg.is(T);
}
},
@@ -583,6 +583,22 @@ pub fn hasAttribute(self: *const Element, name: String, frame: *Frame) !bool {
return value != null;
}
/// Like getAttributeNS, the namespace is currently ignored.
pub fn hasAttributeNS(
self: *const Element,
maybe_namespace: ?[]const u8,
local_name: String,
frame: *Frame,
) !bool {
if (maybe_namespace) |namespace| {
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
log.warn(.not_implemented, "Element.hasAttributeNS", .{ .namespace = namespace });
}
}
return self.hasAttribute(local_name, frame);
}
pub fn hasAttributeSafe(self: *const Element, name: String) bool {
return self._attributes.hasSafe(name);
}
@@ -1673,10 +1689,7 @@ pub fn getTag(self: *const Element) Tag {
.head => .head,
.unknown => .unknown,
},
.svg => |se| switch (se._type) {
.svg => .svg,
.generic => |g| g._tag,
},
.svg => |se| se.getTag(),
};
}
@@ -1940,6 +1953,7 @@ pub const JsApi = struct {
pub const style = bridge.accessor(Element.getOrCreateStyle, Element.setStyle, .{});
pub const attributes = bridge.accessor(Element.getAttributeNamedNodeMap, null, .{});
pub const hasAttribute = bridge.function(Element.hasAttribute, .{});
pub const hasAttributeNS = bridge.function(Element.hasAttributeNS, .{});
pub const hasAttributes = bridge.function(Element.hasAttributes, .{});
pub const getAttribute = bridge.function(Element.getAttribute, .{});
pub const getAttributeNS = bridge.function(Element.getAttributeNS, .{});

View File

@@ -16,13 +16,17 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const Frame = @import("../../Frame.zig");
const Node = @import("../Node.zig");
const Element = @import("../Element.zig");
const AnimatedString = @import("../svg/AnimatedString.zig");
pub const Generic = @import("svg/Generic.zig");
pub const Graphics = @import("svg/Graphics.zig");
const String = lp.String;
@@ -32,24 +36,49 @@ _proto: *Element,
_tag_name: String, // Svg elements are case-preserving
pub const Type = union(enum) {
svg,
graphics: *Graphics,
generic: *Generic,
};
pub fn is(self: *Svg, comptime T: type) ?*T {
inline for (@typeInfo(Type).@"union".fields) |f| {
if (@field(Type, f.name) == self._type) {
if (f.type == T) {
return &@field(self._type, f.name);
switch (self._type) {
.graphics => |g| {
if (T == Graphics) {
return g;
}
if (f.type == *T) {
return @field(self._type, f.name);
return g.is(T);
},
.generic => |g| {
if (T == Generic) {
return g;
}
}
},
}
return null;
}
pub fn getTag(self: *const Svg) Element.Tag {
return switch (self._type) {
.graphics => |g| switch (g._type) {
.svg => .svg,
.g => .g,
// No dedicated Element.Tag values; tag-name matching falls back
// to _tag_name, like it does for generic SVG elements.
.a, .use, .image, .defs => .unknown,
.geometry => |geo| switch (geo._type) {
.rect => .rect,
.circle => .circle,
.ellipse => .ellipse,
.line => .line,
.path => .path,
.polygon => .polygon,
.polyline => .polyline,
},
},
.generic => |g| g._tag,
};
}
pub fn asElement(self: *Svg) *Element {
return self._proto;
}
@@ -57,6 +86,25 @@ pub fn asNode(self: *Svg) *Node {
return self.asElement().asNode();
}
// The nearest ancestor <svg> element, null when this is the outermost svg.
pub fn getOwnerSvgElement(self: *Svg) ?*Graphics.Svg {
var node = self.asNode().parentNode();
while (node) |n| : (node = n.parentNode()) {
const element = n.is(Element) orelse return null;
if (element._namespace != .svg) {
return null;
}
const svg = element.as(Svg);
if (svg.is(Graphics.Svg)) |gfx_svg| {
return gfx_svg;
}
if (svg._tag_name.eql(.wrap("foreignObject"))) {
return null;
}
}
return null;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Svg);
@@ -65,6 +113,16 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
// Overrides el.className to return a readonly AnimatedString
pub const className = bridge.accessor(_className, null, .{});
fn _className(self: *Svg, frame: *Frame) !*AnimatedString {
return AnimatedString.getOrCreate(self.asElement(), .class, frame);
}
pub const ownerSVGElement = bridge.accessor(Svg.getOwnerSvgElement, null, .{});
// closest thing we can provide
pub const viewportElement = bridge.accessor(Svg.getOwnerSvgElement, null, .{});
};
const testing = @import("../../../testing.zig");

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const AnimatedString = @import("../../svg/AnimatedString.zig");
const Graphics = @import("Graphics.zig");
const A = @This();
_proto: *Graphics,
pub fn asElement(self: *A) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *A) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(A);
pub const Meta = struct {
pub const name = "SVGAElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const href = bridge.accessor(_href, null, .{});
fn _href(self: *A, frame: *Frame) !*AnimatedString {
return AnimatedString.getOrCreate(self.asElement(), .href, frame);
}
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Geometry = @import("Geometry.zig");
const Circle = @This();
_proto: *Geometry,
pub fn asElement(self: *Circle) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Circle) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Circle);
pub const Meta = struct {
pub const name = "SVGCircleElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Graphics = @import("Graphics.zig");
const Defs = @This();
_proto: *Graphics,
pub fn asElement(self: *Defs) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Defs) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Defs);
pub const Meta = struct {
pub const name = "SVGDefsElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Geometry = @import("Geometry.zig");
const Ellipse = @This();
_proto: *Geometry,
pub fn asElement(self: *Ellipse) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Ellipse) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Ellipse);
pub const Meta = struct {
pub const name = "SVGEllipseElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Graphics = @import("Graphics.zig");
const G = @This();
_proto: *Graphics,
pub fn asElement(self: *G) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *G) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(G);
pub const Meta = struct {
pub const name = "SVGGElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -17,8 +17,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Svg = @import("../Svg.zig");
const Generic = @This();

View File

@@ -0,0 +1,73 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Graphics = @import("Graphics.zig");
pub const Rect = @import("Rect.zig");
pub const Circle = @import("Circle.zig");
pub const Ellipse = @import("Ellipse.zig");
pub const Line = @import("Line.zig");
pub const Path = @import("Path.zig");
pub const Polygon = @import("Polygon.zig");
pub const Polyline = @import("Polyline.zig");
const Geometry = @This();
_proto: *Graphics,
_type: Type,
pub const Type = union(enum) {
rect: *Rect,
circle: *Circle,
ellipse: *Ellipse,
line: *Line,
path: *Path,
polygon: *Polygon,
polyline: *Polyline,
};
pub fn is(self: *Geometry, comptime T: type) ?*T {
inline for (@typeInfo(Type).@"union".fields) |f| {
if (@field(Type, f.name) == self._type) {
if (f.type == *T) {
return @field(self._type, f.name);
}
}
}
return null;
}
pub fn asElement(self: *Geometry) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Geometry) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Geometry);
pub const Meta = struct {
pub const name = "SVGGeometryElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,75 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const SvgElement = @import("../Svg.zig");
pub const Svg = @import("Svg.zig");
pub const G = @import("G.zig");
pub const A = @import("A.zig");
pub const Use = @import("Use.zig");
pub const Image = @import("Image.zig");
pub const Defs = @import("Defs.zig");
pub const Geometry = @import("Geometry.zig");
const Graphics = @This();
_proto: *SvgElement,
_type: Type,
pub const Type = union(enum) {
svg: *Svg,
g: *G,
a: *A,
use: *Use,
image: *Image,
defs: *Defs,
geometry: *Geometry,
};
pub fn is(self: *Graphics, comptime T: type) ?*T {
inline for (@typeInfo(Type).@"union".fields) |f| {
if (@field(Type, f.name) == self._type) {
if (f.type == *T) {
return @field(self._type, f.name);
}
}
}
if (self._type == .geometry) {
return self._type.geometry.is(T);
}
return null;
}
pub fn asElement(self: *Graphics) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Graphics) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Graphics);
pub const Meta = struct {
pub const name = "SVGGraphicsElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const AnimatedString = @import("../../svg/AnimatedString.zig");
const Graphics = @import("Graphics.zig");
const Image = @This();
_proto: *Graphics,
pub fn asElement(self: *Image) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Image) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Image);
pub const Meta = struct {
pub const name = "SVGImageElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const href = bridge.accessor(_href, null, .{});
fn _href(self: *Image, frame: *Frame) !*AnimatedString {
return AnimatedString.getOrCreate(self.asElement(), .href, frame);
}
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Geometry = @import("Geometry.zig");
const Line = @This();
_proto: *Geometry,
pub fn asElement(self: *Line) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Line) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Line);
pub const Meta = struct {
pub const name = "SVGLineElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Geometry = @import("Geometry.zig");
const Path = @This();
_proto: *Geometry,
pub fn asElement(self: *Path) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Path) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Path);
pub const Meta = struct {
pub const name = "SVGPathElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Geometry = @import("Geometry.zig");
const Polygon = @This();
_proto: *Geometry,
pub fn asElement(self: *Polygon) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Polygon) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Polygon);
pub const Meta = struct {
pub const name = "SVGPolygonElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Geometry = @import("Geometry.zig");
const Polyline = @This();
_proto: *Geometry,
pub fn asElement(self: *Polyline) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Polyline) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Polyline);
pub const Meta = struct {
pub const name = "SVGPolylineElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
};

View File

@@ -19,13 +19,13 @@
const js = @import("../../../js/js.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const Svg = @import("../Svg.zig");
const Geometry = @import("Geometry.zig");
const Rect = @This();
_proto: *Svg,
_proto: *Geometry,
pub fn asElement(self: *Rect) *Element {
return self._proto._proto;
return self._proto.asElement();
}
pub fn asNode(self: *Rect) *Node {
return self.asElement().asNode();

View File

@@ -0,0 +1,95 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const TreeWalker = @import("../../TreeWalker.zig");
const DOMRect = @import("../../DOMRect.zig");
const DOMPoint = @import("../../DOMPoint.zig");
const DOMMatrix = @import("../../DOMMatrix.zig");
const SvgNumber = @import("../../svg/Number.zig");
const Graphics = @import("Graphics.zig");
const Svg = @This();
_proto: *Graphics,
pub fn asElement(self: *Svg) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Svg) *Node {
return self.asElement().asNode();
}
pub fn getElementById(self: *Svg, id: []const u8) ?*Element {
if (id.len == 0) {
return null;
}
var tw = TreeWalker.Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| {
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
if (std.mem.eql(u8, element_id, id)) {
return el;
}
}
return null;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Svg);
pub const Meta = struct {
pub const name = "SVGSVGElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const getElementById = bridge.function(Svg.getElementById, .{});
pub const createSVGPoint = bridge.function(_createSVGPoint, .{});
fn _createSVGPoint(_: *Svg, frame: *Frame) !*DOMPoint {
return DOMPoint.create(0, 0, 0, 1, frame._page);
}
pub const createSVGMatrix = bridge.function(_createSVGMatrix, .{});
fn _createSVGMatrix(_: *Svg, frame: *Frame) !*DOMMatrix {
const identity: [16]f64 = .{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
};
return DOMMatrix.create(identity, true, frame._page);
}
pub const createSVGRect = bridge.function(_createSVGRect, .{});
fn _createSVGRect(_: *Svg, frame: *Frame) !*DOMRect {
return DOMRect.init(0, 0, 0, 0, frame);
}
pub const createSVGNumber = bridge.function(_createSVGNumber, .{});
fn _createSVGNumber(_: *Svg, frame: *Frame) !*SvgNumber {
return frame._factory.create(SvgNumber{});
}
};

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const AnimatedString = @import("../../svg/AnimatedString.zig");
const Graphics = @import("Graphics.zig");
const Use = @This();
_proto: *Graphics,
pub fn asElement(self: *Use) *Element {
return self._proto.asElement();
}
pub fn asNode(self: *Use) *Node {
return self.asElement().asNode();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Use);
pub const Meta = struct {
pub const name = "SVGUseElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const href = bridge.accessor(_href, null, .{});
fn _href(self: *Use, frame: *Frame) !*AnimatedString {
return AnimatedString.getOrCreate(self.asElement(), .href, frame);
}
};

View File

@@ -0,0 +1,84 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig");
const Frame = @import("../../Frame.zig");
const Element = @import("../Element.zig");
pub const Lookup = std.AutoHashMapUnmanaged(Key, *AnimatedString);
const String = lp.String;
const AnimatedString = @This();
_kind: Kind,
_element: *Element,
pub const Kind = enum { class, href };
pub const Key = struct {
element: *Element,
kind: Kind,
};
// Identity map for AnimatedString, help by the frame
pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedString {
const gop = try frame._svg_animated_strings.getOrPut(frame.arena, .{ .element = element, .kind = kind });
if (!gop.found_existing) {
gop.value_ptr.* = try frame._factory.create(AnimatedString{
._element = element,
._kind = kind,
});
}
return gop.value_ptr.*;
}
pub fn getBaseVal(self: *const AnimatedString) []const u8 {
return self._element.getAttributeSafe(self.attributeName()) orelse "";
}
pub fn setBaseVal(self: *AnimatedString, value: String, frame: *Frame) !void {
try self._element.setAttribute(self.attributeName(), value, frame);
}
// No real animation, return the BaseVal
pub fn getAnimVal(self: *const AnimatedString) []const u8 {
return self.getBaseVal();
}
fn attributeName(self: *const AnimatedString) String {
return switch (self._kind) {
.href => comptime .wrap("href"),
.class => comptime .wrap("class"),
};
}
pub const JsApi = struct {
pub const bridge = js.Bridge(AnimatedString);
pub const Meta = struct {
pub const name = "SVGAnimatedString";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const baseVal = bridge.accessor(AnimatedString.getBaseVal, AnimatedString.setBaseVal, .{});
pub const animVal = bridge.accessor(AnimatedString.getAnimVal, null, .{});
};

View File

@@ -0,0 +1,42 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../js/js.zig");
const Number = @This();
_value: f32 = 0,
pub fn getValue(self: *const Number) f32 {
return self._value;
}
pub fn setValue(self: *Number, value: f32) void {
self._value = value;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Number);
pub const Meta = struct {
pub const name = "SVGNumber";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const value = bridge.accessor(Number.getValue, Number.setValue, .{});
};