mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-18 09:54:36 -04:00
Merge pull request #3031 from mh0pe/codex/svg-05-structure
webapi: add SVG structural elements
This commit is contained in:
13 files changed
+540
-27
No files matched your search
@@ -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 => {},
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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, .{});
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user