Merge pull request #3031 from mh0pe/codex/svg-05-structure

webapi: add SVG structural elements
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-07-27 18:56:06 +08:00
commit 302fc79a59
13 files changed
+540 -27

No files matched your search

+10
View File
@@ -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,14 @@ 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 => switch (@as(u104, @bitCast(name[0..13].*))) {
asUint("foreignObject") => return createSvgElementT(frame, Graphics.ForeignObject, name, attribute_iterator, .{ ._proto = undefined }),
else => {},
},
else => {},
}
+7
View File
@@ -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"),
@@ -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);
}
</script>
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<svg id=root width="200" height="100">
<title id=title>Accessible title</title>
<desc id=desc>Long description</desc>
<metadata id=metadata><entry>value</entry></metadata>
<view id=view></view>
<symbol id=symbol><rect width="5" height="5"></rect></symbol>
<switch id=switch requiredExtensions="urn:svg"><circle r="2"></circle></switch>
<g id=container>
<foreignObject id=foreign x="10" y="20" width="30" height="40" transform="translate(5 6)">
<div>HTML content</div>
</foreignObject>
</g>
</svg>
<script id=structuralIdentities>
{
testing.expectEqual(true, $('#symbol') instanceof SVGSymbolElement);
testing.expectEqual(true, $('#symbol') instanceof SVGGraphicsElement);
testing.expectEqual(true, $('#switch') instanceof SVGSwitchElement);
testing.expectEqual(true, $('#switch') instanceof SVGGraphicsElement);
testing.expectEqual(true, $('#foreign') instanceof SVGForeignObjectElement);
testing.expectEqual(true, $('#foreign') instanceof SVGGraphicsElement);
testing.expectEqual(true, $('#view') instanceof SVGViewElement);
testing.expectEqual(true, $('#view') instanceof SVGElement);
testing.expectEqual(false, $('#view') instanceof SVGGraphicsElement);
testing.expectEqual(true, $('#title') instanceof SVGTitleElement);
testing.expectEqual(true, $('#desc') instanceof SVGDescElement);
testing.expectEqual(true, $('#metadata') instanceof SVGMetadataElement);
testing.expectEqual($('#title'), document.querySelector('svg title'));
testing.expectEqual('Accessible title', $('#title').textContent);
testing.expectEqual('Long description', $('#desc').textContent);
testing.expectEqual(true, document.createElementNS('http://www.w3.org/2000/svg', 'symbol') instanceof SVGSymbolElement);
testing.expectEqual(false, document.createElementNS('http://www.w3.org/2000/svg', 'Symbol') instanceof SVGSymbolElement);
}
</script>
<script id=foreignObjectGeometry>
{
const foreign = $('#foreign');
testing.expectEqual(true, foreign.x instanceof SVGAnimatedLength);
testing.expectEqual(true, foreign.x === foreign.x);
testing.expectEqual(10, foreign.x.baseVal.value);
testing.expectEqual(40, foreign.height.baseVal.value);
foreign.width.baseVal.value = 35;
testing.expectEqual('35', foreign.getAttribute('width'));
testing.expectEqual(35, foreign.width.animVal.value);
testing.expectError('NoModificationAllowedError', () => { foreign.width.animVal.value = 1; });
let box = foreign.getBBox();
testing.expectEqual(10, box.x);
testing.expectEqual(20, box.y);
testing.expectEqual(35, box.width);
testing.expectEqual(40, box.height);
// The element's own transform is excluded from its box but included in its
// parent's box.
box = $('#container').getBBox();
testing.expectEqual(15, box.x);
testing.expectEqual(26, box.y);
testing.expectEqual(35, box.width);
testing.expectEqual(40, box.height);
foreign.setAttribute('width', '-1');
testing.expectEqual(0, foreign.getBBox().width);
}
</script>
<script id=structuralBoundaries>
{
// Like <defs>, <symbol> and <switch> contribute no resolvable geometry, so
// they answer with an empty box rather than an error.
testing.expectEqual(0, $('#symbol').getBBox().width);
testing.expectEqual(0, $('#switch').getBBox().width);
testing.expectEqual(true, $('#switch').requiredExtensions instanceof SVGStringList);
testing.expectEqual('urn:svg', $('#switch').requiredExtensions.getItem(0));
}
</script>
<script id=deepGroupBounds>
{
const namespace = 'http://www.w3.org/2000/svg';
const outer = document.createElementNS(namespace, 'g');
let parent = outer;
for (let i = 0; i < 512; i++) {
const group = document.createElementNS(namespace, 'g');
parent.appendChild(group);
parent = group;
}
const rect = document.createElementNS(namespace, 'rect');
rect.setAttribute('x', '3');
rect.setAttribute('y', '4');
rect.setAttribute('width', '5');
rect.setAttribute('height', '6');
parent.appendChild(rect);
$('#root').appendChild(outer);
const box = outer.getBBox();
testing.expectEqual(3, box.x);
testing.expectEqual(4, box.y);
testing.expectEqual(5, box.width);
testing.expectEqual(6, box.height);
}
</script>
+19 -12
View File
@@ -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,22 +41,23 @@ _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 +69,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 +81,8 @@ pub fn getTag(self: *const Svg) Element.Tag {
},
},
.generic => |g| g._tag,
.title => .title,
.view, .desc, .metadata => .unknown,
};
}
+41
View File
@@ -0,0 +1,41 @@
// 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 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;
};
};
@@ -0,0 +1,81 @@
// 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 PathData = @import("../../svg/PathData.zig");
const AnimatedLength = @import("../../svg/AnimatedLength.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 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 };
}
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, .{});
};
+52 -15
View File
@@ -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 top = cursors.items.len - 1;
const node = cursors.items[top].next orelse {
_ = cursors.pop();
continue;
};
cursors.items[top].next = node.nextSibling();
const parent_matrix = cursors.items[top].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),
// <defs> never renders. Nested viewports, <use> and <image> 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,
}),
// <defs> and <symbol> never render. Nested viewports, <switch>,
// <use> and <image> have geometry we cannot resolve yet, so they
// contribute nothing rather than making the whole box unavailable.
.defs, .symbol, .svg, .switch_element, .use, .image => {},
}
}
}
@@ -0,0 +1,43 @@
// 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 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;
};
};
+43
View File
@@ -0,0 +1,43 @@
// 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 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;
};
};
+43
View File
@@ -0,0 +1,43 @@
// 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 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;
};
};
+43
View File
@@ -0,0 +1,43 @@
// 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 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;
};
};
+43
View File
@@ -0,0 +1,43 @@
// 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 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;
};
};