From 1967be7e54eb5d60afda33981da4ac583f9b7092 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 11 Jul 2026 18:31:09 +0800 Subject: [PATCH] webapi: Add various SVG types Rather than having a generic SVG type (which gets us past parsing, but even the slightest JS usage is likely to fail), this adds concrete types for a number of known SVG types. A lot of these are empty (but even that's enough to pass something like `instanceof SVGAElement`), but a handful of the more important accessors and methods are implemented (e.g. I ran into a site that made extensive use of SVGSVGElement.getElementById). --- src/browser/Factory.zig | 48 +++++--- src/browser/Frame.zig | 2 + src/browser/frame/node_factory.zig | 62 ++++++++-- src/browser/js/bridge.zig | 17 +++ .../tests/element/svg/animated_string.html | 73 +++++++++++ src/browser/tests/element/svg/hierarchy.html | 115 ++++++++++++++++++ src/browser/tests/element/svg/svgsvg.html | 85 +++++++++++++ src/browser/webapi/Element.zig | 24 +++- src/browser/webapi/element/Svg.zig | 74 +++++++++-- src/browser/webapi/element/svg/A.zig | 51 ++++++++ src/browser/webapi/element/svg/Circle.zig | 44 +++++++ src/browser/webapi/element/svg/Defs.zig | 44 +++++++ src/browser/webapi/element/svg/Ellipse.zig | 44 +++++++ src/browser/webapi/element/svg/G.zig | 44 +++++++ src/browser/webapi/element/svg/Generic.zig | 2 + src/browser/webapi/element/svg/Geometry.zig | 73 +++++++++++ src/browser/webapi/element/svg/Graphics.zig | 75 ++++++++++++ src/browser/webapi/element/svg/Image.zig | 51 ++++++++ src/browser/webapi/element/svg/Line.zig | 44 +++++++ src/browser/webapi/element/svg/Path.zig | 44 +++++++ src/browser/webapi/element/svg/Polygon.zig | 44 +++++++ src/browser/webapi/element/svg/Polyline.zig | 44 +++++++ src/browser/webapi/element/svg/Rect.zig | 6 +- src/browser/webapi/element/svg/Svg.zig | 95 +++++++++++++++ src/browser/webapi/element/svg/Use.zig | 51 ++++++++ src/browser/webapi/svg/AnimatedString.zig | 84 +++++++++++++ src/browser/webapi/svg/Number.zig | 42 +++++++ 27 files changed, 1341 insertions(+), 41 deletions(-) create mode 100644 src/browser/tests/element/svg/animated_string.html create mode 100644 src/browser/tests/element/svg/hierarchy.html create mode 100644 src/browser/tests/element/svg/svgsvg.html create mode 100644 src/browser/webapi/element/svg/A.zig create mode 100644 src/browser/webapi/element/svg/Circle.zig create mode 100644 src/browser/webapi/element/svg/Defs.zig create mode 100644 src/browser/webapi/element/svg/Ellipse.zig create mode 100644 src/browser/webapi/element/svg/G.zig create mode 100644 src/browser/webapi/element/svg/Geometry.zig create mode 100644 src/browser/webapi/element/svg/Graphics.zig create mode 100644 src/browser/webapi/element/svg/Image.zig create mode 100644 src/browser/webapi/element/svg/Line.zig create mode 100644 src/browser/webapi/element/svg/Path.zig create mode 100644 src/browser/webapi/element/svg/Polygon.zig create mode 100644 src/browser/webapi/element/svg/Polyline.zig create mode 100644 src/browser/webapi/element/svg/Svg.zig create mode 100644 src/browser/webapi/element/svg/Use.zig create mode 100644 src/browser/webapi/svg/AnimatedString.zig create mode 100644 src/browser/webapi/svg/Number.zig diff --git a/src/browser/Factory.zig b/src/browser/Factory.zig index 248ededab..9e1ee0685 100644 --- a/src/browser/Factory.zig +++ b/src/browser/Factory.zig @@ -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) { diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 23c2047c8..e4a6a0fdf 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -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) diff --git a/src/browser/frame/node_factory.zig b/src/browser/frame/node_factory.zig index b647e439c..95dd36ec9 100644 --- a/src/browser/frame/node_factory.zig +++ b/src/browser/frame/node_factory.zig @@ -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); diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 627563a95..b82ef3d47 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -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"), diff --git a/src/browser/tests/element/svg/animated_string.html b/src/browser/tests/element/svg/animated_string.html new file mode 100644 index 000000000..7a8520eca --- /dev/null +++ b/src/browser/tests/element/svg/animated_string.html @@ -0,0 +1,73 @@ + + + + + + + + +
+ + + + diff --git a/src/browser/tests/element/svg/hierarchy.html b/src/browser/tests/element/svg/hierarchy.html new file mode 100644 index 000000000..17dd71194 --- /dev/null +++ b/src/browser/tests/element/svg/hierarchy.html @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/element/svg/svgsvg.html b/src/browser/tests/element/svg/svgsvg.html new file mode 100644 index 000000000..e73b55587 --- /dev/null +++ b/src/browser/tests/element/svg/svgsvg.html @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 2ed88adc4..04a9bc2d2 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -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(), }; } @@ -1936,6 +1949,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, .{}); diff --git a/src/browser/webapi/element/Svg.zig b/src/browser/webapi/element/Svg.zig index 1d30d41c6..8594eaa5b 100644 --- a/src/browser/webapi/element/Svg.zig +++ b/src/browser/webapi/element/Svg.zig @@ -16,13 +16,17 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +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 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"); diff --git a/src/browser/webapi/element/svg/A.zig b/src/browser/webapi/element/svg/A.zig new file mode 100644 index 000000000..da5cad8dd --- /dev/null +++ b/src/browser/webapi/element/svg/A.zig @@ -0,0 +1,51 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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); + } +}; diff --git a/src/browser/webapi/element/svg/Circle.zig b/src/browser/webapi/element/svg/Circle.zig new file mode 100644 index 000000000..067f23904 --- /dev/null +++ b/src/browser/webapi/element/svg/Circle.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Defs.zig b/src/browser/webapi/element/svg/Defs.zig new file mode 100644 index 000000000..ba3f85cce --- /dev/null +++ b/src/browser/webapi/element/svg/Defs.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Ellipse.zig b/src/browser/webapi/element/svg/Ellipse.zig new file mode 100644 index 000000000..00a1311b5 --- /dev/null +++ b/src/browser/webapi/element/svg/Ellipse.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/G.zig b/src/browser/webapi/element/svg/G.zig new file mode 100644 index 000000000..26a264173 --- /dev/null +++ b/src/browser/webapi/element/svg/G.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Generic.zig b/src/browser/webapi/element/svg/Generic.zig index dfde518d0..86af4455e 100644 --- a/src/browser/webapi/element/svg/Generic.zig +++ b/src/browser/webapi/element/svg/Generic.zig @@ -17,8 +17,10 @@ // along with this program. If not, see . const js = @import("../../../js/js.zig"); + const Node = @import("../../Node.zig"); const Element = @import("../../Element.zig"); + const Svg = @import("../Svg.zig"); const Generic = @This(); diff --git a/src/browser/webapi/element/svg/Geometry.zig b/src/browser/webapi/element/svg/Geometry.zig new file mode 100644 index 000000000..280feb818 --- /dev/null +++ b/src/browser/webapi/element/svg/Geometry.zig @@ -0,0 +1,73 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Graphics.zig b/src/browser/webapi/element/svg/Graphics.zig new file mode 100644 index 000000000..ccb1386b9 --- /dev/null +++ b/src/browser/webapi/element/svg/Graphics.zig @@ -0,0 +1,75 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Image.zig b/src/browser/webapi/element/svg/Image.zig new file mode 100644 index 000000000..73a36abac --- /dev/null +++ b/src/browser/webapi/element/svg/Image.zig @@ -0,0 +1,51 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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); + } +}; diff --git a/src/browser/webapi/element/svg/Line.zig b/src/browser/webapi/element/svg/Line.zig new file mode 100644 index 000000000..d40e1e305 --- /dev/null +++ b/src/browser/webapi/element/svg/Line.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Path.zig b/src/browser/webapi/element/svg/Path.zig new file mode 100644 index 000000000..9308449fd --- /dev/null +++ b/src/browser/webapi/element/svg/Path.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Polygon.zig b/src/browser/webapi/element/svg/Polygon.zig new file mode 100644 index 000000000..752917079 --- /dev/null +++ b/src/browser/webapi/element/svg/Polygon.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Polyline.zig b/src/browser/webapi/element/svg/Polyline.zig new file mode 100644 index 000000000..a232e4950 --- /dev/null +++ b/src/browser/webapi/element/svg/Polyline.zig @@ -0,0 +1,44 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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; + }; +}; diff --git a/src/browser/webapi/element/svg/Rect.zig b/src/browser/webapi/element/svg/Rect.zig index 43edf942c..c6dd4f40c 100644 --- a/src/browser/webapi/element/svg/Rect.zig +++ b/src/browser/webapi/element/svg/Rect.zig @@ -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(); diff --git a/src/browser/webapi/element/svg/Svg.zig b/src/browser/webapi/element/svg/Svg.zig new file mode 100644 index 000000000..920b25c7f --- /dev/null +++ b/src/browser/webapi/element/svg/Svg.zig @@ -0,0 +1,95 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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{}); + } +}; diff --git a/src/browser/webapi/element/svg/Use.zig b/src/browser/webapi/element/svg/Use.zig new file mode 100644 index 000000000..fc49f7517 --- /dev/null +++ b/src/browser/webapi/element/svg/Use.zig @@ -0,0 +1,51 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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); + } +}; diff --git a/src/browser/webapi/svg/AnimatedString.zig b/src/browser/webapi/svg/AnimatedString.zig new file mode 100644 index 000000000..0f3d6cc5f --- /dev/null +++ b/src/browser/webapi/svg/AnimatedString.zig @@ -0,0 +1,84 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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, .{}); +}; diff --git a/src/browser/webapi/svg/Number.zig b/src/browser/webapi/svg/Number.zig new file mode 100644 index 000000000..d1a29feca --- /dev/null +++ b/src/browser/webapi/svg/Number.zig @@ -0,0 +1,42 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +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, .{}); +};