From 0648da895ce07fbe5a47b2d85941fbb9f94af281 Mon Sep 17 00:00:00 2001 From: Madison Steiner <8176115+mh0pe@users.noreply.github.com> Date: Tue, 21 Jul 2026 22:45:30 -0700 Subject: [PATCH 1/2] webapi: add SVG structural elements --- src/browser/frame/node_factory.zig | 9 ++ src/browser/js/bridge.zig | 7 ++ src/browser/tests/element/svg/hierarchy.html | 7 ++ src/browser/tests/element/svg/structure.html | 108 ++++++++++++++++++ src/browser/webapi/element/Svg.zig | 29 +++-- src/browser/webapi/element/svg/Desc.zig | 26 +++++ .../webapi/element/svg/ForeignObject.zig | 63 ++++++++++ src/browser/webapi/element/svg/Graphics.zig | 67 ++++++++--- src/browser/webapi/element/svg/Metadata.zig | 26 +++++ src/browser/webapi/element/svg/Switch.zig | 26 +++++ src/browser/webapi/element/svg/Symbol.zig | 26 +++++ src/browser/webapi/element/svg/Title.zig | 26 +++++ src/browser/webapi/element/svg/View.zig | 26 +++++ 13 files changed, 419 insertions(+), 27 deletions(-) create mode 100644 src/browser/tests/element/svg/structure.html create mode 100644 src/browser/webapi/element/svg/Desc.zig create mode 100644 src/browser/webapi/element/svg/ForeignObject.zig create mode 100644 src/browser/webapi/element/svg/Metadata.zig create mode 100644 src/browser/webapi/element/svg/Switch.zig create mode 100644 src/browser/webapi/element/svg/Symbol.zig create mode 100644 src/browser/webapi/element/svg/Title.zig create mode 100644 src/browser/webapi/element/svg/View.zig diff --git a/src/browser/frame/node_factory.zig b/src/browser/frame/node_factory.zig index 8b7f6d74b..ddd0aa083 100644 --- a/src/browser/frame/node_factory.zig +++ b/src/browser/frame/node_factory.zig @@ -901,17 +901,22 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons }, 4 => switch (@as(u32, @bitCast(name[0..4].*))) { asUint("defs") => return createSvgElementT(frame, Graphics.Defs, name, attribute_iterator, .{ ._proto = undefined }), + asUint("desc") => return createSvgElementT(frame, Element.Svg.Desc, name, attribute_iterator, .{ ._proto = undefined }), asUint("rect") => return createSvgElementT(frame, Geometry.Rect, name, attribute_iterator, .{ ._proto = undefined }), asUint("line") => return createSvgElementT(frame, Geometry.Line, name, attribute_iterator, .{ ._proto = undefined }), asUint("path") => return createSvgElementT(frame, Geometry.Path, name, attribute_iterator, .{ ._proto = undefined }), + asUint("view") => return createSvgElementT(frame, Element.Svg.View, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 5 => switch (@as(u40, @bitCast(name[0..5].*))) { asUint("image") => return createSvgElementT(frame, Graphics.Image, name, attribute_iterator, .{ ._proto = undefined }), + asUint("title") => return createSvgElementT(frame, Element.Svg.Title, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 6 => switch (@as(u48, @bitCast(name[0..6].*))) { asUint("circle") => return createSvgElementT(frame, Geometry.Circle, name, attribute_iterator, .{ ._proto = undefined }), + asUint("switch") => return createSvgElementT(frame, Graphics.Switch, name, attribute_iterator, .{ ._proto = undefined }), + asUint("symbol") => return createSvgElementT(frame, Graphics.Symbol, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, 7 => switch (@as(u56, @bitCast(name[0..7].*))) { @@ -920,9 +925,13 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons else => {}, }, 8 => switch (@as(u64, @bitCast(name[0..8].*))) { + asUint("metadata") => return createSvgElementT(frame, Element.Svg.Metadata, name, attribute_iterator, .{ ._proto = undefined }), asUint("polyline") => return createSvgElementT(frame, Geometry.Polyline, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, + 13 => if (std.mem.eql(u8, name, "foreignObject")) { + return createSvgElementT(frame, Graphics.ForeignObject, name, attribute_iterator, .{ ._proto = undefined }); + }, else => {}, } diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index b0c13794e..953d0bf97 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -1061,6 +1061,13 @@ pub const PageJsApis = flattenTypes(&.{ @import("../webapi/element/svg/Use.zig"), @import("../webapi/element/svg/Image.zig"), @import("../webapi/element/svg/Defs.zig"), + @import("../webapi/element/svg/Symbol.zig"), + @import("../webapi/element/svg/Switch.zig"), + @import("../webapi/element/svg/ForeignObject.zig"), + @import("../webapi/element/svg/View.zig"), + @import("../webapi/element/svg/Title.zig"), + @import("../webapi/element/svg/Desc.zig"), + @import("../webapi/element/svg/Metadata.zig"), @import("../webapi/element/svg/Rect.zig"), @import("../webapi/element/svg/Circle.zig"), @import("../webapi/element/svg/Ellipse.zig"), diff --git a/src/browser/tests/element/svg/hierarchy.html b/src/browser/tests/element/svg/hierarchy.html index 441b94b4b..3a1e8a795 100644 --- a/src/browser/tests/element/svg/hierarchy.html +++ b/src/browser/tests/element/svg/hierarchy.html @@ -39,6 +39,13 @@ testing.expectEqual('function', typeof SVGAnimatedString); testing.expectEqual('function', typeof SVGNumber); testing.expectEqual('function', typeof SVGAnimatedNumber); + testing.expectEqual('function', typeof SVGSymbolElement); + testing.expectEqual('function', typeof SVGSwitchElement); + testing.expectEqual('function', typeof SVGForeignObjectElement); + testing.expectEqual('function', typeof SVGViewElement); + testing.expectEqual('function', typeof SVGTitleElement); + testing.expectEqual('function', typeof SVGDescElement); + testing.expectEqual('function', typeof SVGMetadataElement); } diff --git a/src/browser/tests/element/svg/structure.html b/src/browser/tests/element/svg/structure.html new file mode 100644 index 000000000..fc96901b8 --- /dev/null +++ b/src/browser/tests/element/svg/structure.html @@ -0,0 +1,108 @@ + + + + + Accessible title + Long description + value + + + + + +
HTML content
+
+
+
+ + + + + + + + diff --git a/src/browser/webapi/element/Svg.zig b/src/browser/webapi/element/Svg.zig index 8594eaa5b..86e0eeb3b 100644 --- a/src/browser/webapi/element/Svg.zig +++ b/src/browser/webapi/element/Svg.zig @@ -27,6 +27,10 @@ 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"); +pub const View = @import("svg/View.zig"); +pub const Title = @import("svg/Title.zig"); +pub const Desc = @import("svg/Desc.zig"); +pub const Metadata = @import("svg/Metadata.zig"); const String = lp.String; @@ -37,23 +41,22 @@ _tag_name: String, // Svg elements are case-preserving pub const Type = union(enum) { graphics: *Graphics, + view: *View, + title: *Title, + desc: *Desc, + metadata: *Metadata, generic: *Generic, }; pub fn is(self: *Svg, comptime T: type) ?*T { - switch (self._type) { - .graphics => |g| { - if (T == Graphics) { - return g; + inline for (@typeInfo(Type).@"union".fields) |field| { + if (@field(Type, field.name) == self._type) { + if (field.type == *T) { + return @field(self._type, field.name); } - return g.is(T); - }, - .generic => |g| { - if (T == Generic) { - return g; - } - }, + } } + if (self._type == .graphics) return self._type.graphics.is(T); return null; } @@ -64,7 +67,7 @@ pub fn getTag(self: *const Svg) Element.Tag { .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, + .a, .use, .image, .defs, .symbol, .switch_element, .foreign_object => .unknown, .geometry => |geo| switch (geo._type) { .rect => .rect, .circle => .circle, @@ -76,6 +79,8 @@ pub fn getTag(self: *const Svg) Element.Tag { }, }, .generic => |g| g._tag, + .title => .title, + .view, .desc, .metadata => .unknown, }; } diff --git a/src/browser/webapi/element/svg/Desc.zig b/src/browser/webapi/element/svg/Desc.zig new file mode 100644 index 000000000..25551c121 --- /dev/null +++ b/src/browser/webapi/element/svg/Desc.zig @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// SPDX-License-Identifier: AGPL-3.0-or-later + +const js = @import("../../../js/js.zig"); +const Node = @import("../../Node.zig"); +const Element = @import("../../Element.zig"); +const Svg = @import("../Svg.zig"); + +const Desc = @This(); +_proto: *Svg, + +pub fn asElement(self: *Desc) *Element { + return self._proto.asElement(); +} +pub fn asNode(self: *Desc) *Node { + return self.asElement().asNode(); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Desc); + pub const Meta = struct { + pub const name = "SVGDescElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; +}; diff --git a/src/browser/webapi/element/svg/ForeignObject.zig b/src/browser/webapi/element/svg/ForeignObject.zig new file mode 100644 index 000000000..d44a7547b --- /dev/null +++ b/src/browser/webapi/element/svg/ForeignObject.zig @@ -0,0 +1,63 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// SPDX-License-Identifier: AGPL-3.0-or-later + +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 AnimatedLength = @import("../../svg/AnimatedLength.zig"); +const PathData = @import("../../svg/PathData.zig"); +const Graphics = @import("Graphics.zig"); + +const ForeignObject = @This(); +_proto: *Graphics, + +pub fn asElement(self: *ForeignObject) *Element { + return self._proto.asElement(); +} +pub fn asNode(self: *ForeignObject) *Node { + return self.asElement().asNode(); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(ForeignObject); + pub const Meta = struct { + pub const name = "SVGForeignObjectElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const x = bridge.accessor(ForeignObject.getX, null, .{}); + pub const y = bridge.accessor(ForeignObject.getY, null, .{}); + pub const width = bridge.accessor(ForeignObject.getWidth, null, .{}); + pub const height = bridge.accessor(ForeignObject.getHeight, null, .{}); +}; + +pub fn getX(self: *ForeignObject, frame: *Frame) !*AnimatedLength { + return AnimatedLength.getOrCreate(self.asElement(), .x, frame); +} +pub fn getY(self: *ForeignObject, frame: *Frame) !*AnimatedLength { + return AnimatedLength.getOrCreate(self.asElement(), .y, frame); +} +pub fn getWidth(self: *ForeignObject, frame: *Frame) !*AnimatedLength { + return AnimatedLength.getOrCreate(self.asElement(), .width, frame); +} +pub fn getHeight(self: *ForeignObject, frame: *Frame) !*AnimatedLength { + return AnimatedLength.getOrCreate(self.asElement(), .height, frame); +} + +pub fn getBounds(self: *ForeignObject, frame: *Frame) !PathData.Bounds { + const x = (try self.getX(frame)).getBaseVal().getValue(frame); + const y = (try self.getY(frame)).getBaseVal().getValue(frame); + const width = (try self.getWidth(frame)).getBaseVal().getValue(frame); + const height = (try self.getHeight(frame)).getBaseVal().getValue(frame); + if (!std.math.isFinite(x) or !std.math.isFinite(y) or + !std.math.isFinite(width) or !std.math.isFinite(height)) + { + return .{}; + } + if (width <= 0 or height <= 0) return .{}; + return .{ .min_x = x, .min_y = y, .max_x = x + width, .max_y = y + height }; +} diff --git a/src/browser/webapi/element/svg/Graphics.zig b/src/browser/webapi/element/svg/Graphics.zig index c4dd4c08f..25a0ddfbe 100644 --- a/src/browser/webapi/element/svg/Graphics.zig +++ b/src/browser/webapi/element/svg/Graphics.zig @@ -35,6 +35,9 @@ 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 Symbol = @import("Symbol.zig"); +pub const Switch = @import("Switch.zig"); +pub const ForeignObject = @import("ForeignObject.zig"); pub const Geometry = @import("Geometry.zig"); const Graphics = @This(); @@ -48,6 +51,9 @@ pub const Type = union(enum) { use: *Use, image: *Image, defs: *Defs, + symbol: *Symbol, + switch_element: *Switch, + foreign_object: *ForeignObject, geometry: *Geometry, }; @@ -98,8 +104,9 @@ pub fn getBBox(self: *Graphics, frame: *Frame) !*DOMRect { defer path.deinit(frame.local_arena); bounds = path.bounds(.{}); }, - .g, .a, .svg => try accumulateChildren(self, .{}, &bounds, frame, 0), - .defs, .use, .image => {}, + .foreign_object => |foreign_object| bounds = try foreign_object.getBounds(frame), + .g, .a, .svg => try accumulateChildren(self, .{}, &bounds, frame), + .defs, .symbol, .switch_element, .use, .image => {}, } if (bounds.isEmpty()) { return DOMRect.create(.{}, frame._factory); @@ -113,20 +120,31 @@ pub fn getBBox(self: *Graphics, frame: *Frame) !*DOMRect { }, frame._factory); } -const MAX_NESTING_DEPTH = 512; +fn accumulateChildren(parent: *Graphics, matrix: PathData.Matrix, bounds: *PathData.Bounds, frame: *Frame) !void { + const Cursor = struct { + next: ?*Node, + matrix: PathData.Matrix, + }; + var cursors: std.ArrayList(Cursor) = .empty; + try cursors.append(frame.local_arena, .{ + .next = parent.asNode().firstChild(), + .matrix = matrix, + }); -fn accumulateChildren(parent: *Graphics, matrix: PathData.Matrix, bounds: *PathData.Bounds, frame: *Frame, depth: usize) !void { - if (depth == MAX_NESTING_DEPTH) { - return; - } + while (cursors.items.len != 0) { + const cursor = &cursors.items[cursors.items.len - 1]; + const node = cursor.next orelse { + _ = cursors.pop(); + continue; + }; + cursor.next = node.nextSibling(); + const parent_matrix = cursor.matrix; - var child = parent.asNode().firstChild(); - while (child) |node| : (child = node.nextSibling()) { const element = node.is(Element) orelse continue; if (element._namespace != .svg) continue; const svg = element.as(SvgElement); const graphics = svg.is(Graphics) orelse continue; - const child_matrix = matrix.multiply(transformMatrix(element)); + const child_matrix = parent_matrix.multiply(transformMatrix(element)); switch (graphics._type) { .geometry => |geometry| { @@ -134,11 +152,30 @@ fn accumulateChildren(parent: *Graphics, matrix: PathData.Matrix, bounds: *PathD defer path.deinit(frame.local_arena); bounds.merge(path.bounds(child_matrix)); }, - .g, .a => try accumulateChildren(graphics, child_matrix, bounds, frame, depth + 1), - // never renders. Nested viewports, and have - // geometry we cannot resolve yet, so they contribute nothing rather - // than making the whole box unavailable. - .defs, .svg, .use, .image => {}, + .foreign_object => |foreign_object| { + const child_bounds = try foreign_object.getBounds(frame); + if (!child_bounds.isEmpty()) { + var foreign_path: PathData.Path = .{}; + defer foreign_path.deinit(frame.local_arena); + const top_left = PathData.Point{ .x = child_bounds.min_x, .y = child_bounds.min_y }; + const top_right = PathData.Point{ .x = child_bounds.max_x, .y = child_bounds.min_y }; + const bottom_right = PathData.Point{ .x = child_bounds.max_x, .y = child_bounds.max_y }; + const bottom_left = PathData.Point{ .x = child_bounds.min_x, .y = child_bounds.max_y }; + try foreign_path.appendLine(top_left, top_right, frame.local_arena); + try foreign_path.appendLine(top_right, bottom_right, frame.local_arena); + try foreign_path.appendLine(bottom_right, bottom_left, frame.local_arena); + try foreign_path.appendLine(bottom_left, top_left, frame.local_arena); + bounds.merge(foreign_path.bounds(child_matrix)); + } + }, + .g, .a => try cursors.append(frame.local_arena, .{ + .next = graphics.asNode().firstChild(), + .matrix = child_matrix, + }), + // and never render. Nested viewports, , + // and have geometry we cannot resolve yet, so they + // contribute nothing rather than making the whole box unavailable. + .defs, .symbol, .svg, .switch_element, .use, .image => {}, } } } diff --git a/src/browser/webapi/element/svg/Metadata.zig b/src/browser/webapi/element/svg/Metadata.zig new file mode 100644 index 000000000..0429e38da --- /dev/null +++ b/src/browser/webapi/element/svg/Metadata.zig @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// SPDX-License-Identifier: AGPL-3.0-or-later + +const js = @import("../../../js/js.zig"); +const Node = @import("../../Node.zig"); +const Element = @import("../../Element.zig"); +const Svg = @import("../Svg.zig"); + +const Metadata = @This(); +_proto: *Svg, + +pub fn asElement(self: *Metadata) *Element { + return self._proto.asElement(); +} +pub fn asNode(self: *Metadata) *Node { + return self.asElement().asNode(); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Metadata); + pub const Meta = struct { + pub const name = "SVGMetadataElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; +}; diff --git a/src/browser/webapi/element/svg/Switch.zig b/src/browser/webapi/element/svg/Switch.zig new file mode 100644 index 000000000..1322edf7d --- /dev/null +++ b/src/browser/webapi/element/svg/Switch.zig @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// SPDX-License-Identifier: AGPL-3.0-or-later + +const js = @import("../../../js/js.zig"); +const Node = @import("../../Node.zig"); +const Element = @import("../../Element.zig"); +const Graphics = @import("Graphics.zig"); + +const Switch = @This(); +_proto: *Graphics, + +pub fn asElement(self: *Switch) *Element { + return self._proto.asElement(); +} +pub fn asNode(self: *Switch) *Node { + return self.asElement().asNode(); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Switch); + pub const Meta = struct { + pub const name = "SVGSwitchElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; +}; diff --git a/src/browser/webapi/element/svg/Symbol.zig b/src/browser/webapi/element/svg/Symbol.zig new file mode 100644 index 000000000..45a51e886 --- /dev/null +++ b/src/browser/webapi/element/svg/Symbol.zig @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// SPDX-License-Identifier: AGPL-3.0-or-later + +const js = @import("../../../js/js.zig"); +const Node = @import("../../Node.zig"); +const Element = @import("../../Element.zig"); +const Graphics = @import("Graphics.zig"); + +const Symbol = @This(); +_proto: *Graphics, + +pub fn asElement(self: *Symbol) *Element { + return self._proto.asElement(); +} +pub fn asNode(self: *Symbol) *Node { + return self.asElement().asNode(); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Symbol); + pub const Meta = struct { + pub const name = "SVGSymbolElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; +}; diff --git a/src/browser/webapi/element/svg/Title.zig b/src/browser/webapi/element/svg/Title.zig new file mode 100644 index 000000000..74455107a --- /dev/null +++ b/src/browser/webapi/element/svg/Title.zig @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// SPDX-License-Identifier: AGPL-3.0-or-later + +const js = @import("../../../js/js.zig"); +const Node = @import("../../Node.zig"); +const Element = @import("../../Element.zig"); +const Svg = @import("../Svg.zig"); + +const Title = @This(); +_proto: *Svg, + +pub fn asElement(self: *Title) *Element { + return self._proto.asElement(); +} +pub fn asNode(self: *Title) *Node { + return self.asElement().asNode(); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Title); + pub const Meta = struct { + pub const name = "SVGTitleElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; +}; diff --git a/src/browser/webapi/element/svg/View.zig b/src/browser/webapi/element/svg/View.zig new file mode 100644 index 000000000..3b14fe3fa --- /dev/null +++ b/src/browser/webapi/element/svg/View.zig @@ -0,0 +1,26 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// SPDX-License-Identifier: AGPL-3.0-or-later + +const js = @import("../../../js/js.zig"); +const Node = @import("../../Node.zig"); +const Element = @import("../../Element.zig"); +const Svg = @import("../Svg.zig"); + +const View = @This(); +_proto: *Svg, + +pub fn asElement(self: *View) *Element { + return self._proto.asElement(); +} +pub fn asNode(self: *View) *Node { + return self.asElement().asNode(); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(View); + pub const Meta = struct { + pub const name = "SVGViewElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; +}; From 00a0517df3009457d9d003128042b90d1f3227e0 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 27 Jul 2026 18:36:51 +0800 Subject: [PATCH 2/2] Add correct license header Format / consistency with rest of the codebase --- src/browser/frame/node_factory.zig | 5 +- src/browser/webapi/element/Svg.zig | 4 +- src/browser/webapi/element/svg/Desc.zig | 19 +++++- .../webapi/element/svg/ForeignObject.zig | 60 ++++++++++++------- src/browser/webapi/element/svg/Graphics.zig | 8 +-- src/browser/webapi/element/svg/Metadata.zig | 21 ++++++- src/browser/webapi/element/svg/Switch.zig | 21 ++++++- src/browser/webapi/element/svg/Symbol.zig | 21 ++++++- src/browser/webapi/element/svg/Title.zig | 21 ++++++- src/browser/webapi/element/svg/View.zig | 21 ++++++- 10 files changed, 161 insertions(+), 40 deletions(-) diff --git a/src/browser/frame/node_factory.zig b/src/browser/frame/node_factory.zig index ddd0aa083..024b6614e 100644 --- a/src/browser/frame/node_factory.zig +++ b/src/browser/frame/node_factory.zig @@ -929,8 +929,9 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons asUint("polyline") => return createSvgElementT(frame, Geometry.Polyline, name, attribute_iterator, .{ ._proto = undefined }), else => {}, }, - 13 => if (std.mem.eql(u8, name, "foreignObject")) { - return createSvgElementT(frame, Graphics.ForeignObject, name, attribute_iterator, .{ ._proto = undefined }); + 13 => switch (@as(u104, @bitCast(name[0..13].*))) { + asUint("foreignObject") => return createSvgElementT(frame, Graphics.ForeignObject, name, attribute_iterator, .{ ._proto = undefined }), + else => {}, }, else => {}, } diff --git a/src/browser/webapi/element/Svg.zig b/src/browser/webapi/element/Svg.zig index 86e0eeb3b..442d60402 100644 --- a/src/browser/webapi/element/Svg.zig +++ b/src/browser/webapi/element/Svg.zig @@ -56,7 +56,9 @@ pub fn is(self: *Svg, comptime T: type) ?*T { } } } - if (self._type == .graphics) return self._type.graphics.is(T); + if (self._type == .graphics) { + return self._type.graphics.is(T); + } return null; } diff --git a/src/browser/webapi/element/svg/Desc.zig b/src/browser/webapi/element/svg/Desc.zig index 25551c121..0ce39857c 100644 --- a/src/browser/webapi/element/svg/Desc.zig +++ b/src/browser/webapi/element/svg/Desc.zig @@ -1,5 +1,20 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) -// SPDX-License-Identifier: AGPL-3.0-or-later +// 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"); diff --git a/src/browser/webapi/element/svg/ForeignObject.zig b/src/browser/webapi/element/svg/ForeignObject.zig index d44a7547b..aa1d6db2e 100644 --- a/src/browser/webapi/element/svg/ForeignObject.zig +++ b/src/browser/webapi/element/svg/ForeignObject.zig @@ -1,5 +1,20 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) -// SPDX-License-Identifier: AGPL-3.0-or-later +// 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"); @@ -7,8 +22,10 @@ const js = @import("../../../js/js.zig"); const Frame = @import("../../../Frame.zig"); const Node = @import("../../Node.zig"); const Element = @import("../../Element.zig"); -const AnimatedLength = @import("../../svg/AnimatedLength.zig"); + const PathData = @import("../../svg/PathData.zig"); +const AnimatedLength = @import("../../svg/AnimatedLength.zig"); + const Graphics = @import("Graphics.zig"); const ForeignObject = @This(); @@ -21,20 +38,6 @@ pub fn asNode(self: *ForeignObject) *Node { return self.asElement().asNode(); } -pub const JsApi = struct { - pub const bridge = js.Bridge(ForeignObject); - pub const Meta = struct { - pub const name = "SVGForeignObjectElement"; - pub const prototype_chain = bridge.prototypeChain(); - pub var class_id: bridge.ClassId = undefined; - }; - - pub const x = bridge.accessor(ForeignObject.getX, null, .{}); - pub const y = bridge.accessor(ForeignObject.getY, null, .{}); - pub const width = bridge.accessor(ForeignObject.getWidth, null, .{}); - pub const height = bridge.accessor(ForeignObject.getHeight, null, .{}); -}; - pub fn getX(self: *ForeignObject, frame: *Frame) !*AnimatedLength { return AnimatedLength.getOrCreate(self.asElement(), .x, frame); } @@ -53,11 +56,26 @@ pub fn getBounds(self: *ForeignObject, frame: *Frame) !PathData.Bounds { const y = (try self.getY(frame)).getBaseVal().getValue(frame); const width = (try self.getWidth(frame)).getBaseVal().getValue(frame); const height = (try self.getHeight(frame)).getBaseVal().getValue(frame); - if (!std.math.isFinite(x) or !std.math.isFinite(y) or - !std.math.isFinite(width) or !std.math.isFinite(height)) - { + + if (!std.math.isFinite(x) or !std.math.isFinite(y) or !std.math.isFinite(width) or !std.math.isFinite(height)) { + return .{}; + } + if (width <= 0 or height <= 0) { return .{}; } - if (width <= 0 or height <= 0) return .{}; return .{ .min_x = x, .min_y = y, .max_x = x + width, .max_y = y + height }; } + +pub const JsApi = struct { + pub const bridge = js.Bridge(ForeignObject); + pub const Meta = struct { + pub const name = "SVGForeignObjectElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const x = bridge.accessor(ForeignObject.getX, null, .{}); + pub const y = bridge.accessor(ForeignObject.getY, null, .{}); + pub const width = bridge.accessor(ForeignObject.getWidth, null, .{}); + pub const height = bridge.accessor(ForeignObject.getHeight, null, .{}); +}; diff --git a/src/browser/webapi/element/svg/Graphics.zig b/src/browser/webapi/element/svg/Graphics.zig index 25a0ddfbe..d9b4dc269 100644 --- a/src/browser/webapi/element/svg/Graphics.zig +++ b/src/browser/webapi/element/svg/Graphics.zig @@ -132,13 +132,13 @@ fn accumulateChildren(parent: *Graphics, matrix: PathData.Matrix, bounds: *PathD }); while (cursors.items.len != 0) { - const cursor = &cursors.items[cursors.items.len - 1]; - const node = cursor.next orelse { + const top = cursors.items.len - 1; + const node = cursors.items[top].next orelse { _ = cursors.pop(); continue; }; - cursor.next = node.nextSibling(); - const parent_matrix = cursor.matrix; + cursors.items[top].next = node.nextSibling(); + const parent_matrix = cursors.items[top].matrix; const element = node.is(Element) orelse continue; if (element._namespace != .svg) continue; diff --git a/src/browser/webapi/element/svg/Metadata.zig b/src/browser/webapi/element/svg/Metadata.zig index 0429e38da..ec1085c84 100644 --- a/src/browser/webapi/element/svg/Metadata.zig +++ b/src/browser/webapi/element/svg/Metadata.zig @@ -1,9 +1,26 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) -// SPDX-License-Identifier: AGPL-3.0-or-later +// 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 Svg = @import("../Svg.zig"); const Metadata = @This(); diff --git a/src/browser/webapi/element/svg/Switch.zig b/src/browser/webapi/element/svg/Switch.zig index 1322edf7d..4e3059eaa 100644 --- a/src/browser/webapi/element/svg/Switch.zig +++ b/src/browser/webapi/element/svg/Switch.zig @@ -1,9 +1,26 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) -// SPDX-License-Identifier: AGPL-3.0-or-later +// 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 Switch = @This(); diff --git a/src/browser/webapi/element/svg/Symbol.zig b/src/browser/webapi/element/svg/Symbol.zig index 45a51e886..0737e54ca 100644 --- a/src/browser/webapi/element/svg/Symbol.zig +++ b/src/browser/webapi/element/svg/Symbol.zig @@ -1,9 +1,26 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) -// SPDX-License-Identifier: AGPL-3.0-or-later +// 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 Symbol = @This(); diff --git a/src/browser/webapi/element/svg/Title.zig b/src/browser/webapi/element/svg/Title.zig index 74455107a..6f9f0daa9 100644 --- a/src/browser/webapi/element/svg/Title.zig +++ b/src/browser/webapi/element/svg/Title.zig @@ -1,9 +1,26 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) -// SPDX-License-Identifier: AGPL-3.0-or-later +// 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 Svg = @import("../Svg.zig"); const Title = @This(); diff --git a/src/browser/webapi/element/svg/View.zig b/src/browser/webapi/element/svg/View.zig index 3b14fe3fa..204577613 100644 --- a/src/browser/webapi/element/svg/View.zig +++ b/src/browser/webapi/element/svg/View.zig @@ -1,9 +1,26 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) -// SPDX-License-Identifier: AGPL-3.0-or-later +// 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 Svg = @import("../Svg.zig"); const View = @This();