tiny.zen.diagram.ego.svg
Defined in diagram.ego.
API (6)
Actions
Public operations.
render: Renders one call map into caller-owned output storage.validateDocumentFragment: Validates the documentation fragment envelope around one rendered call map.write: Writes one bounded, linked, static SVG call map.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: lib/zen/src/diagram/ego/root.zig:3
zig
pub const svg = @import("svg.zig");Source: lib/zen/src/diagram/ego/svg.zig
zig
const std = @import("std");const root = @import("root.zig");const zen = @import("../../root.zig");const html = zen.html;const layout = root.layout;const model = root.model;pub const output_bytes_max: usize = 96 * 1024;pub const document_fragment_bytes_max: usize = output_bytes_max + 1024;const escaped_bytes_max: usize = model.href_bytes_max * 6;pub const FragmentError = error{ InvalidDocumentFragment, UnsafeDocumentFragment,};const stylesheet = \\.zen-ego{color:var(--ink,#1a1714);overflow:visible} \\.zen-ego text{ \\font-family:var(--face-mono,ui-monospace,monospace); \\pointer-events:none \\} \\.zen-ego-edge{ \\fill:none; \\stroke:var(--rule-bold,rgba(26,23,20,.3)); \\stroke-width:1.5; \\vector-effect:non-scaling-stroke \\} \\.zen-ego-edge-more{stroke-dasharray:4 4} \\.zen-ego-arrow{fill:var(--ink-mute,#8c8275)} \\.zen-ego-heading{ \\fill:var(--ink-soft,#564e44); \\font-weight:700; \\letter-spacing:.08em; \\text-transform:uppercase \\} \\.zen-ego-empty{fill:var(--ink-mute,#8c8275);font-style:italic} \\.zen-ego-node>rect{ \\fill:var(--paper,#f4f1ea); \\stroke:var(--rule-bold,rgba(26,23,20,.3)); \\stroke-width:1.25; \\vector-effect:non-scaling-stroke \\} \\.zen-ego-node .zen-ego-label{fill:var(--ink,#1a1714);font-weight:700} \\.zen-ego-node .zen-ego-context{fill:var(--ink-soft,#564e44)} \\.zen-ego .zen-ego-node{text-decoration:none} \\.zen-ego-subject>rect{ \\fill:var(--paper-sunk,#ece8dd); \\stroke:var(--accent-ink,#d2371f); \\stroke-width:2.25 \\} \\.zen-ego-subject .zen-ego-label{font-weight:900} \\.zen-ego-more>rect{fill:transparent;stroke-dasharray:4 3} \\.zen-ego-badge rect{ \\fill:var(--paper-raised,var(--paper,#f4f1ea)); \\stroke:var(--rule-bold,rgba(26,23,20,.3)); \\stroke-width:1 \\} \\.zen-ego-badge text{ \\fill:var(--ink-soft,#564e44); \\font-weight:800; \\letter-spacing:.04em \\} \\.zen-ego-self-loop{stroke:var(--accent-ink,#d2371f);stroke-width:2} \\a.zen-ego-node:is(:hover,:focus-visible)>rect{ \\stroke:var(--accent-ink,#d2371f); \\stroke-width:2.5 \\} \\a.zen-ego-node:is(:hover,:focus-visible) .zen-ego-label{ \\fill:var(--accent-ink,#d2371f); \\text-decoration:underline \\} \\a.zen-ego-node:focus-visible{outline:none} \\a.zen-ego-node:focus-visible .zen-ego-badge rect{ \\stroke:var(--accent-ink,#d2371f) \\} \\.zen-ego-node-plain>rect{stroke-dasharray:3 3};/// Renders one call map into caller-owned output storage.pub fn render( output: []u8, network: model.Network, surface: model.Surface,) (model.Error || std.Io.Writer.Error)![]const u8 { var writer = std.Io.Writer.fixed(output); try write(&writer, network, surface); return writer.buffered();}/// Writes one bounded, linked, static SVG call map.pub fn write( writer: *std.Io.Writer, network: model.Network, surface: model.Surface,) (model.Error || std.Io.Writer.Error)!void { const plan = try layout.Plan.inspect(network, surface); const leaf = network.callers.total == 0 and network.callees.total == 0; try writeOpen(writer, network, plan.geometry, leaf); if (!(leaf and plan.geometry.arrangement == .horizontal)) { try writeHeaders(writer, &plan); } try writeConnections( writer, &plan.callers, plan.subject, plan.geometry, .callers, ); try writeConnections( writer, &plan.callees, plan.subject, plan.geometry, .callees, ); try writeEmptyStates(writer, &plan); try writeBand(writer, &plan.callers, plan.geometry, .callers); try writeBand(writer, &plan.callees, plan.geometry, .callees); if (network.recursive) try writeSelfLoop(writer, plan.subject); try writeNode(writer, network.subject, plan.subject, plan.geometry, true); try writer.writeAll("</svg>");}/// Validates the documentation fragment envelope around one rendered call map.pub fn validateDocumentFragment(source: []const u8) FragmentError!void { const fragment = std.mem.trim(u8, source, " \t\r\n"); if (fragment.len == 0 or fragment.len > document_fragment_bytes_max) { return error.InvalidDocumentFragment; } const wide = "<figure class=\"docs-call-map docs-call-map-primary zen-role-wide"; const anchored = "<figure class=\"docs-call-map docs-call-map-anchored zen-role-wide"; if (!std.mem.startsWith(u8, fragment, wide) and !std.mem.startsWith(u8, fragment, anchored)) { return error.InvalidDocumentFragment; } if (!std.mem.endsWith(u8, fragment, "</figcaption></figure>")) { return error.InvalidDocumentFragment; } if (!std.unicode.utf8ValidateSlice(fragment) or containsAsciiInsensitive(fragment, "<script") or containsAsciiInsensitive(fragment, "<foreignobject") or containsEventAttribute(fragment) or containsAsciiInsensitive(fragment, " style=") or containsAsciiInsensitive(fragment, "xlink:href") or containsAsciiInsensitive(fragment, "javascript:") or containsAsciiInsensitive(fragment, "data:") or containsAsciiInsensitive(fragment, "url(") or containsAsciiInsensitive(fragment, "@import")) { return error.UnsafeDocumentFragment; } try validateFragmentTags(fragment); try validateFragmentLinks(fragment);}fn validateFragmentTags(fragment: []const u8) FragmentError!void { const prefixes = [_][]const u8{ "<figure class=\"docs-call-map docs-call-map-primary zen-role-wide", "<figure class=\"docs-call-map docs-call-map-anchored zen-role-wide", "</figure>", "<svg ", "</svg>", "<style>", "</style>", "<path ", "<text ", "</text>", "<a ", "</a>", "<rect ", "<g ", "</g>", "<figcaption>", "</figcaption>", }; var cursor: usize = 0; while (std.mem.indexOfScalarPos(u8, fragment, cursor, '<')) |start| { const end = std.mem.indexOfScalarPos(u8, fragment, start, '>') orelse return error.InvalidDocumentFragment; var admitted = false; for (prefixes) |prefix| { admitted = admitted or std.mem.startsWith(u8, fragment[start..], prefix); } if (!admitted) return error.UnsafeDocumentFragment; cursor = end + 1; } if (std.mem.count(u8, fragment, "<svg ") != 1 or std.mem.count(u8, fragment, "</svg>") != 1) { return error.InvalidDocumentFragment; }}fn validateFragmentLinks(fragment: []const u8) FragmentError!void { const marker = " href=\""; var cursor: usize = 0; while (std.mem.indexOfPos(u8, fragment, cursor, marker)) |start| { const value_start = start + marker.len; if (value_start == fragment.len) return error.InvalidDocumentFragment; const end = std.mem.indexOfScalarPos(u8, fragment, value_start, '"') orelse return error.InvalidDocumentFragment; if (end == value_start) return error.InvalidDocumentFragment; if (!model.safeHref(fragment[value_start..end])) { return error.UnsafeDocumentFragment; } cursor = end + 1; }}fn containsAsciiInsensitive(haystack: []const u8, needle: []const u8) bool { if (needle.len == 0 or needle.len > haystack.len) return false; return std.ascii.findIgnoreCase(haystack, needle) != null;}fn containsEventAttribute(fragment: []const u8) bool { var tag_start: usize = 0; while (std.mem.indexOfScalarPos(u8, fragment, tag_start, '<')) |start| { const end = std.mem.indexOfScalarPos(u8, fragment, start, '>') orelse return true; var cursor = start + 1; while (cursor + 3 < end) : (cursor += 1) { if (!std.ascii.isWhitespace(fragment[cursor])) continue; const name_start = cursor + 1; if (std.ascii.toLower(fragment[name_start]) != 'o' or std.ascii.toLower(fragment[name_start + 1]) != 'n') continue; var name_end = name_start + 2; while (name_end < end and (std.ascii.isAlphanumeric(fragment[name_end]) or fragment[name_end] == '-' or fragment[name_end] == '_')) { name_end += 1; } while (name_end < end and std.ascii.isWhitespace(fragment[name_end])) { name_end += 1; } if (name_end < end and fragment[name_end] == '=') return true; } tag_start = end + 1; } return false;}const Direction = enum { callers, callees,};fn writeOpen( writer: *std.Io.Writer, network: model.Network, geometry: model.Geometry, leaf: bool,) std.Io.Writer.Error!void { try writer.print( "<svg xmlns=\"http://www.w3.org/2000/svg\" " ++ "class=\"zen-ego{s}\" viewBox=\"0 0 {d} {d}\" " ++ "role=\"group\" aria-label=\"Static call map for ", .{ if (leaf) " zen-ego-leaf" else "", geometry.width, geometry.height, }, ); try writeAccessibleName(writer, network.subject); try writer.writeAll(if (leaf and geometry.arrangement == .horizontal) ". It has no direct callers and no direct calls." else switch (geometry.arrangement) { .horizontal => ". Callers flow in from the left. Calls flow out to the right.", .vertical => ". Callers flow in from above. Calls flow out below.", }); if (network.recursive) try writer.writeAll(" It calls itself."); try writer.writeAll("\"><style>"); try writer.writeAll(stylesheet); try writer.writeAll("</style>");}fn writeHeaders( writer: *std.Io.Writer, plan: *const layout.Plan,) std.Io.Writer.Error!void { try writeHeader( writer, plan.callers.heading.x, plan.callers.heading.y, plan.geometry.heading_size, "Called by", ); try writeHeader( writer, plan.callees.heading.x, plan.callees.heading.y, plan.geometry.heading_size, "Calls", );}fn writeHeader( writer: *std.Io.Writer, x: f32, y: f32, size: f32, value: []const u8,) std.Io.Writer.Error!void { try writer.print( "<text class=\"zen-ego-heading\" x=\"{d:.2}\" y=\"{d:.2}\" " ++ "font-size=\"{d:.2}\" text-anchor=\"middle\">{s}</text>", .{ x, y, size, value }, );}fn writeConnections( writer: *std.Io.Writer, band: *const layout.Band, subject: layout.Rect, geometry: model.Geometry, direction: Direction,) std.Io.Writer.Error!void { std.debug.assert(band.count <= model.max_slots_per_band); for (band.slice()) |item| { const dashed = switch (item.content) { .node => false, .more => true, }; switch (geometry.arrangement) { .horizontal => try writeHorizontalConnection( writer, item.box, subject, direction, dashed, ), .vertical => try writeVerticalConnection( writer, item.box, direction, dashed, ), } }}fn writeHorizontalConnection( writer: *std.Io.Writer, item: layout.Rect, subject: layout.Rect, direction: Direction, dashed: bool,) std.Io.Writer.Error!void { const start_x = if (direction == .callers) item.x + item.width + 4 else subject.x + subject.width + 4; const end_x = if (direction == .callers) subject.x - 4 else item.x - 4; const start_y = if (direction == .callers) item.centerY() else subject.centerY(); const end_y = if (direction == .callers) subject.centerY() else item.centerY(); try writeHorizontalEdge(writer, start_x, start_y, end_x, end_y, dashed);}fn writeHorizontalEdge( writer: *std.Io.Writer, start_x: f32, start_y: f32, end_x: f32, end_y: f32, dashed: bool,) std.Io.Writer.Error!void { const middle = (start_x + end_x) / 2; try writer.print( "<path class=\"zen-ego-edge{s}\" d=\"M {d:.2} {d:.2} " ++ "C {d:.2} {d:.2}, {d:.2} {d:.2}, {d:.2} {d:.2}\"/>", .{ if (dashed) " zen-ego-edge-more" else "", start_x, start_y, middle, start_y, middle, end_y, end_x, end_y, }, ); try writer.print( "<path class=\"zen-ego-arrow\" d=\"M {d:.2} {d:.2} " ++ "L {d:.2} {d:.2} L {d:.2} {d:.2} Z\"/>", .{ end_x, end_y, end_x - 7, end_y - 4, end_x - 7, end_y + 4 }, );}fn writeVerticalConnection( writer: *std.Io.Writer, item: layout.Rect, direction: Direction, dashed: bool,) std.Io.Writer.Error!void { const start_y = if (direction == .callers) item.y + item.height + 1 else item.y - 10; const end_y = if (direction == .callers) start_y + 8 else item.y - 2; try writeVerticalStub(writer, item.centerX(), start_y, end_y, dashed);}fn writeVerticalStub( writer: *std.Io.Writer, x: f32, start_y: f32, end_y: f32, dashed: bool,) std.Io.Writer.Error!void { std.debug.assert(start_y < end_y); try writer.print( "<path class=\"zen-ego-edge{s}\" d=\"M {d:.2} {d:.2} L {d:.2} {d:.2}\"/>", .{ if (dashed) " zen-ego-edge-more" else "", x, start_y, x, end_y, }, ); try writer.print( "<path class=\"zen-ego-arrow\" d=\"M {d:.2} {d:.2} " ++ "L {d:.2} {d:.2} L {d:.2} {d:.2} Z\"/>", .{ x, end_y, x - 4, end_y - 7, x + 4, end_y - 7 }, );}fn writeSelfLoop( writer: *std.Io.Writer, subject: layout.Rect,) std.Io.Writer.Error!void { const start_x = subject.centerX() - 18; const end_x = subject.centerX() + 18; const end_y = subject.y - 3; const top_y = end_y - 22; try writer.print( "<path class=\"zen-ego-edge zen-ego-self-loop\" aria-hidden=\"true\" " ++ "d=\"M {d:.2} {d:.2} C {d:.2} {d:.2}, {d:.2} {d:.2}, " ++ "{d:.2} {d:.2}\"/>", .{ start_x, end_y, start_x, top_y, end_x, top_y, end_x, end_y }, ); try writer.print( "<path class=\"zen-ego-arrow\" aria-hidden=\"true\" " ++ "d=\"M {d:.2} {d:.2} L {d:.2} {d:.2} L {d:.2} {d:.2} Z\"/>", .{ end_x, end_y, end_x - 4, end_y - 7, end_x + 4, end_y - 7 }, );}fn writeEmptyStates( writer: *std.Io.Writer, plan: *const layout.Plan,) std.Io.Writer.Error!void { const geometry = plan.geometry; if (plan.callers.count == 0) { try writeEmpty( writer, plan.callers.empty.x, plan.callers.empty.y, geometry.empty_size, "No direct callers", ); } if (plan.callees.count == 0) { try writeEmpty( writer, plan.callees.empty.x, plan.callees.empty.y, geometry.empty_size, "No direct calls", ); }}fn writeEmpty( writer: *std.Io.Writer, x: f32, y: f32, size: f32, value: []const u8,) std.Io.Writer.Error!void { try writer.print( "<text class=\"zen-ego-empty\" x=\"{d:.2}\" y=\"{d:.2}\" " ++ "font-size=\"{d:.2}\" text-anchor=\"middle\" " ++ "dominant-baseline=\"middle\">{s}</text>", .{ x, y, size, value }, );}fn writeBand( writer: *std.Io.Writer, band: *const layout.Band, geometry: model.Geometry, direction: Direction,) std.Io.Writer.Error!void { std.debug.assert(band.count <= model.max_slots_per_band); for (band.slice()) |item| switch (item.content) { .node => |node| try writeNode(writer, node, item.box, geometry, false), .more => |more| try writeMore(writer, more, item.box, geometry, direction), };}fn writeNode( writer: *std.Io.Writer, node: model.Node, box: layout.Rect, geometry: model.Geometry, subject: bool,) std.Io.Writer.Error!void { const linked = node.href.len != 0; try writer.writeAll(if (linked) "<a class=\"zen-ego-node" else "<g class=\"zen-ego-node zen-ego-node-plain"); if (subject) try writer.writeAll(" zen-ego-subject"); if (linked) { try writer.writeAll("\" href=\""); try writeEscaped(writer, node.href); try writer.writeAll("\" tabindex=\"0"); } else { try writer.writeAll("\" role=\"img"); } try writer.writeAll("\" aria-label=\""); try writeAccessibleName(writer, node); try writer.writeAll("\">"); try writeBox(writer, box); const label_size = if (subject) geometry.subject_label_size else geometry.label_size; if (node.badge.len > 0) try writeBadge(writer, node.badge, box, geometry.badge_size); try writeNodeText(writer, node, box, label_size, geometry.context_size); try writer.writeAll(if (linked) "</a>" else "</g>");}fn writeMore( writer: *std.Io.Writer, more: layout.More, box: layout.Rect, geometry: model.Geometry, direction: Direction,) std.Io.Writer.Error!void { try writer.writeAll( "<a class=\"zen-ego-node zen-ego-more\" href=\"", ); try writeEscaped(writer, more.href); try writer.print("\" tabindex=\"0\" aria-label=\"{d} more direct {s}\">", .{ more.count, if (direction == .callers) "callers" else "calls", }); try writeBox(writer, box); try writer.print( "<text class=\"zen-ego-label\" x=\"{d:.2}\" y=\"{d:.2}\" " ++ "font-size=\"{d:.2}\" text-anchor=\"middle\" " ++ "dominant-baseline=\"middle\">+{d} more</text></a>", .{ box.centerX(), box.centerY(), geometry.label_size, more.count }, );}fn writeBox( writer: *std.Io.Writer, box: layout.Rect,) std.Io.Writer.Error!void { try writer.print( "<rect x=\"{d:.2}\" y=\"{d:.2}\" width=\"{d:.2}\" " ++ "height=\"{d:.2}\" rx=\"8\"/>", .{ box.x, box.y, box.width, box.height }, );}fn writeNodeText( writer: *std.Io.Writer, node: model.Node, box: layout.Rect, label_size: f32, context_size: f32,) std.Io.Writer.Error!void { const center_x = box.centerX(); const center_y = box.centerY(); if (node.context.len == 0) { try writeText( writer, "zen-ego-label", node.label, center_x, center_y + if (node.badge.len > 0) @as(f32, 7) else 0, label_size, box.width - 14, ); return; } const context_y = if (node.badge.len > 0) center_y + 2 else center_y - 9; const label_y = if (node.badge.len > 0) center_y + 15 else center_y + 10; try writeText( writer, "zen-ego-context", node.context, center_x, context_y, context_size, box.width - 14, ); try writeText( writer, "zen-ego-label", node.label, center_x, label_y, label_size, box.width - 14, );}fn writeBadge( writer: *std.Io.Writer, value: []const u8, box: layout.Rect, size: f32,) std.Io.Writer.Error!void { const glyphs = std.unicode.utf8CountCodepoints(value) catch unreachable; const desired = @as(f32, @floatFromInt(glyphs)) * size * 0.62 + 12; const width = @min(box.width - 12, @max(@as(f32, 34), desired)); const x = box.x + box.width - width - 6; const y = box.y + 5; try writer.print( "<g class=\"zen-ego-badge\"><rect x=\"{d:.2}\" y=\"{d:.2}\" " ++ "width=\"{d:.2}\" height=\"14\" rx=\"7\"/>", .{ x, y, width }, ); try writeText(writer, "zen-ego-badge-label", value, x + width / 2, y + 7, size, width - 8); try writer.writeAll("</g>");}fn writeText( writer: *std.Io.Writer, class: []const u8, value: []const u8, x: f32, y: f32, size: f32, width: f32,) std.Io.Writer.Error!void { try writer.print( "<text class=\"{s}\" x=\"{d:.2}\" y=\"{d:.2}\" " ++ "font-size=\"{d:.2}\" text-anchor=\"middle\" " ++ "dominant-baseline=\"middle\"", .{ class, x, y, size }, ); const glyphs = std.unicode.utf8CountCodepoints(value) catch unreachable; if (@as(f32, @floatFromInt(glyphs)) * size * 0.62 > width) { try writer.print( " textLength=\"{d:.2}\" lengthAdjust=\"spacingAndGlyphs\"", .{width}, ); } try writer.writeByte('>'); try writeEscaped(writer, value); try writer.writeAll("</text>");}fn writeAccessibleName( writer: *std.Io.Writer, node: model.Node,) std.Io.Writer.Error!void { if (node.title.len > 0) return writeEscaped(writer, node.title); if (node.badge.len > 0) { try writeEscaped(writer, node.badge); try writer.writeByte('.'); } if (node.context.len > 0) { try writeEscaped(writer, node.context); try writer.writeByte('.'); } try writeEscaped(writer, node.label);}fn writeEscaped( writer: *std.Io.Writer, value: []const u8,) std.Io.Writer.Error!void { var escaped: [escaped_bytes_max]u8 = undefined; const length = html.escapedLength(value) catch unreachable; std.debug.assert(length <= escaped.len); try writer.writeAll(html.writeEscaped(&escaped, value));}Complete call list for diagram.ego.svg.write
8 direct calls.
tiny.zen.diagram.ego.LayoutPlan.inspect[function] atlib/zen/src/diagram/ego/layout.zig:77lib.zen.src.diagram.ego.svg.writeBand[function] — private source atlib/zen/src/diagram/ego/svg.zig:496in nearest public ownertiny.zen.diagram.ego.svglib.zen.src.diagram.ego.svg.writeConnections[function] — private source atlib/zen/src/diagram/ego/svg.zig:317in nearest public ownertiny.zen.diagram.ego.svglib.zen.src.diagram.ego.svg.writeEmptyStates[function] — private source atlib/zen/src/diagram/ego/svg.zig:456in nearest public ownertiny.zen.diagram.ego.svglib.zen.src.diagram.ego.svg.writeHeaders[function] — private source atlib/zen/src/diagram/ego/svg.zig:283in nearest public ownertiny.zen.diagram.ego.svglib.zen.src.diagram.ego.svg.writeNode[function] — private source atlib/zen/src/diagram/ego/svg.zig:509in nearest public ownertiny.zen.diagram.ego.svglib.zen.src.diagram.ego.svg.writeOpen[function] — private source atlib/zen/src/diagram/ego/svg.zig:254in nearest public ownertiny.zen.diagram.ego.svglib.zen.src.diagram.ego.svg.writeSelfLoop[function] — private source atlib/zen/src/diagram/ego/svg.zig:435in nearest public ownertiny.zen.diagram.ego.svg
Audit
| Definitions | 7 |
|---|---|
| Public names | 9 |
| Members | 2 |
| Version | 26.7.0 |
| Revision | daab053ee433 |