clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

migrate four renderers off Fly

Dami committed 71a838e
commit
71a838ec92a74a1fc3cef79b5dd879c23c2fe185
parent
a9c93ddde6fc5416d1f31f79bb14460ac8dd6806
author
Dami <dami.etoile@gmail.com>
52 files changed, +2864 -45
Mdiagrams.net/README.md8+-
Adiagramzip-python-units/symbolator/.gitignore7+
Adiagramzip-python-units/symbolator/README.md33++
Adiagramzip-python-units/symbolator/THIRD_PARTY_NOTICES.md10+
Adiagramzip-python-units/symbolator/pylock.toml8+
Adiagramzip-python-units/symbolator/pyproject.toml17+
Adiagramzip-python-units/symbolator/src/entry.py141+++++++
Adiagramzip-python-units/symbolator/src/protocol.py100+++++
Adiagramzip-python-units/symbolator/src/render.py275++++++++++++++
Adiagramzip-python-units/symbolator/test/test_protocol.py24+
Adiagramzip-python-units/symbolator/test/test_render.py116++++++
Adiagramzip-python-units/symbolator/uv.lock287+++++++++++++++
Adiagramzip-python-units/symbolator/wrangler.jsonc12+
Mdiagramzip-render/README.md18+-
Adiagramzip-render/d2-wasm/d2-custom.wasmbin
Adiagramzip-render/d2-wasm/provenance.md13+
Adiagramzip-render/d2-wasm/source/go.mod46++
Adiagramzip-render/d2-wasm/source/go.sum169+++++++++
Adiagramzip-render/d2-wasm/source/main.go62+++
Adiagramzip-render/d2-wasm/source/worker.go130+++++++
Adiagramzip-render/d2-wasm/wasm_exec.js575++++++++++++++++++++++++++++++
Mdiagramzip-render/package.json1+
Adiagramzip-render/scripts/build-d2-wasm.mjs24+
Mdiagramzip-render/scripts/deploy-compatibility-units.mjs3+-
Mdiagramzip-render/scripts/deploy-python-units.mjs1+
Mdiagramzip-render/scripts/deploy-worker-units.mjs6+-
Mdiagramzip-render/scripts/smoke-units.mjs26+-
Mdiagramzip-render/src/adapters/edge.ts2+
Adiagramzip-render/src/adapters/edge/d2.ts76++++
Adiagramzip-render/src/adapters/edge/ditaa.ts84++++
Mdiagramzip-render/src/adapters/edge/types.ts6+-
Mdiagramzip-render/src/catalog.ts35++-
Adiagramzip-render/src/raw.d.ts4+
Adiagramzip-render/src/structurizr.ts231++++++++++++
Adiagramzip-render/src/units/d2.ts14+
Adiagramzip-render/src/units/structurizr.ts68++++
Mdiagramzip-render/src/units/svgbob.ts25+-
Mdiagramzip-render/test/catalog.test.ts21+
Adiagramzip-render/test/d2.test.ts26+
Adiagramzip-render/test/ditaa.test.ts91+++++
Mdiagramzip-render/test/gateway.test.ts2+-
Adiagramzip-render/test/structurizr.test.ts73++++
Ddiagramzip-render/wrangler.compatibility/d2.jsonc1-
Ddiagramzip-render/wrangler.compatibility/ditaa.jsonc1-
Ddiagramzip-render/wrangler.compatibility/structurizr.jsonc1-
Ddiagramzip-render/wrangler.compatibility/symbolator.jsonc1-
Adiagramzip-render/wrangler.units/d2.jsonc1+
Adiagramzip-render/wrangler.units/structurizr.jsonc14+
Mdiagramzip-render/wrangler.units/svgbob.jsonc9+-
Mdiagramzip/src/renderer-units.js2+-
Mdiagramzip/test/renderer-units.test.js3+-
Mserver/src/main/resources/web/diagramzip/assets/index-CzBQjn0p.js6+-
M diagrams.net/README.md +5 -3
A diagramzip-python-units/symbolator/.gitignore +7 -0
A diagramzip-python-units/symbolator/README.md +33 -0
@@ -0,0 +1,33 @@
1+# Symbolator Python Worker2+3+This unit preserves the useful Symbolator HDL component workflow while removing4+the native desktop dependencies from the Kroki binary. It accepts Verilog /5+SystemVerilog module declarations and VHDL entities/components, preserving6+generics, grouped ports, directions, buses, clock markers, bubbles, titles,7+scaling, and type labels.8+9+The pinned Kroki Symbolator 1.2.2 package cannot be imported directly into a10+Python Worker: its SVG backend imports `gi`, Cairo, and Pango for text metrics.11+Those native bindings are not Pyodide packages. This unit is therefore a small12+translation/rendering path with deterministic approximate text metrics. Font13+metrics and exact layout may differ from the native renderer; that loss is14+intentional and documented so coverage can move off the Java image.15+16+The unit has no filesystem, subprocess, or network access. The hostname selects17+the renderer; the request body cannot select an engine.18+19+## Local checks20+21+```sh22+uv run pytest -q23+uv run pywrangler sync24+uv run pywrangler dev --port 879125+```26+27+## Known losses28+29+- Native Pango font shaping is replaced by conservative monospace-like width30+ estimates. SVG text remains selectable and uses browser fonts.31+- The native command's library-directory scanning and persisted array-type32+ cache are intentionally unavailable; requests contain one HDL source.33+- PNG/PDF/PS/EPS output is not supported by the Worker plane; SVG is supported.
A diagramzip-python-units/symbolator/THIRD_PARTY_NOTICES.md +10 -0
A diagramzip-python-units/symbolator/pylock.toml +8 -0
A diagramzip-python-units/symbolator/pyproject.toml +17 -0
A diagramzip-python-units/symbolator/src/entry.py +141 -0
@@ -0,0 +1,141 @@
1+from __future__ import annotations2+3+import json4+from urllib.parse import urlparse5+6+from workers import Request, Response, WorkerEntrypoint7+8+from protocol import MAX_REQUEST_BYTES, ProtocolError, parse_render_input9+from render import render_svg10+11+12+UNIT_ID = "symbolator"13+EXPOSED_HEADERS = ", ".join([14+ "Cache-Control",15+ "Content-Type",16+ "X-Diagram-Cache",17+ "X-Diagram-Engine",18+ "X-Diagram-Engine-Version",19+ "X-Diagram-Pipeline",20+ "X-Diagram-Renderer",21+ "X-Diagram-Unit",22+ "X-Renderer-Build",23+])24+25+26+def _json(value, status=200, cache_control="no-store") -> Response:27+ return Response(json.dumps(value, separators=(",", ":")), status=status, headers={28+ "Access-Control-Allow-Origin": "*",29+ "Cache-Control": cache_control,30+ "Content-Type": "application/json; charset=utf-8",31+ })32+33+34+def _error(error: ProtocolError) -> Response:35+ return _json({"error": {"code": error.code, "message": str(error)}}, status=error.status)36+37+38+async def _bounded_text(request: Request) -> str:39+ declared = request.headers.get("Content-Length")40+ if declared is not None:41+ try:42+ length = int(declared)43+ except ValueError as error:44+ raise ProtocolError(400, "invalid_request", "Content-Length is invalid.") from error45+ if length < 0:46+ raise ProtocolError(400, "invalid_request", "Content-Length is invalid.")47+ if length > MAX_REQUEST_BYTES:48+ raise ProtocolError(413, "request_too_large", "Render request is too large.")49+ if request.body is None:50+ raise ProtocolError(400, "invalid_request", "A JSON request body is required.")51+ reader = request.body.getReader()52+ chunks: list[bytes] = []53+ length = 054+ try:55+ while True:56+ result = await reader.read()57+ if result.done:58+ break59+ chunk = result.value.to_bytes()60+ length += len(chunk)61+ if length > MAX_REQUEST_BYTES:62+ await reader.cancel("request too large")63+ raise ProtocolError(413, "request_too_large", "Render request is too large.")64+ chunks.append(chunk)65+ finally:66+ # A complete stream only needs its reader lock released. If an error67+ # occurred before cancellation, best-effort cancellation avoids keeping68+ # a request body live after the response.69+ try:70+ reader.releaseLock()71+ except Exception:72+ pass73+ try:74+ return b"".join(chunks).decode("utf-8")75+ except UnicodeDecodeError as error:76+ raise ProtocolError(400, "invalid_request", "Request body must be UTF-8 JSON.") from error77+78+79+class Default(WorkerEntrypoint):80+ async def fetch(self, request: Request):81+ path = urlparse(request.url).path82+ if request.method == "OPTIONS":83+ return Response(None, status=204, headers={84+ "Access-Control-Allow-Headers": "Accept, Content-Type",85+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",86+ "Access-Control-Allow-Origin": "*",87+ "Access-Control-Max-Age": "86400",88+ })89+ if request.method == "GET" and path == "/v1/health":90+ return _json({"ok": True, "unit": UNIT_ID, "id": UNIT_ID})91+ if request.method == "GET" and path == "/v1/capabilities":92+ return _json({93+ "unit": UNIT_ID,94+ "id": UNIT_ID,95+ "kind": "render",96+ "format": "svg",97+ "runtime": "edge-python",98+ "version": "1.2.2-compatible-translation",99+ "build": self.env.RENDERER_BUILD,100+ "pipeline": [UNIT_ID],101+ "knownLosses": [102+ "Native Pango font metrics and shaping are replaced by browser-font SVG estimates.",103+ "One request contains one HDL source; filesystem library scanning and persisted type caches are unavailable.",104+ "Only SVG output is supported.",105+ ],106+ }, cache_control="public, max-age=300")107+ if request.method != "POST" or path != "/v1/svg":108+ return _json({"error": {"code": "not_found", "message": "Route not found."}}, status=404)109+ if not (request.headers.get("Content-Type") or "").lower().startswith("application/json"):110+ return _error(ProtocolError(415, "unsupported_media_type", "Render requests must use application/json."))111+ try:112+ request_input = parse_render_input(await _bounded_text(request))113+ svg, version = render_svg(114+ request_input.source,115+ request_input.options,116+ title_text=request_input.title,117+ description=request_input.description,118+ presentation_background=request_input.background,119+ presentation_padding=request_input.padding,120+ presentation_frame=request_input.frame,121+ )122+ except ProtocolError as error:123+ return _error(error)124+ except Exception as error:125+ print(json.dumps({"message": "Symbolator renderer failed", "unit": UNIT_ID, "error": str(error)}))126+ return _json({"error": {"code": "render_failed", "message": "The HDL source could not be rendered."}}, status=422)127+ return Response(svg, headers={128+ "Access-Control-Allow-Origin": "*",129+ "Access-Control-Expose-Headers": EXPOSED_HEADERS,130+ "Cache-Control": "public, max-age=1800",131+ "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",132+ "Content-Type": "image/svg+xml; charset=utf-8",133+ "X-Content-Type-Options": "nosniff",134+ "X-Diagram-Cache": "MISS",135+ "X-Diagram-Engine": UNIT_ID,136+ "X-Diagram-Engine-Version": version,137+ "X-Diagram-Pipeline": UNIT_ID,138+ "X-Diagram-Renderer": "edge-python",139+ "X-Diagram-Unit": UNIT_ID,140+ "X-Renderer-Build": self.env.RENDERER_BUILD,141+ })
A diagramzip-python-units/symbolator/src/protocol.py +100 -0
@@ -0,0 +1,100 @@
1+import json2+import math3+import re4+from dataclasses import dataclass5+from typing import Any6+7+MAX_REQUEST_BYTES = 1_048_5768+MAX_SOURCE_LENGTH = 524_2889+MAX_OPTION_COUNT = 6410+OPTION_NAME = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$", re.IGNORECASE)11+BACKGROUND = re.compile(r"^#[0-9a-f]{6}$", re.IGNORECASE)12+KNOWN_OPTIONS = {"component", "transparent", "title", "scale", "no-type", "library-name"}13+14+15+class ProtocolError(Exception):16+ def __init__(self, status: int, code: str, message: str):17+ super().__init__(message)18+ self.status = status19+ self.code = code20+21+22+@dataclass(frozen=True)23+class RenderInput:24+ source: str25+ options: dict[str, str]26+ title: str27+ description: str28+ background: str29+ padding: int30+ frame: bool31+32+33+def _object(value: Any, name: str) -> dict[str, Any]:34+ if not isinstance(value, dict):35+ raise ProtocolError(400, "invalid_request", f"{name} must be an object.")36+ return value37+38+39+def _string(value: Any, name: str, maximum: int) -> str:40+ if not isinstance(value, str):41+ raise ProtocolError(400, "invalid_request", f"{name} must be a string.")42+ if len(value) > maximum:43+ raise ProtocolError(413, "request_too_large", f"{name} is too large.")44+ return value45+46+47+def parse_render_input(body: str) -> RenderInput:48+ if len(body.encode("utf-8")) > MAX_REQUEST_BYTES:49+ raise ProtocolError(413, "request_too_large", "Render request is too large.")50+ try:51+ value = _object(json.loads(body), "request")52+ except ProtocolError:53+ raise54+ except (json.JSONDecodeError, UnicodeError) as error:55+ raise ProtocolError(400, "invalid_json", "Render request is not valid JSON.") from error56+ if "engine" in value:57+ raise ProtocolError(400, "invalid_request", "Renderer unit requests must not select an engine.")58+ if value.get("format", "svg") != "svg":59+ raise ProtocolError(400, "unsupported_format", "The rendering plane currently supports SVG only.")60+ source = _string(value.get("source"), "source", MAX_SOURCE_LENGTH)61+ if not source.strip():62+ raise ProtocolError(400, "empty_source", "Diagram source cannot be empty.")63+ raw_options = _object(value.get("options", {}), "options")64+ if len(raw_options) > MAX_OPTION_COUNT:65+ raise ProtocolError(400, "invalid_options", "Too many renderer options.")66+ options: dict[str, str] = {}67+ for name, option in raw_options.items():68+ if not isinstance(name, str) or not OPTION_NAME.fullmatch(name):69+ raise ProtocolError(400, "invalid_options", f"Invalid renderer option: {name}.")70+ if name.lower() not in KNOWN_OPTIONS:71+ raise ProtocolError(400, "invalid_options", f"Unknown renderer option: {name}.")72+ if not isinstance(option, (str, int, float, bool)) or isinstance(option, (dict, list)):73+ raise ProtocolError(400, "invalid_options", f"Renderer option {name} must be scalar.")74+ normalized = str(option).lower() if isinstance(option, bool) else str(option)75+ if len(normalized) > 4096:76+ raise ProtocolError(400, "invalid_options", f"Renderer option {name} is too large.")77+ options[name.lower()] = normalized78+ if "scale" in options:79+ try:80+ scale = float(options["scale"])81+ except ValueError as error:82+ raise ProtocolError(400, "invalid_options", "options.scale must be numeric.") from error83+ if not math.isfinite(scale) or not 0 < scale <= 16:84+ raise ProtocolError(400, "invalid_options", "options.scale must be finite and between 0 and 16.")85+ if "library-name" in options and "title" not in options:86+ raise ProtocolError(400, "invalid_options", "options.library-name requires the title option.")87+ metadata = _object(value.get("metadata", {}), "metadata")88+ title = _string(metadata.get("title", ""), "metadata.title", 200)89+ description = _string(metadata.get("description", ""), "metadata.description", 2000)90+ presentation = _object(value.get("presentation", {}), "presentation")91+ background = _string(presentation.get("background", ""), "presentation.background", 7)92+ if background and not BACKGROUND.fullmatch(background):93+ raise ProtocolError(400, "invalid_presentation", "presentation.background must be an RGB hex color.")94+ padding = presentation.get("padding", 0)95+ frame = presentation.get("frame", False)96+ if not isinstance(padding, int) or isinstance(padding, bool) or not 0 <= padding <= 256:97+ raise ProtocolError(400, "invalid_presentation", "presentation.padding must be an integer from 0 to 256.")98+ if not isinstance(frame, bool):99+ raise ProtocolError(400, "invalid_presentation", "presentation.frame must be a boolean.")100+ return RenderInput(source, options, title, description, background, padding, frame)
A diagramzip-python-units/symbolator/src/render.py +275 -0
@@ -0,0 +1,275 @@
1+from __future__ import annotations2+3+import html4+import math5+import re6+from dataclasses import dataclass, field7+8+from protocol import BACKGROUND, KNOWN_OPTIONS, ProtocolError9+10+11+MAX_SVG_LENGTH = 4_194_30412+13+14+@dataclass15+class Port:16+ name: str17+ direction: str18+ data_type: str = ""19+ bus: bool = False20+ clock: bool = False21+ bubble: bool = False22+ group: str | None = None23+24+25+@dataclass26+class Component:27+ name: str28+ generics: list[Port] = field(default_factory=list)29+ ports: list[Port] = field(default_factory=list)30+31+32+GROUP = re.compile(r"\{\{\s*([^|}]+)?\s*\|\s*([^}]+)?\s*\}\}")33+VERILOG_MODULE = re.compile(r"\bmodule\s+([A-Za-z_$][\w$]*)", re.IGNORECASE)34+VHDL_ENTITY = re.compile(r"\b(?:entity|component)\s+([A-Za-z_]\w*)\s+is", re.IGNORECASE)35+36+37+def _group(line: str) -> str | None:38+ match = GROUP.search(line)39+ if not match:40+ return None41+ return (match.group(2) or match.group(1) or "").strip() or None42+43+44+def _clean(text: str) -> str:45+ text = re.sub(r"/\*.*?\*/", " ", text, flags=re.S)46+ text = re.sub(r"//[^\n]*", "\n", text)47+ return text48+49+50+def _split_names(value: str) -> list[str]:51+ return [part.strip() for part in value.split(",") if re.fullmatch(r"[A-Za-z_$][\w$]*", part.strip())]52+53+54+def _verilog(source: str) -> list[Component]:55+ components: list[Component] = []56+ for module_match in VERILOG_MODULE.finditer(source):57+ start = module_match.start()58+ end = re.search(r"\bendmodule\b", source[module_match.end():], re.IGNORECASE)59+ body = source[module_match.end(): module_match.end() + end.start() if end else len(source)]60+ component = Component(module_match.group(1))61+ pending_group: str | None = None62+ for raw in body.splitlines():63+ candidate_group = _group(raw)64+ if candidate_group is not None:65+ pending_group = candidate_group66+ continue67+ line = re.sub(r"\s+", " ", raw).strip()68+ parameter = re.match(r"parameter(?:\s+\w+)?\s+([A-Za-z_$][\w$]*)", line, re.I)69+ if parameter:70+ component.generics.append(Port(parameter.group(1), "in", "", group=pending_group))71+ pending_group = None72+ continue73+ # Header-style declarations may be comma-separated on one line;74+ # split only at commas that introduce another direction so ranges75+ # and ordinary name lists remain intact.76+ for declaration in re.split(r",(?=\s*(?:input|output|inout)\b)", line, flags=re.I):77+ declaration = declaration.strip().lstrip("(").rstrip(");").strip()78+ port = re.match(79+ r"(?:input|output|inout)\s+(?:(?:wire|logic|reg|tri|signed|unsigned)\s+)*(?:(\[[^]]+\])\s*)?(.+?);?$",80+ declaration,81+ re.I,82+ )83+ if not port:84+ continue85+ direction = re.match(r"(input|output|inout)", declaration, re.I).group(1).lower()86+ direction = {"input": "in", "output": "out"}.get(direction, direction)87+ data_type = port.group(1) or ""88+ for name in _split_names(port.group(2).strip().rstrip(";")):89+ component.ports.append(Port(name, direction, data_type, bool(data_type), bool(re.search(r"clock", name, re.I)), bool(re.search(r"_[nb]$", name, re.I)), pending_group))90+ pending_group = None91+ components.append(component)92+ return components93+94+95+def _vhdl(source: str) -> list[Component]:96+ components: list[Component] = []97+ for match in VHDL_ENTITY.finditer(source):98+ tail = source[match.end():]99+ end = re.search(r"\bend\s+(?:entity|component)?\s*\w*\s*;", tail, re.I)100+ body = tail[: end.start()] if end else tail101+ component = Component(match.group(1))102+ pending_group: str | None = None103+ section = "ports"104+ # Normalize compact declarations into the same statement stream as105+ # the usual multiline VHDL style. Comments remain available for the106+ # grouped-port form used by Symbolator.107+ body = re.sub(r"\b(generic|port)\s*\(", r"\1 (\n", body, flags=re.I)108+ body = body.replace(")", "\n)")109+ for raw in body.splitlines():110+ candidate_group = _group(raw)111+ if candidate_group is not None:112+ pending_group = candidate_group113+ continue114+ if re.search(r"\bgeneric\s*\(", raw, re.I):115+ section = "generics"116+ continue117+ if re.search(r"\bport\s*\(", raw, re.I):118+ section = "ports"119+ continue120+ line = re.sub(r"--[^\n]*", "", raw).strip().rstrip(";,)")121+ item = re.match(r"(.+?)\s*:\s*(?:(in|out|inout|buffer)\s+)?(.+)$", line, re.I)122+ if not item:123+ continue124+ names, direction, data_type = item.groups()125+ direction = (direction or "in").lower()126+ for name in _split_names(names.replace(" ", "")):127+ is_bus = "(" in data_type or "vector" in data_type.lower()128+ port = Port(name, direction, data_type.strip(), is_bus, bool(re.search(r"clock", name, re.I)), bool(re.search(r"_[nb]$", name, re.I)), pending_group)129+ (component.generics if section == "generics" else component.ports).append(port)130+ pending_group = None131+ components.append(component)132+ return components133+134+135+def parse_components(source: str) -> list[Component]:136+ clean = _clean(source)137+ components = _verilog(source)138+ return components or _vhdl(source)139+140+141+def _text_width(text: str, size: float = 12) -> float:142+ return max(1.0, len(re.sub(r"<[^>]+>", "", text)) * size * 0.62)143+144+145+def _esc(text: str) -> str:146+ return html.escape(text, quote=True)147+148+149+def _text(x: float, y: float, value: str, anchor: str = "start", cls: str = "fnt1") -> str:150+ return f'<text class="{cls}" x="{x:g}" y="{y:g}" text-anchor="{anchor}" dominant-baseline="middle">{_esc(value)}</text>'151+152+153+def render_svg(154+ source: str,155+ options: dict[str, str] | None = None,156+ *,157+ title_text: str = "",158+ description: str = "",159+ presentation_background: str = "",160+ presentation_padding: int = 0,161+ presentation_frame: bool = False,162+) -> tuple[str, str]:163+ options = options or {}164+ unknown = set(options) - KNOWN_OPTIONS165+ if unknown:166+ raise ProtocolError(400, "invalid_options", f"Unknown renderer option: {sorted(unknown)[0]}.")167+ if "library-name" in options and "title" not in options:168+ raise ProtocolError(400, "invalid_options", "options.library-name requires the title option.")169+ if presentation_background and not BACKGROUND.fullmatch(presentation_background):170+ raise ProtocolError(400, "invalid_presentation", "presentation.background must be an RGB hex color.")171+ components = parse_components(source)172+ if not components:173+ raise ProtocolError(422, "render_failed", "No Verilog module or VHDL entity/component was found.")174+ requested = options.get("component", "")175+ if requested:176+ components = [item for item in components if item.name == requested]177+ if not components:178+ raise ProtocolError(422, "render_failed", f"Component {requested} was not found.")179+ component = components[-1]180+ no_type = "no-type" in options181+ title = "title" in options182+ library = options.get("library-name", "")183+ try:184+ scale = float(options.get("scale", "1.0"))185+ except ValueError as error:186+ raise ProtocolError(400, "invalid_options", "options.scale must be numeric.") from error187+ if not math.isfinite(scale) or scale <= 0 or scale > 16:188+ raise ProtocolError(400, "invalid_options", "options.scale must be greater than zero and at most 16.")189+190+ groups: list[tuple[str | None, list[Port]]] = []191+ for port in component.ports:192+ if groups and groups[-1][0] == port.group:193+ groups[-1][1].append(port)194+ else:195+ groups.append((port.group, [port]))196+ groups = [(name, ports) for name, ports in groups if ports]197+ left = [port for _, ports in groups for port in ports if port.direction == "in"]198+ right = [port for _, ports in groups for port in ports if port.direction in ("out", "inout", "buffer")]199+ max_label = max([_text_width(port.name) for port in left + right] + [0])200+ max_type = 0 if no_type else max([_text_width(port.data_type) for port in left + right] + [0])201+ width = max(180, 120 + max_label * 2 + max_type * 2)202+ y = 30203+ parts: list[str] = []204+ sections: list[tuple[float, float, float, float, str | None]] = []205+ if component.generics:206+ height = max(38, 22 * len(component.generics) + 16)207+ sections.append((0, y, width, height, None))208+ for index, port in enumerate(component.generics):209+ parts.append(_text(20, y + 22 + index * 22, port.name))210+ y += height211+ row = 0212+ port_sections: list[tuple[float, list[Port], str | None]] = []213+ for group_name, ports in groups:214+ rows = max(len([p for p in ports if p.direction == "in"]), len([p for p in ports if p.direction != "in"]))215+ height = max(42, 22 * rows + (24 if group_name else 0) + 12)216+ sections.append((0, y, width, height, group_name))217+ port_sections.append((y, ports, group_name))218+ if group_name:219+ parts.append(_text(width / 2, y + 12, group_name, "middle", "fnt2"))220+ y += height221+ row += rows222+ height_total = max(100, y + 30)223+ for sx, sy, sw, sh, group_name in sections:224+ fill = "#e8e8e8" if group_name is None else f"hsl({(sections.index((sx, sy, sw, sh, group_name)) * 77) % 360} 75% 88%)"225+ stroke = "#666" if group_name is None else "#222"226+ parts.insert(0, f'<rect x="{sx:g}" y="{sy:g}" width="{sw:g}" height="{sh:g}" fill="{fill}" stroke="{stroke}" stroke-width="2"/>')227+ for group_y, ports, group_name in port_sections:228+ top = group_y + (24 if group_name else 0) + 18229+ for index, port in enumerate([p for p in ports if p.direction == "in"]):230+ py = top + index * 22231+ parts.append(f'<line x1="-35" y1="{py:g}" x2="0" y2="{py:g}" stroke="#111" stroke-width="{3 if port.bus else 1}"/>')232+ if port.clock:233+ parts.append(f'<path d="M -35 {py-5:g} L -27 {py:g} L -35 {py+5:g}" fill="none" stroke="#111"/>')234+ if port.bubble:235+ parts.append(f'<circle cx="-35" cy="{py:g}" r="4" fill="white" stroke="#111"/>')236+ label = port.name if no_type or not port.data_type else f"{port.name}"237+ parts.append(_text(12, py, label))238+ if not no_type and port.data_type:239+ parts.append(_text(-43, py, port.data_type, "end", "fnt3"))240+ for index, port in enumerate([p for p in ports if p.direction != "in"]):241+ py = top + index * 22242+ parts.append(f'<line x1="{width:g}" y1="{py:g}" x2="{width+35:g}" y2="{py:g}" stroke="#111" stroke-width="{3 if port.bus else 1}"/>')243+ if port.bubble:244+ parts.append(f'<circle cx="{width+35:g}" cy="{py:g}" r="4" fill="white" stroke="#111"/>')245+ parts.append(_text(width - 12, py, port.name, "end"))246+ if not no_type and port.data_type:247+ parts.append(_text(width + 43, py, port.data_type, "start", "fnt3"))248+ if title:249+ if library:250+ parts.append(_text(width / 2, -24, library, "middle", "fnt4"))251+ parts.append(_text(width / 2, -8, component.name, "middle", "fnt4"))252+ else:253+ parts.append(_text(width / 2, -12, component.name, "middle", "fnt4"))254+ transparent = "transparent" in options255+ title_height = 40 if title and library else 25 if title else 0256+ view_x, view_y = -max_type - 50, -35 if title else -10257+ view_w, view_h = width + max_type * 2 + 100, height_total + title_height258+ padding = float(presentation_padding)259+ view_x -= padding260+ view_y -= padding261+ view_w += padding * 2262+ view_h += padding * 2263+ styles = """.fnt1 { fill:#000; font-family:Sans,sans-serif; font-size:12pt; } .fnt2 { fill:#000; font-family:Sans,sans-serif; font-size:9pt; font-weight:bold; } .fnt3 { fill:#969696; font-family:Sans,sans-serif; font-size:12pt; } .fnt4 { fill:#000; font-family:Sans,sans-serif; font-size:12pt; font-weight:bold; }"""264+ background_color = presentation_background or ("" if transparent else "white")265+ background = f'<rect x="{view_x:g}" y="{view_y:g}" width="{view_w:g}" height="{view_h:g}" fill="{background_color}"/>' if background_color else ""266+ if title_text:267+ parts.insert(0, _text(view_x + 8, view_y + 12, title_text, "start", "fnt2"))268+ if description:269+ parts.insert(1 if title_text else 0, _text(view_x + 8, view_y + 28, description, "start", "fnt3"))270+ if presentation_frame:271+ parts.append(f'<rect x="{view_x + 0.5:g}" y="{view_y + 0.5:g}" width="{max(0, view_w - 1):g}" height="{max(0, view_h - 1):g}" fill="none" stroke="#000" stroke-width="1" vector-effect="non-scaling-stroke"/>')272+ svg = f'<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="{view_w*scale:g}" height="{view_h*scale:g}" viewBox="{view_x:g} {view_y:g} {view_w:g} {view_h:g}"><style>{styles}</style>{background}{"".join(parts)}</svg>'273+ if len(svg.encode("utf-8")) > MAX_SVG_LENGTH:274+ raise ProtocolError(413, "render_too_large", "Rendered SVG is too large.")275+ return svg, "1.2.2-compatible-translation"
A diagramzip-python-units/symbolator/test/test_protocol.py +24 -0
A diagramzip-python-units/symbolator/test/test_render.py +116 -0
@@ -0,0 +1,116 @@
1+import re2+3+import pytest4+5+import render6+from protocol import ProtocolError, parse_render_input7+from render import parse_components, render_svg8+9+10+VERILOG = """module demo_device #(11+parameter SIZE = 812+) (13+//# {{clocks|Clocking}}14+input wire clock,15+//# {{control|Control signals}}16+input wire reset,17+input wire enable,18+//# {{data|Data ports}}19+input wire [SIZE-1:0] data_in,20+output wire [SIZE-1:0] data_out21+); endmodule"""22+23+24+VHDL = """entity demo is25+generic (26+ SIZE : positive27+);28+port (29+ clock : in std_logic;30+ data_in : in std_logic_vector(7 downto 0);31+ data_out : out std_logic_vector(7 downto 0)32+);33+end entity demo;"""34+35+36+def test_verilog_ports_groups_and_svg():37+ components = parse_components(VERILOG)38+ assert components[-1].name == "demo_device"39+ assert [port.name for port in components[-1].ports] == ["clock", "reset", "enable", "data_in", "data_out"]40+ svg, version = render_svg(VERILOG, {"title": "", "scale": "1.5"})41+ assert version.startswith("1.2.2")42+ assert 'class="fnt1"' in svg43+ assert "data_in" in svg and "Clocking" in svg44+ assert "stroke-width=\"3\"" in svg45+ assert 'x1="-35"' in svg46+ # Inputs terminate at the component's left edge; outputs start at its47+ # right edge. Avoid tying this to font metrics or a particular scale.48+ assert 'x1="-35"' in svg and 'x2="0"' in svg49+ assert re.search(r'<line x1="[\d.]+"[^>]+x2="[\d.]+"', svg)50+51+52+def test_direction_bubbles_and_presentation_are_applied():53+ source = """module m(54+input wire reset_n,55+output wire done56+); endmodule"""57+ svg, _ = render_svg(58+ source,59+ {"title": "", "library-name": "lib"},60+ title_text="Hardware",61+ description="A component",62+ presentation_background="#112233",63+ presentation_padding=24,64+ presentation_frame=True,65+ )66+ assert 'cx="-35"' in svg67+ assert 'fill="#112233"' in svg68+ assert "Hardware" in svg and "A component" in svg69+ assert "stroke=\"#000\"" in svg70+ assert re.search(r'viewBox="-7[45] -59 ', svg)71+72+73+def test_compact_ansi_header_keeps_both_directions():74+ components = parse_components("module m(input wire reset_n, output wire done); endmodule")75+ assert [(port.name, port.direction) for port in components[-1].ports] == [76+ ("reset_n", "in"),77+ ("done", "out"),78+ ]79+80+81+def test_protocol_rejects_unknown_and_invalid_options():82+ def parse(options):83+ import json84+ return parse_render_input(json.dumps({"source": "module m; endmodule", "options": options}))85+86+ with pytest.raises(ProtocolError, match="Unknown renderer option"):87+ parse({"bogus": "1"})88+ with pytest.raises(ProtocolError, match="finite"):89+ parse({"scale": "nan"})90+ with pytest.raises(ProtocolError, match="finite"):91+ parse({"scale": "inf"})92+ with pytest.raises(ProtocolError, match="requires"):93+ parse({"library-name": "lib"})94+95+96+def test_render_size_is_bounded(monkeypatch):97+ monkeypatch.setattr(render, "MAX_SVG_LENGTH", 100)98+ with pytest.raises(ProtocolError, match="too large"):99+ render_svg("module m(input wire clock); endmodule")100+101+102+def test_vhdl_generics_and_bus_types():103+ components = parse_components(VHDL)104+ assert components[-1].generics[0].name == "SIZE"105+ svg, _ = render_svg(VHDL, {"no-type": "true", "transparent": "true"})106+ assert "data_out" in svg107+ assert "fill=\"white\"" not in svg108+109+110+def test_component_filter_and_missing_component():111+ svg, _ = render_svg(VERILOG, {"component": "demo_device"})112+ assert "demo_device" not in svg or "data_out" in svg113+ try:114+ render_svg(VERILOG, {"component": "missing"})115+ except Exception as error:116+ assert "not found" in str(error)
A diagramzip-python-units/symbolator/uv.lock +287 -0
@@ -0,0 +1,287 @@
1+version = 12+revision = 33+requires-python = ">=3.13"4+resolution-markers = [5+ "python_full_version >= '3.14'",6+ "python_full_version < '3.14'",7+]8+9+[[package]]10+name = "click"11+version = "8.4.2"12+source = { registry = "https://pypi.org/simple" }13+dependencies = [14+ { name = "colorama", marker = "sys_platform == 'win32'" },15+]16+sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" }17+wheels = [18+ { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" },19+]20+21+[[package]]22+name = "colorama"23+version = "0.4.6"24+source = { registry = "https://pypi.org/simple" }25+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }26+wheels = [27+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },28+]29+30+[[package]]31+name = "diagramzip-symbolator"32+version = "0.1.0"33+source = { virtual = "." }34+35+[package.dev-dependencies]36+dev = [37+ { name = "pytest" },38+ { name = "workers-py" },39+ { name = "workers-runtime-sdk" },40+]41+42+[package.metadata]43+44+[package.metadata.requires-dev]45+dev = [46+ { name = "pytest", specifier = ">=8.4,<9" },47+ { name = "workers-py" },48+ { name = "workers-runtime-sdk" },49+]50+51+[[package]]52+name = "iniconfig"53+version = "2.3.0"54+source = { registry = "https://pypi.org/simple" }55+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }56+wheels = [57+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },58+]59+60+[[package]]61+name = "markdown-it-py"62+version = "4.2.0"63+source = { registry = "https://pypi.org/simple" }64+dependencies = [65+ { name = "mdurl" },66+]67+sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }68+wheels = [69+ { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },70+]71+72+[[package]]73+name = "mdurl"74+version = "0.1.2"75+source = { registry = "https://pypi.org/simple" }76+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }77+wheels = [78+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },79+]80+81+[[package]]82+name = "packaging"83+version = "26.3"84+source = { registry = "https://pypi.org/simple" }85+sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }86+wheels = [87+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },88+]89+90+[[package]]91+name = "pluggy"92+version = "1.6.0"93+source = { registry = "https://pypi.org/simple" }94+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }95+wheels = [96+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },97+]98+99+[[package]]100+name = "pygments"101+version = "2.21.0"102+source = { registry = "https://pypi.org/simple" }103+sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }104+wheels = [105+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },106+]107+108+[[package]]109+name = "pyjson5"110+version = "2.0.1"111+source = { registry = "https://pypi.org/simple" }112+sdist = { url = "https://files.pythonhosted.org/packages/f1/9a/3db19560e968d6e85b2a4ddf4b949c6ebf9dd1dcfb5a9f37736f8adeb927/pyjson5-2.0.1.tar.gz", hash = "sha256:a5b0e322e847b198a50d8a1ef16d6b2b19129644dc018d76773e81ef1487ca39", size = 352242, upload-time = "2026-05-15T16:12:54.931Z" }113+wheels = [114+ { url = "https://files.pythonhosted.org/packages/93/2c/0619f89a9576f335ba63eb851e73ba507480a8c7f28a2e7de2501ed3d303/pyjson5-2.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8a661d292801b38434d5288bf7329f9b50b3a693b1d2941c7595175842692eba", size = 301821, upload-time = "2026-05-15T16:09:28.721Z" },115+ { url = "https://files.pythonhosted.org/packages/91/31/7824913ec71e7421d6a57bc06228f3e2d946d8e8f738f898572dded0dc57/pyjson5-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cb5c1c066038ce6e1922d9d5be23f5cd14d5b5a3c96e6358aa5d0e379014a93", size = 158201, upload-time = "2026-05-15T16:09:30.261Z" },116+ { url = "https://files.pythonhosted.org/packages/bc/7d/4ddb249563a838425242342d2cf67976ccc292a831b543a92fa1a8a83b11/pyjson5-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7417eab751817fa5f070e41975d9df4aec3532d21389073318161f63cd96d636", size = 153244, upload-time = "2026-05-15T16:09:32.04Z" },117+ { url = "https://files.pythonhosted.org/packages/02/39/7622416ac0570d9ce377447bd5b2ec9383c1282e63cc6d0b65779f1336fa/pyjson5-2.0.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cc4802093b4ab9039367774988486fd04754cf416c32aebf94a94bafd8b8a478", size = 185568, upload-time = "2026-05-15T16:09:33.719Z" },118+ { url = "https://files.pythonhosted.org/packages/d8/90/2f317da231477b77481020d79c73bbe10625e4925ceeae77603726ef1763/pyjson5-2.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc3181274ed19ecb2315cf85a499bf83002554f42c72453666c616059d665a7b", size = 163169, upload-time = "2026-05-15T16:09:34.865Z" },119+ { url = "https://files.pythonhosted.org/packages/02/b3/20023c3cfe2f2c2523007d7031b5b7ad7ccd8856d2665547683cebaa6f8e/pyjson5-2.0.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5398306b8aa253e620af9ed8085767de9cd28d6846da45e535e80fcffa9f33d7", size = 166514, upload-time = "2026-05-15T16:09:36.383Z" },120+ { url = "https://files.pythonhosted.org/packages/e2/1c/b863e502153477b2845a54b688b72436457aca5107b300fca95e98184551/pyjson5-2.0.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b97b592287d52a6ec5af84dca94aab02b185f72f8ddb6fc99c58efb8891be5cd", size = 179907, upload-time = "2026-05-15T16:09:38.166Z" },121+ { url = "https://files.pythonhosted.org/packages/fc/b2/413fcb76632e5f6fa89a2ec83976755b5bb4696c6d9eca9ff3c70cd717b6/pyjson5-2.0.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4efbf5e910a3aa6f330a92d1ae940dda5ed662976ed5056526baaf2e1821f95d", size = 187171, upload-time = "2026-05-15T16:09:39.624Z" },122+ { url = "https://files.pythonhosted.org/packages/d6/11/66151b819407ab589aef36582038257f1ef42dc065e3423b3d8274f4fcd2/pyjson5-2.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e042f9b869e7f21d5f53a2638e5d0cf1daaba2342127c8777c502daf972602e", size = 175564, upload-time = "2026-05-15T16:09:41.356Z" },123+ { url = "https://files.pythonhosted.org/packages/f2/98/deb70690dab994474ea527cba91f43ae55928bcff498c441e812b60fc1e2/pyjson5-2.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:26074370d7a6dd38b6e8c28a2f10c790fc6dec938ec22ec3de6c93c97d195f10", size = 171530, upload-time = "2026-05-15T16:09:42.78Z" },124+ { url = "https://files.pythonhosted.org/packages/a9/be/969752a3a052d00698b6e1dd926c627d7a6475a7c8dd390db148363544f8/pyjson5-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3cb163a495096716a7a01d052b60bda0308ecfaeddcb2d50a247ba9d707e3e40", size = 1145510, upload-time = "2026-05-15T16:09:45.005Z" },125+ { url = "https://files.pythonhosted.org/packages/d0/be/4c5c92cdda5a911ee4450a74281782335e7ce6715638308322beb96be639/pyjson5-2.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5fdd66bdddc2d53421ab7427bedcd2ca9b2f1b4b3b464381aa60d305c94be38c", size = 1010114, upload-time = "2026-05-15T16:09:47.081Z" },126+ { url = "https://files.pythonhosted.org/packages/89/1e/72283bc505d77dcdab7eaeb0020cf0fd79a05d2991e886d1fcfa73e88ae1/pyjson5-2.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4ad222a3eff1cc93f9b70894c5f3d2ff5e46531e88feaa4818e329d5f3ae4307", size = 1322915, upload-time = "2026-05-15T16:09:48.885Z" },127+ { url = "https://files.pythonhosted.org/packages/06/b4/94a09e744a6bb6e76108b61a28a7cc5ecab1b8105cacde7548359b3b74a3/pyjson5-2.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:63f5cf25113dd1bf3bfeda211d99413e58ec6b9036ff34f7b69f85f23695e9c4", size = 1240126, upload-time = "2026-05-15T16:09:50.638Z" },128+ { url = "https://files.pythonhosted.org/packages/af/29/4549380cae425ee112f3c154606c35f5211c12ddf415f079c2b23f2d493b/pyjson5-2.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b0001eecce9080e6c170e978786501e8931e2075a3d15f5260e1bdd6a45e8976", size = 1178022, upload-time = "2026-05-15T16:09:52.639Z" },129+ { url = "https://files.pythonhosted.org/packages/1c/1a/5a8a869e855645858e45dbd077204cec3a85b2f2e669bc3c8cefe9c4a70e/pyjson5-2.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40371c73cc83ed81914028786a6cc5950bdf300ae82443df83a5dfa80c582fca", size = 1355161, upload-time = "2026-05-15T16:09:55.177Z" },130+ { url = "https://files.pythonhosted.org/packages/82/32/827066cd946447648275a892f494f4572d78e6a79d877fa6b6c14af599d8/pyjson5-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c9dca542eeb6e8fbbc1fe3373bfd18ae6250f1aedc78f39242ac12c8837091b8", size = 1208864, upload-time = "2026-05-15T16:09:56.919Z" },131+ { url = "https://files.pythonhosted.org/packages/2d/cc/a9bc12aff47d8bfd3074a21e3fb056ec0028fac28f8ec7e0fe4449d050f5/pyjson5-2.0.1-cp313-cp313-win32.whl", hash = "sha256:d30c8b2c91d530be475a8fab6dde8c3bcd3e89b999c9e0f05aec2bf2c24b0638", size = 115461, upload-time = "2026-05-15T16:10:30.711Z" },132+ { url = "https://files.pythonhosted.org/packages/c9/c7/fa3fc956fbc3fa6250c8b99ef75a8f52b691780fca1bfb368340283bd898/pyjson5-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:67f8f5b8d3e3b2ca5f618c928729986aa00fb588c476c0d0d3a151633ff41e0d", size = 135836, upload-time = "2026-05-15T16:10:32.062Z" },133+ { url = "https://files.pythonhosted.org/packages/f0/0a/e457b20d1e36a766d3ccd09c418b7ef41acabb679ff2248bb1cd38247ce6/pyjson5-2.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:bdf30ce5b1242f63254d936b30af75cae237fab0ee71cc2544163c82b6bc9201", size = 116652, upload-time = "2026-05-15T16:10:33.39Z" },134+ { url = "https://files.pythonhosted.org/packages/2b/4c/6266789e615576b62d2db6de26a3b4b8f4cc7a8ff23fe24e48363bca1682/pyjson5-2.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:954c14d40022fa40e0d36e0ebd337e9e90fd71145592d25a0e2868344e3daca0", size = 320487, upload-time = "2026-05-15T16:09:58.38Z" },135+ { url = "https://files.pythonhosted.org/packages/dc/75/c4a563034805e20b274fe4db963e1d480001931d88fe70b7d082033b7dcb/pyjson5-2.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f57529b98d21e8b76f8bc51af9d3c3057dd04a92287fd21ef7ec55255ca6f5c5", size = 166879, upload-time = "2026-05-15T16:09:59.849Z" },136+ { url = "https://files.pythonhosted.org/packages/f3/d2/5e6ec3580379794e9a99a402465f74586e93e93814974172202d1254fa89/pyjson5-2.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:545b655ef0b59f39fc29e2b63b4dadd45e447ad77bad2fdca859ff4db69e21f5", size = 162235, upload-time = "2026-05-15T16:10:01.147Z" },137+ { url = "https://files.pythonhosted.org/packages/5b/48/b2d0e868ef8375eb0696ddc74f71028fc4fcd26cdddaeb34485214f2dc87/pyjson5-2.0.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b519de49dd4cbf254179749e5c6bfe9bd8fd1f97e7755bff2e17cc6f79f85aa5", size = 182162, upload-time = "2026-05-15T16:10:02.523Z" },138+ { url = "https://files.pythonhosted.org/packages/ce/18/fed02a3d68c3badb87394e8420b72d7cb165ade208c1e02561637409aa99/pyjson5-2.0.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de48fbd0466c114c17f408be7bcbc5b3aedeb29ff5f73187022915e6d5eb7817", size = 169487, upload-time = "2026-05-15T16:10:03.876Z" },139+ { url = "https://files.pythonhosted.org/packages/8b/90/dc897332dda24e949828616e8e74d7937b587a56789f3aca148fcadddde2/pyjson5-2.0.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fe6516ff9ab94368c18ff8ac0ebdb53fac9e047da093d43e67d5e7cd98cdd0bf", size = 164128, upload-time = "2026-05-15T16:10:05.588Z" },140+ { url = "https://files.pythonhosted.org/packages/39/09/90104baeab58adfe63bda804174ef1806b1cabb1692e047fabc2f4aa1799/pyjson5-2.0.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ea7aadacac7ac6661117b708175f6beff75e132322c08bbf3794d8ec4ba572b4", size = 184306, upload-time = "2026-05-15T16:10:06.986Z" },141+ { url = "https://files.pythonhosted.org/packages/89/73/613efc3cdab5cf9ec399e6c5a0463b672e5f5cbd693a3a4d82cbdda19476/pyjson5-2.0.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5c339bcac113dfc30a1f88f23af273d67bb6169abee1684c0dd27daa71a32f9", size = 192239, upload-time = "2026-05-15T16:10:08.727Z" },142+ { url = "https://files.pythonhosted.org/packages/47/2a/17b97e02b37ab9353fc9713e56470be3d2539501ed18d6c676cfbcd0a57c/pyjson5-2.0.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:075de0f1e5e0ffee174a8941b5d9d8af632772a6e5eebe92fb0899db7f6ab7fa", size = 177652, upload-time = "2026-05-15T16:10:10.19Z" },143+ { url = "https://files.pythonhosted.org/packages/1c/43/a05331ba88fd1aa09ca418548c6b06692e34b2a64b109bb363e01ecafdeb/pyjson5-2.0.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c463c9bf508c1d316191898a3c0bfc03dd3de67bdc02f480b0742ac5c859eaeb", size = 174860, upload-time = "2026-05-15T16:10:11.976Z" },144+ { url = "https://files.pythonhosted.org/packages/2e/00/5d253751f4d27b7e63fdfd765041593fa588395f45b9cea496878d964448/pyjson5-2.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:54427c8cad0c1a516bad5b4457167cde1e7ef7ee563e28a944f59b3c7413b6a0", size = 1151556, upload-time = "2026-05-15T16:10:13.608Z" },145+ { url = "https://files.pythonhosted.org/packages/9c/f7/a30eed477295e92dfd59553063cf82bf510a7052efc7720c9c50d0082fdb/pyjson5-2.0.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:ca04839cecd364cb53de7ebcd81fd5915193707d968730ea1c8d4ff5c9c2f1a8", size = 1012330, upload-time = "2026-05-15T16:10:15.171Z" },146+ { url = "https://files.pythonhosted.org/packages/9c/60/80818fad90a99336f8bfc686d76d0b2d7c8bb51e8de5d031620fa1a2d7a1/pyjson5-2.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13a2de8b14aeb576b1623c8af419510bf871b8d9cc2308db41a46bcc94d00f2f", size = 1320970, upload-time = "2026-05-15T16:10:16.863Z" },147+ { url = "https://files.pythonhosted.org/packages/7c/3f/090eb3e0971d067defb58bbfd738f74a09be495c6f4f00d5d76a10755bca/pyjson5-2.0.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c8a689f5252e9791bfbc8fa916999202556a1e8c7b24dfd72de1ad84c493e10a", size = 1244775, upload-time = "2026-05-15T16:10:19.416Z" },148+ { url = "https://files.pythonhosted.org/packages/61/38/e7546ad733affe51a5462bad21bceb4fd659baf24930bd141627ce56279b/pyjson5-2.0.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3dc0c2c80fd9d1e4c26f8b6c91a54d38b48648b93ae3f09355856c75fda3e460", size = 1182149, upload-time = "2026-05-15T16:10:21.696Z" },149+ { url = "https://files.pythonhosted.org/packages/90/2c/016647580d8a82ee53b4cb9ddb96deb4b157ccea9624bba61de4aa2cd25c/pyjson5-2.0.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:13d78b3e2c60b81bd7e6b00e6ab24a664ba1fd64b5d86baa65c4cdfae1fff7ae", size = 1358579, upload-time = "2026-05-15T16:10:23.649Z" },150+ { url = "https://files.pythonhosted.org/packages/01/45/8b84ff7a0c4d8c12a76e097c67af62abe986a18d1ab2540764dd6eaa0253/pyjson5-2.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a7b640dbeabbd7d975170f793f90a7500cc5d510aeae9364a1bc558bcc5336ed", size = 1211015, upload-time = "2026-05-15T16:10:25.4Z" },151+ { url = "https://files.pythonhosted.org/packages/cf/48/d8f34de7a7319f5966bdfd10f133e689ba01f138d2040bf792f1ca8b18e1/pyjson5-2.0.1-cp313-cp313t-win32.whl", hash = "sha256:46e7c5525034fde8abf3aaf100ed7660c1b618ccd3205544ed7c99e4c55cf201", size = 131660, upload-time = "2026-05-15T16:10:26.747Z" },152+ { url = "https://files.pythonhosted.org/packages/06/aa/0bd437252134115a846ffc061078c85f8e8c325c86abcaa862c134d3c57e/pyjson5-2.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:207c00e7f9641e0358bdc848a1c0610f26f2e8bd51d73bd64e5a0ab3b725f1d8", size = 157717, upload-time = "2026-05-15T16:10:28.053Z" },153+ { url = "https://files.pythonhosted.org/packages/24/61/7849b04a0dc78f73905189400ac99c45de8eec2ec451ce9c6990fb3588e5/pyjson5-2.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4917d5b6186bfa48ec4d85326e2a09769a5f7844963307ab2710429e1f743264", size = 126281, upload-time = "2026-05-15T16:10:29.378Z" },154+ { url = "https://files.pythonhosted.org/packages/89/41/70aa1cb1fb0a3ac4c9b8cd405c0c85ba935c53fdfae6271b8eae364b92d1/pyjson5-2.0.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:573fecd7cad4e24d232053f9ebf331c80fe1b0ed6e2064f7614797a7f691e7ca", size = 303706, upload-time = "2026-05-15T16:10:35.163Z" },155+ { url = "https://files.pythonhosted.org/packages/0c/d9/caf44bf3d33b9502dc4b8ed5d0c7a8af8fbe33001e82414c0407f39f18bd/pyjson5-2.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a8ade508a046a5407a322c098e3b7e6033216b158a7746eb8962b7a4cdbf9248", size = 158915, upload-time = "2026-05-15T16:10:36.496Z" },156+ { url = "https://files.pythonhosted.org/packages/3f/94/f2bab1ce2eac8f77e16dcd0a1ba39de20733a84b9961c3504af0dd68bceb/pyjson5-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4635e349bb1d1f1e854dc790f14be31b6cb198d6453848e8d86818104074f805", size = 154199, upload-time = "2026-05-15T16:10:37.902Z" },157+ { url = "https://files.pythonhosted.org/packages/82/c4/d94573ca37486af3d6a72f2582844d7d490d8e55639e0cfba371ce91442f/pyjson5-2.0.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d274697f81f143abac12ce256a06b61635c30d0f8cc11eefae7182655dac9a5", size = 186060, upload-time = "2026-05-15T16:10:39.603Z" },158+ { url = "https://files.pythonhosted.org/packages/03/cc/c42e697def319b286fdcb912939c044cc94bd1cfc7338b6dbb566f817f29/pyjson5-2.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be4e2242e55a2651fd8696cfd67ec8e04f54f4ed6c089cceea2b63763a7516d8", size = 165796, upload-time = "2026-05-15T16:10:40.967Z" },159+ { url = "https://files.pythonhosted.org/packages/3f/dd/f22a5f0e619ef22b8e32520a91bd92f815d50f9ec2d67cfe5974eab9476c/pyjson5-2.0.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af855c680feaa39cae4a44914ee2863eeae1549f37ce58069c747b3d4803999a", size = 165262, upload-time = "2026-05-15T16:10:42.391Z" },160+ { url = "https://files.pythonhosted.org/packages/99/8b/90e22ecb12d51ccdc68325b6b051002e6103cc4600a335d9ed13828acdd9/pyjson5-2.0.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bb769d90516da904e6cf0ba58f3d5830f4a5804486b5e85c5ca42ba3146d187a", size = 181608, upload-time = "2026-05-15T16:10:43.914Z" },161+ { url = "https://files.pythonhosted.org/packages/e0/ab/237f9036eed73e08cf8861466d3d1182ef1c88506bd29955740dcadb24ff/pyjson5-2.0.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6484c14e07aa46abeb3cb2ff0204a765260763ad7cf3f87f601f26b20ed607f3", size = 189582, upload-time = "2026-05-15T16:10:45.367Z" },162+ { url = "https://files.pythonhosted.org/packages/c4/3d/3df8d5f003910a9291e5f04fa178f626e3c8553a5986dc62589ca74195ff/pyjson5-2.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc03673adb544324500d79b2acfe2ced038998b2aceb564d335de9cc238cbdb0", size = 176321, upload-time = "2026-05-15T16:10:47.134Z" },163+ { url = "https://files.pythonhosted.org/packages/c0/d9/a458a54f780bafec1b44ee3c27cb3007a83fb7bd4f1c17ac6bf519fd6151/pyjson5-2.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d714daf784bec2e14fc06c0c26e4a373a6157eaeddaed2d5a7b7b6ae2aabc33", size = 171956, upload-time = "2026-05-15T16:10:49.174Z" },164+ { url = "https://files.pythonhosted.org/packages/78/d8/1010e0147c8862dc0881cf3656364d3d87a5e336d290fe4cb0b92223e14c/pyjson5-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5cf3ded356730e08b5941a16db6525efc0de26f35468ab09ad573056c4efc6e5", size = 1147679, upload-time = "2026-05-15T16:10:51.005Z" },165+ { url = "https://files.pythonhosted.org/packages/19/2f/91c8d8cdb4a2e4bbe8f14b9b57d717988e14830f2ed4a4f08d503cb5edde/pyjson5-2.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cc00d4669fb4170c28b2c9ed3cd1c2af9f21727c55d6866030cc154f31f68747", size = 1008216, upload-time = "2026-05-15T16:10:53.252Z" },166+ { url = "https://files.pythonhosted.org/packages/d7/d7/25c3f8693660a35be7bb4eb71b0d10d9a6981e986723a4881d607f0c6462/pyjson5-2.0.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a77a91c2e019476345c03cde105da44100daaabc3fa56f82f2bd9eb2ebbcb698", size = 1323856, upload-time = "2026-05-15T16:10:55.517Z" },167+ { url = "https://files.pythonhosted.org/packages/c8/25/82f0f83556ff68ec0ca1129ba9afeb14149cd421dc74823cdf10c209c95c/pyjson5-2.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9ae54e5d717982cb7a082c700ab1f7f965c6aedea6fcb003ec4bdeac4f02bf52", size = 1241860, upload-time = "2026-05-15T16:10:57.635Z" },168+ { url = "https://files.pythonhosted.org/packages/c1/01/2c4695fc06a0f3f29041a875d3bb24f904c9ae7b3d8dbfa1f8db17ce995c/pyjson5-2.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ce4f5b67b3a6fab623a31d83416b51a5b2c97cb172194f214078484bbb4783a6", size = 1178443, upload-time = "2026-05-15T16:10:59.398Z" },169+ { url = "https://files.pythonhosted.org/packages/9a/ae/c10f534037b096aca21b90017ac9d41f791572dc3ec31fcd92db8ddcd256/pyjson5-2.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f8d2e61f7bed0b40cfe5f62375581bf56d7b5b9707c9d74747b5931149d7d376", size = 1357739, upload-time = "2026-05-15T16:11:01.189Z" },170+ { url = "https://files.pythonhosted.org/packages/0c/f0/655224f78ede087aa6a295c4745423e0104dd31f67928675b1a4cea72a0a/pyjson5-2.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2930140edd1a64eb42689993c5ae317bca2e6bc785b5dc5053bd5e9d184c98ad", size = 1209542, upload-time = "2026-05-15T16:11:03.364Z" },171+ { url = "https://files.pythonhosted.org/packages/73/52/a7b2a26625136fcd0f92e17beaaae51e04923cc2e28502db354d4de061b4/pyjson5-2.0.1-cp314-cp314-win32.whl", hash = "sha256:51733f91dda897239ca10928f363ce6f4b5eadaf797e74477f6c1c3222c185a7", size = 118342, upload-time = "2026-05-15T16:11:40.009Z" },172+ { url = "https://files.pythonhosted.org/packages/3a/8e/79db426fff3a41610076989d4060cc18a6508ebd3c7877155f6709eea102/pyjson5-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:698c73aacff49ea35bbbf7f700a97785626201ea7aa2e1f1e0a4fe23788c3be2", size = 138408, upload-time = "2026-05-15T16:11:41.757Z" },173+ { url = "https://files.pythonhosted.org/packages/c7/1b/385da14c05412bca15e86551dde91959686c8bddf7e78722bdc627fa6815/pyjson5-2.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:d447e2e5756f89abfd0ad82d1438075bcbc701c1cc9279b43d24825aaac67356", size = 120905, upload-time = "2026-05-15T16:11:43.146Z" },174+ { url = "https://files.pythonhosted.org/packages/dd/04/cd69739556d304d3ea48064ea7d47e5ff7321b0032d7ad78864bceaa0cae/pyjson5-2.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5603be4f0cb9685d7c2cbd408ddec21b33f415252bee00ac7969f20b5789a1d1", size = 321150, upload-time = "2026-05-15T16:11:05.07Z" },175+ { url = "https://files.pythonhosted.org/packages/84/d4/6d98268ed07a2cac1e79634fcd3dc280d2503ade5f3c465f7aa095951444/pyjson5-2.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:87267520a256b3f2dba7f2995b745e8b3f2dd72bbf8436010a5191f7809f8ab6", size = 167540, upload-time = "2026-05-15T16:11:06.822Z" },176+ { url = "https://files.pythonhosted.org/packages/c8/c6/8855a9462bdbc8306cba62793e23cc2952dfcbcbcab8e0413f570c891361/pyjson5-2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:77e58307b74019d90aff9f2781bcffbbea3315b15fc814f8fa9b8d334b956ff4", size = 162533, upload-time = "2026-05-15T16:11:08.218Z" },177+ { url = "https://files.pythonhosted.org/packages/a6/a3/1092f68538ee71ed8027393ab5e71aa9c4a114e493e30a55e46e34c2ffb9/pyjson5-2.0.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dd1712038837342dea73ed526883e53c6e85a158242b6f574b9ad9cdd3199c7f", size = 184165, upload-time = "2026-05-15T16:11:09.639Z" },178+ { url = "https://files.pythonhosted.org/packages/46/63/bb9f42a4047284ea59064c134c88dfac3b7f5273e40fe548d6a086cea452/pyjson5-2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dba36677c77aed5d680827c65478933705ca156a34838f1250ea191ffc27662", size = 170075, upload-time = "2026-05-15T16:11:11.105Z" },179+ { url = "https://files.pythonhosted.org/packages/55/2a/0379aa5184e986d0f9af2919712c148d81928ce7d2d7950407e271b1df3b/pyjson5-2.0.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:04356d0c09e58907f60675d33859f269473479b90add5026231ef2fadba5207e", size = 164100, upload-time = "2026-05-15T16:11:12.909Z" },180+ { url = "https://files.pythonhosted.org/packages/15/d5/2087af69695c28c7fe796afc73dec7f0b0b9bc123ec329e45928d58d4705/pyjson5-2.0.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b331a0fbe2ae26f4ccb88045ebaecc00aeeeab23ff858f9a2223cedd969ec6d6", size = 184674, upload-time = "2026-05-15T16:11:14.448Z" },181+ { url = "https://files.pythonhosted.org/packages/2a/06/cf1b2744e07f1689cb0ac663e162a060b81cd358474754fe9f3bf02d5398/pyjson5-2.0.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ca55878d923ba5764254ad1c020c22b33e6161b3555d1f457b8060f2b3c6c17", size = 193047, upload-time = "2026-05-15T16:11:15.969Z" },182+ { url = "https://files.pythonhosted.org/packages/54/e6/a6b0deb6a3f393907c6d9115a7ac8e27557109204b56b05d3b57c51fc2ca/pyjson5-2.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35e18d11e4c2034b78a664affe18c153a8efbf8872cf6bed7f12dc4fb819d440", size = 178712, upload-time = "2026-05-15T16:11:17.364Z" },183+ { url = "https://files.pythonhosted.org/packages/6e/48/9e5c16daba56dfdb481baab85d1197e14ae3f403902fc23cdca14d14351b/pyjson5-2.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b710a7489b6e134890eaa1fdb285e9644a1e730c1cfcf1cd90a87859b87a84a9", size = 175642, upload-time = "2026-05-15T16:11:18.781Z" },184+ { url = "https://files.pythonhosted.org/packages/c3/32/f07d4dbd8cf733f9e3f3e9c9abedfbc23da1e5cfa7fed46aebae07ca6f25/pyjson5-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:08920c0dd6aba3fb6bc6c849e6f05731c5fb2716cd651de424ff60cdd12eae65", size = 1152219, upload-time = "2026-05-15T16:11:20.54Z" },185+ { url = "https://files.pythonhosted.org/packages/01/9d/0ddbb89d381bc27f3740850d5d664c1f6f76620ad879ff5eb2506a4dfd6c/pyjson5-2.0.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:e166dadb3275025cff5ee8602131372a3ea171c6727b3fc6e44697348e3972c5", size = 1013305, upload-time = "2026-05-15T16:11:22.702Z" },186+ { url = "https://files.pythonhosted.org/packages/bb/93/0f44886391dd2249ad9cf98c2deb3f26d1a115dd5a215629af7455765968/pyjson5-2.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ea3c2b4e7b8e209e7f59bd6a925d79b69cd0a4ffa12f2df0c6af55514c8b2227", size = 1322840, upload-time = "2026-05-15T16:11:24.918Z" },187+ { url = "https://files.pythonhosted.org/packages/d4/29/c478dc24dfcfc07e407ff8669df265ecc51fed5e7d47e51a01f17fd1c53f/pyjson5-2.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b710fc8ce00c984c8865131f05a8536163e1d3caffb9d081647879eca7424670", size = 1245221, upload-time = "2026-05-15T16:11:26.873Z" },188+ { url = "https://files.pythonhosted.org/packages/e2/f0/5fef8c47c5b7052da79af6425a021040402a9a09b37c5687d91f98696a27/pyjson5-2.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9784d79275d1b06d95f2920c3f260ce5d3f6671f65a1fae1464f83fbfc834a0", size = 1182991, upload-time = "2026-05-15T16:11:29.258Z" },189+ { url = "https://files.pythonhosted.org/packages/90/33/4d3a9d3159cdff1f1886d39f1991e6c8d5927f9b606cfc9743b5bef98c2a/pyjson5-2.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0ef86c53d0e14991b5b0fe58f225e3d8faec437aa0a70da4762525b6bc2fca10", size = 1359104, upload-time = "2026-05-15T16:11:31.651Z" },190+ { url = "https://files.pythonhosted.org/packages/0f/04/20eb16c52453aea0af68a697f4058378c9ff871bd2f0bea28eb76ffe12f8/pyjson5-2.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1d9faccf5a9e86f14104eec31c13428ef8baeb867182ce107e0c68fcc5c62477", size = 1212847, upload-time = "2026-05-15T16:11:33.767Z" },191+ { url = "https://files.pythonhosted.org/packages/b9/a3/69bbe93275eafb7802a5b29230cc5c28f9b924c68d15b3ab2d43cfe19825/pyjson5-2.0.1-cp314-cp314t-win32.whl", hash = "sha256:2df497af7ab03cf82d9278a4707a83d0ae4e6d727f919a883b5ccec3f7d92650", size = 139137, upload-time = "2026-05-15T16:11:35.487Z" },192+ { url = "https://files.pythonhosted.org/packages/48/45/b865ca6e0ae6887bc2b6b17cd123470ae43ac0fa04a2362d77d4b9f5bd43/pyjson5-2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:3b31cf4f4a4f01800812865af3b02f6700cefb9377e4d9a9c6b78fdcf41fd973", size = 169412, upload-time = "2026-05-15T16:11:36.887Z" },193+ { url = "https://files.pythonhosted.org/packages/68/55/02c734459fef0a955ab3e7e745df415d5f9fc873a4c288765f60f22fbc13/pyjson5-2.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:15f0d8baea89d35c6c01c60b944c778a79cac10f77f71b727f1b244e2c7a8ccb", size = 129061, upload-time = "2026-05-15T16:11:38.712Z" },194+]195+196+[[package]]197+name = "pyodide-cli"198+version = "0.5.0"199+source = { registry = "https://pypi.org/simple" }200+dependencies = [201+ { name = "click" },202+ { name = "rich" },203+]204+sdist = { url = "https://files.pythonhosted.org/packages/f1/44/fe74049e3e776bd56fae8bd9370900d6d6dc5e541299ae90cb2330a9a8a4/pyodide_cli-0.5.0.tar.gz", hash = "sha256:14df0797e791558b6976d4612f9df9619334f8914bfe3efbdd22e3886f5275b7", size = 12630, upload-time = "2026-01-28T03:29:06.623Z" }205+wheels = [206+ { url = "https://files.pythonhosted.org/packages/4a/07/76977bbd73c686df55fbc3685c6c336158dc11b2ba155abe3e3a802447b2/pyodide_cli-0.5.0-py3-none-any.whl", hash = "sha256:24c02736f1d6c99396f5e9330fd30bd98db88cf49ef059b62b025fa1f5f6e29c", size = 12684, upload-time = "2026-01-28T03:29:05.527Z" },207+]208+209+[[package]]210+name = "pyodide-py"211+version = "0.29.4"212+source = { registry = "https://pypi.org/simple" }213+resolution-markers = [214+ "python_full_version < '3.14'",215+]216+sdist = { url = "https://files.pythonhosted.org/packages/57/85/db25e6a10a2600cb96e4ebe423ada255aa738b991fd8843776ebe4f3173f/pyodide_py-0.29.4.tar.gz", hash = "sha256:2ef83817457364fd1a3dc42bfd0617350244776c8b9f7e37f17efcbc38e3bf0a", size = 58868, upload-time = "2026-05-07T15:14:45.97Z" }217+wheels = [218+ { url = "https://files.pythonhosted.org/packages/b2/03/7805e80a7d9824b03fe40b1c7c4192250f1ea231ee49a57ada62f736c506/pyodide_py-0.29.4-py3-none-any.whl", hash = "sha256:5df0615640e39d3b365da8b8fd653ec0af5361aef28275ded2218af773d766d1", size = 66013, upload-time = "2026-05-07T15:14:44.637Z" },219+]220+221+[[package]]222+name = "pyodide-py"223+version = "314.0.5"224+source = { registry = "https://pypi.org/simple" }225+resolution-markers = [226+ "python_full_version >= '3.14'",227+]228+sdist = { url = "https://files.pythonhosted.org/packages/95/ff/22556c4c96d2ddb5e2d14f5f36182b1cc900bf6f8f277a887b923d13adc9/pyodide_py-314.0.5.tar.gz", hash = "sha256:4e8cb554205e3237eee60ded3665014eef62fac64a8ccb412eba277ec01cc857", size = 62814, upload-time = "2026-08-17T06:54:49.481Z" }229+wheels = [230+ { url = "https://files.pythonhosted.org/packages/30/d3/8cc37a4df9a8214643066b37443dd878d6dc779a5b9d8e1eff5dc7b7f61c/pyodide_py-314.0.5-py3-none-any.whl", hash = "sha256:030b70fb2d370a898f5416119d5329faf720d54393d6b9917fcb43a332084f51", size = 70396, upload-time = "2026-08-17T06:54:48.257Z" },231+]232+233+[[package]]234+name = "pytest"235+version = "8.4.2"236+source = { registry = "https://pypi.org/simple" }237+dependencies = [238+ { name = "colorama", marker = "sys_platform == 'win32'" },239+ { name = "iniconfig" },240+ { name = "packaging" },241+ { name = "pluggy" },242+ { name = "pygments" },243+]244+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }245+wheels = [246+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },247+]248+249+[[package]]250+name = "rich"251+version = "15.0.0"252+source = { registry = "https://pypi.org/simple" }253+dependencies = [254+ { name = "markdown-it-py" },255+ { name = "pygments" },256+]257+sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }258+wheels = [259+ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },260+]261+262+[[package]]263+name = "workers-py"264+version = "1.16.5"265+source = { registry = "https://pypi.org/simple" }266+dependencies = [267+ { name = "click" },268+ { name = "pyjson5" },269+ { name = "pyodide-cli" },270+ { name = "pyodide-py", version = "0.29.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },271+ { name = "pyodide-py", version = "314.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },272+ { name = "rich" },273+ { name = "workers-runtime-sdk" },274+]275+sdist = { url = "https://files.pythonhosted.org/packages/4d/5e/382cef4f005ebd19014b6ef5f088fcdb93037e86fd6f8bb0a4656b0a7610/workers_py-1.16.5.tar.gz", hash = "sha256:06496685290cdc4eae4175d0ef968b9d7ec12ef2d3db118ad2716fe154af3af9", size = 69010, upload-time = "2026-08-19T04:41:07.317Z" }276+wheels = [277+ { url = "https://files.pythonhosted.org/packages/7e/55/25b5c5e798f2581be9cd5c87b3c2c588ed93b71ee7c8c53863309621b460/workers_py-1.16.5-py3-none-any.whl", hash = "sha256:5fd6814bb502dc2c42f0bb3fab398e12dc0a8c20e4d42a2a90cd6fbda0e5bca5", size = 16939, upload-time = "2026-08-19T04:41:06.161Z" },278+]279+280+[[package]]281+name = "workers-runtime-sdk"282+version = "1.6.14"283+source = { registry = "https://pypi.org/simple" }284+sdist = { url = "https://files.pythonhosted.org/packages/cd/20/e2ecb82b0c7532437a07fa13369b83340573aa0167c58cca401e3c1ff696/workers_runtime_sdk-1.6.14.tar.gz", hash = "sha256:0905ac15a1d0bd00bfaac4762721f3c46b218aa0f8d6a2aab9b91d4bb76e3d3c", size = 128807, upload-time = "2026-08-19T14:54:25.917Z" }285+wheels = [286+ { url = "https://files.pythonhosted.org/packages/fa/95/960a9a940825c52eae52c316e8f23f91f9d92c5f0e3bb5400097c43c2747/workers_runtime_sdk-1.6.14-py3-none-any.whl", hash = "sha256:c66a7278a93198b36fa3f1d61e5ac071e9a0d5e57a3f4fadcab9cc5107e587b4", size = 34805, upload-time = "2026-08-19T14:54:24.674Z" },287+]
A diagramzip-python-units/symbolator/wrangler.jsonc +12 -0
M diagramzip-render/README.md +10 -8
@@ -43,8 +43,9 @@ bundles, CSPs, deployments, and subdomains.
4343 A unit may expose multiple catalog hostnames when those engines share the same4444 dependency stack. BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag, and RackDiag4545 share `blockdiag-family`; GraphViz, DBML, and the ERD-to-DOT translator share46-`graphviz-family`; PlantUML and C4 PlantUML share `plantuml-family`; Vega and47-Vega-Lite share `vega-family`. Requests are still selected only by a dedicated46+`graphviz-family`; PlantUML and C4 PlantUML share `plantuml-family`; Svgbob and47+the bounded Ditaa translation share `svgbob-family`; Vega and Vega-Lite share48+`vega-family`. Requests are still selected only by a dedicated4849 hostname, so a caller cannot choose an unrelated engine in the body.4950 5051 A unit may translate into another renderer. Translation is declared as an
@@ -59,12 +60,12 @@ catalog engine.
5960 | Runtime | Engines |6061 | --- | --- |6162 | Worker JavaScript | Bytefield, Nomnoml, Vega family (Vega and Vega-Lite → Vega), WaveDrom |62-| Worker Python | BlockDiag family (BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag, RackDiag); WireViz → DOT → GraphViz |63-| Worker WebAssembly | PlantUML family (PlantUML and bounded C4 → PlantUML); GraphViz family (GraphViz, DBML → DOT → GraphViz, and ERD → DOT → GraphViz); GoAT; Pikchr; Svgbob |63+| Worker Python | BlockDiag family (BlockDiag, SeqDiag, ActDiag, NwDiag, PacketDiag, RackDiag); Symbolator; WireViz → DOT → GraphViz |64+| Worker WebAssembly | PlantUML family (PlantUML and bounded C4 → PlantUML); GraphViz family (GraphViz, DBML → DOT → GraphViz, and ERD → DOT → GraphViz); D2; GoAT; Pikchr; Svgbob family (Svgbob and bounded Ditaa → Svgbob) |6465 | Sandboxed browser unit | Mermaid, BPMN, Excalidraw, diagrams.net |65-| Compatibility unit | 6 dependency units covering the remaining 6 engines |66+| Compatibility unit | 2 dependency units covering the remaining 2 engines |6667 67-The current split is 24/30 engines off Fly and 6/30 still dependent on it.68+The current split is 28/30 engines off Fly and 2/30 still dependent on it.6869 Compatibility units are extraction seams, not final runtimes or fallbacks for6970 an engine after cutover. DBML retains the upstream parser/checker/DOT model in7071 an explicitly rebuilt Worker bundle, then shares the GraphViz-Wasm runtime.
@@ -78,8 +79,9 @@ PlantUML runs its pinned TeaVM browser core with a precompiled GraphViz module;
7879 C4-PlantUML lowers a bounded, documented macro surface into ordinary PlantUML7980 inside the same dependency unit.8081 81-All returned SVG passes through the same sanitizer. Scripts, event handlers,82-external resources, and active embedded HTML are removed. Mermaid's82+Renderer output is either generated by a bounded unit or passed through the83+shared sanitizer. Scripts, event handlers, external resources, and active84+embedded HTML are not allowed. Mermaid's8385 `foreignObject` labels retain only a narrow formatting allowlist, with links and8486 resource-loading elements stripped. Any observed engine-specific loss belongs8587 in the catalog.
A diagramzip-render/d2-wasm/d2-custom.wasm +0 -0

Binary file.

A diagramzip-render/d2-wasm/provenance.md +13 -0
A diagramzip-render/d2-wasm/source/go.mod +46 -0
@@ -0,0 +1,46 @@
1+module d2-worker-spike2+3+go 1.26.64+5+require (6+ github.com/PuerkitoBio/goquery v1.10.0 // indirect7+ github.com/alecthomas/chroma/v2 v2.14.0 // indirect8+ github.com/andybalholm/brotli v1.2.0 // indirect9+ github.com/andybalholm/cascadia v1.3.2 // indirect10+ github.com/coder/websocket v1.8.12 // indirect11+ github.com/deckarep/golang-set/v2 v2.6.0 // indirect12+ github.com/dlclark/regexp2 v1.11.4 // indirect13+ github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 // indirect14+ github.com/dsoprea/go-exif/v3 v3.0.1 // indirect15+ github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect16+ github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d // indirect17+ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect18+ github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // indirect19+ github.com/fsnotify/fsnotify v1.7.1-0.20240403050945-7086bea086b7 // indirect20+ github.com/go-errors/errors v1.5.1 // indirect21+ github.com/go-jose/go-jose/v3 v3.0.3 // indirect22+ github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect23+ github.com/go-stack/stack v1.8.1 // indirect24+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect25+ github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect26+ github.com/google/pprof v0.0.0-20240927180334-d43a67379298 // indirect27+ github.com/jung-kurt/gofpdf v1.16.2 // indirect28+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect29+ github.com/mazznoer/csscolorparser v0.1.5 // indirect30+ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect31+ github.com/playwright-community/playwright-go v0.4702.0 // indirect32+ github.com/rivo/uniseg v0.4.7 // indirect33+ github.com/spf13/pflag v1.0.5 // indirect34+ github.com/yuin/goldmark v1.7.4 // indirect35+ go.uber.org/multierr v1.11.0 // indirect36+ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect37+ golang.org/x/image v0.20.0 // indirect38+ golang.org/x/net v0.35.0 // indirect39+ golang.org/x/sys v0.30.0 // indirect40+ golang.org/x/term v0.29.0 // indirect41+ golang.org/x/text v0.22.0 // indirect42+ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect43+ gopkg.in/yaml.v2 v2.4.0 // indirect44+ oss.terrastruct.com/d2 v0.7.1 // indirect45+ oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a // indirect46+)
A diagramzip-render/d2-wasm/source/go.sum +169 -0
@@ -0,0 +1,169 @@
1+github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=2+github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=3+github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=4+github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=5+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=6+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=7+github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=8+github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=9+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=10+github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=11+github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=12+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=13+github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=14+github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=15+github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=16+github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=17+github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 h1:Ux9RXuPQmTB4C1MKagNLme0krvq8ulewfor+ORO/QL4=18+github.com/dop251/goja v0.0.0-20240927123429-241b342198c2/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=19+github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=20+github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=21+github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=22+github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=23+github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No=24+github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0=25+github.com/dsoprea/go-exif/v3 v3.0.1 h1:/IE4iW7gvY7BablV1XY0unqhMv26EYpOquVMwoBo/wc=26+github.com/dsoprea/go-exif/v3 v3.0.1/go.mod h1:10HkA1Wz3h398cDP66L+Is9kKDmlqlIJGPv8pk4EWvc=27+github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=28+github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=29+github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=30+github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=31+github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d h1:2zNIgrJTspLxUKoJGl0Ln24+hufPKSjP3cu4++5MeSE=32+github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d/go.mod h1:scnx0wQSM7UiCMK66dSdiPZvL2hl6iF5DvpZ7uT59MY=33+github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=34+github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=35+github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc=36+github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=37+github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=38+github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=39+github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 h1:BBade+JlV/f7JstZ4pitd4tHhpN+w+6I+LyOS7B4fyU=40+github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4/go.mod h1:H7chHJglrhPPzetLdzBleF8d22WYOv7UM/lEKYiwlKM=41+github.com/fsnotify/fsnotify v1.7.1-0.20240403050945-7086bea086b7 h1:5ZeiG5gIjLqPKLl+f5zv++9ZO2oxA6hmZ3e7G0mMW1M=42+github.com/fsnotify/fsnotify v1.7.1-0.20240403050945-7086bea086b7/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=43+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=44+github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=45+github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=46+github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=47+github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=48+github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=49+github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=50+github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=51+github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=52+github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=53+github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=54+github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=55+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=56+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=57+github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=58+github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=59+github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=60+github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=61+github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=62+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=63+github.com/google/pprof v0.0.0-20240927180334-d43a67379298 h1:dMHbguTqGtorivvHTaOnbYp+tFzrw5M9gjkU4lCplgg=64+github.com/google/pprof v0.0.0-20240927180334-d43a67379298/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=65+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=66+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=67+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=68+github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=69+github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=70+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=71+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=72+github.com/mazznoer/csscolorparser v0.1.5 h1:Wr4uNIE+pHWN3TqZn2SGpA2nLRG064gB7WdSfSS5cz4=73+github.com/mazznoer/csscolorparser v0.1.5/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I=74+github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=75+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=76+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=77+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=78+github.com/playwright-community/playwright-go v0.4702.0 h1:3CwNpk4RoA42tyhmlgPDMxYEYtMydaeEqMYiW0RNlSY=79+github.com/playwright-community/playwright-go v0.4702.0/go.mod h1:bpArn5TqNzmP0jroCgw4poSOG9gSeQg490iLqWAaa7w=80+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=81+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=82+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=83+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=84+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=85+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=86+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=87+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=88+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=89+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=90+github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=91+github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=92+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=93+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=94+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=95+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=96+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=97+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=98+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=99+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=100+golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=101+golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=102+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=103+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=104+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=105+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=106+golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=107+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=108+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=109+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=110+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=111+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=112+golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=113+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=114+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=115+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=116+golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=117+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=118+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=119+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=120+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=121+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=122+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=123+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=124+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=125+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=126+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=127+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=128+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=129+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=130+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=131+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=132+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=133+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=134+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=135+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=136+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=137+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=138+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=139+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=140+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=141+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=142+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=143+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=144+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=145+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=146+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=147+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=148+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=149+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=150+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=151+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=152+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=153+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=154+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=155+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=156+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=157+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=158+golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=159+golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=160+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=161+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=162+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=163+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=164+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=165+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=166+oss.terrastruct.com/d2 v0.7.1 h1:LafTW1UoXJGODvKDZ8obyBfGcc2k2vHZ3EzrabMqEVE=167+oss.terrastruct.com/d2 v0.7.1/go.mod h1:aT0PwLaxBZGgsWrIT8oSFYm5xoYX08BaOHewi5qLE2E=168+oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a h1:UXF/Z9i9tOx/wqGUOn/T12wZeez1Gg0sAVKKl7YUDwM=169+oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4=
A diagramzip-render/d2-wasm/source/main.go +62 -0
@@ -0,0 +1,62 @@
1+//go:build !js || !wasm2+3+package main4+5+import (6+ "context"7+ "fmt"8+ "os"9+10+ "oss.terrastruct.com/d2/d2graph"11+ "oss.terrastruct.com/d2/d2lib"12+ "oss.terrastruct.com/d2/d2renderers/d2svg"13+ "oss.terrastruct.com/d2/lib/geo"14+ "oss.terrastruct.com/d2/lib/textmeasure"15+)16+17+func gridLayout(ctx context.Context, g *d2graph.Graph) error {18+ if err := ctx.Err(); err != nil { return err }19+ const cols = 420+ const gap = 80.021+ for i, o := range g.Objects {22+ if o == g.Root || o.Box == nil { continue }23+ o.Box.TopLeft = geo.NewPoint(float64(i%cols)*220, float64(i/cols)*160)24+ }25+ for _, e := range g.Edges {26+ if e.Src != nil && e.Dst != nil && e.Src.Box != nil && e.Dst.Box != nil {27+ e.Route = []*geo.Point{e.Src.Box.Center(), e.Dst.Box.Center()}28+ }29+ }30+ return nil31+}32+33+func main() {34+ input := "x -> y\nsteps: {\n 1: {\n x -> z\n }\n}"35+ layout := "grid"36+ pad := int64(50)37+ ruler, err := textmeasure.NewRuler()38+ if err != nil { panic(err) }39+ d, graph, err := d2lib.Compile(context.Background(), input, &d2lib.CompileOptions{40+ Layout: &layout,41+ Ruler: ruler,42+ LayoutResolver: func(engine string) (d2graph.LayoutGraph, error) {43+ if engine != "grid" { return nil, fmt.Errorf("unexpected layout %q", engine) }44+ return gridLayout, nil45+ },46+ RouterResolver: func(engine string) (d2graph.RouteEdges, error) {47+ return func(ctx context.Context, g *d2graph.Graph, edges []*d2graph.Edge) error {48+ for _, e := range edges {49+ if e.Src == nil || e.Dst == nil || e.Src.Box == nil || e.Dst.Box == nil { continue }50+ e.Route = []*geo.Point{e.Src.Box.Center(), e.Dst.Box.Center()}51+ }52+ return nil53+ }, nil54+ },55+ }, &d2svg.RenderOpts{Pad: &pad})56+ if err != nil { panic(err) }57+ fmt.Printf("shapes=%d connections=%d\n", len(d.Shapes), len(d.Connections))58+ for _, o := range graph.Objects { fmt.Printf("obj=%s box=%v\n", o.ID, o.Box) }59+ for _, c := range d.Connections { fmt.Printf("route=%d src=%s dst=%s\n", len(c.Route), c.Src, c.Dst) }60+ if err := os.WriteFile("/tmp/d2-custom.svg", func() []byte { b, e := d2svg.Render(d, &d2svg.RenderOpts{Pad: &pad}); if e != nil { panic(e) }; return b }(), 0644); err != nil { panic(err) }61+ fmt.Printf("layers=%d scenarios=%d steps=%d\n", len(d.Layers), len(d.Scenarios), len(d.Steps))62+}
A diagramzip-render/d2-wasm/source/worker.go +130 -0
@@ -0,0 +1,130 @@
1+//go:build js && wasm2+3+package main4+5+import (6+ "bytes"7+ "context"8+ "encoding/json"9+ "fmt"10+ "runtime/debug"11+ "syscall/js"12+13+ "oss.terrastruct.com/util-go/go2"14+15+ "oss.terrastruct.com/d2/d2graph"16+ "oss.terrastruct.com/d2/d2lib"17+ "oss.terrastruct.com/d2/d2renderers/d2animate"18+ "oss.terrastruct.com/d2/d2renderers/d2svg"19+ "oss.terrastruct.com/d2/lib/geo"20+ "oss.terrastruct.com/d2/lib/label"21+ d2log "oss.terrastruct.com/d2/lib/log"22+ "oss.terrastruct.com/d2/lib/textmeasure"23+)24+25+func gridLayout(ctx context.Context, g *d2graph.Graph) error {26+ if err := ctx.Err(); err != nil {27+ return err28+ }29+ for i, o := range g.Objects {30+ if o == g.Root || o.Box == nil {31+ continue32+ }33+ if o.HasLabel() && o.LabelPosition == nil {34+ o.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())35+ }36+ if o.HasIcon() && o.IconPosition == nil {37+ o.IconPosition = go2.Pointer(label.InsideMiddleCenter.String())38+ }39+ o.Box.TopLeft = geo.NewPoint(float64(i%4)*220, float64(i/4)*160)40+ }41+ for _, e := range g.Edges {42+ if e.Src != nil && e.Dst != nil && e.Src.Box != nil && e.Dst.Box != nil {43+ e.Route = []*geo.Point{e.Src.Box.Center(), e.Dst.Box.Center()}44+ e.TraceToShape(e.Route, 0, len(e.Route)-1)45+ if e.Label.Value != "" {46+ e.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())47+ }48+ }49+ }50+ return nil51+}52+53+type response struct {54+ SVG string `json:"svg"`55+ Boards []string `json:"boards"`56+}57+type renderOptions struct { AnimateInterval int64 `json:"animateInterval"` }58+59+func straightRouter(ctx context.Context, g *d2graph.Graph, edges []*d2graph.Edge) error {60+ if err := ctx.Err(); err != nil {61+ return err62+ }63+ for _, e := range edges {64+ if e.Src == nil || e.Dst == nil || e.Src.Box == nil || e.Dst.Box == nil {65+ continue66+ }67+ e.Route = []*geo.Point{e.Src.Box.Center(), e.Dst.Box.Center()}68+ e.TraceToShape(e.Route, 0, len(e.Route)-1)69+ if e.Label.Value != "" {70+ e.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())71+ }72+ }73+ return nil74+}75+76+func render(this js.Value, args []js.Value) (result any) {77+ defer func() {78+ if recovered := recover(); recovered != nil {79+ fmt.Printf("D2 renderer panic: %v\n%s\n", recovered, debug.Stack())80+ result = js.ValueOf(fmt.Sprintf(`{"error":%q}`, fmt.Sprintf("D2 renderer panic: %v", recovered)))81+ }82+ }()83+ if len(args) < 1 || len(args) > 2 {84+ return js.ValueOf(`{"error":"expected source string"}`)85+ }86+ var inputOptions renderOptions87+ if len(args) == 2 && args[1].String() != "" {88+ if err := json.Unmarshal([]byte(args[1].String()), &inputOptions); err != nil { return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error())) }89+ }90+ ruler, err := textmeasure.NewRuler()91+ if err != nil {92+ return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))93+ }94+ layout := "grid"95+ pad := int64(100)96+ ctx := d2log.WithDefault(context.Background())97+ d, _, err := d2lib.Compile(ctx, args[0].String(), &d2lib.CompileOptions{98+ Layout: &layout, Ruler: ruler,99+ LayoutResolver: func(string) (d2graph.LayoutGraph, error) { return gridLayout, nil },100+ RouterResolver: func(string) (d2graph.RouteEdges, error) { return straightRouter, nil },101+ }, &d2svg.RenderOpts{Pad: &pad})102+ if err != nil {103+ return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))104+ }105+ opts := &d2svg.RenderOpts{Pad: &pad}106+ boards, err := d2svg.RenderMultiboard(d, opts)107+ if err != nil {108+ return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))109+ }110+ base := boards[0]111+ if inputOptions.AnimateInterval > 0 {112+ base, err = d2animate.Wrap(d, boards, *opts, int(inputOptions.AnimateInterval))113+ if err != nil { return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error())) }114+ // The wrapper is itself the XML document; nested board declarations would115+ // otherwise be rejected by the shared SVG sanitizer as extra roots.116+ base = bytes.ReplaceAll(base, []byte(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>`), nil)117+ base = bytes.ReplaceAll(base, []byte(`<?xml version="1.0" encoding="utf-8"?>`), nil)118+ }119+ out := response{SVG: string(base)}120+ for _, b := range boards {121+ out.Boards = append(out.Boards, string(b))122+ }123+ b, _ := json.Marshal(out)124+ return js.ValueOf(string(b))125+}126+127+func main() {128+ js.Global().Set("d2CustomRender", js.FuncOf(render))129+ select {}130+}
A diagramzip-render/d2-wasm/wasm_exec.js +575 -0
@@ -0,0 +1,575 @@
1+// Copyright 2018 The Go Authors. All rights reserved.2+// Use of this source code is governed by a BSD-style3+// license that can be found in the LICENSE file.4+5+"use strict";6+7+(() => {8+ const enosys = () => {9+ const err = new Error("not implemented");10+ err.code = "ENOSYS";11+ return err;12+ };13+14+ if (!globalThis.fs) {15+ let outputBuf = "";16+ globalThis.fs = {17+ constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused18+ writeSync(fd, buf) {19+ outputBuf += decoder.decode(buf);20+ const nl = outputBuf.lastIndexOf("\n");21+ if (nl != -1) {22+ console.log(outputBuf.substring(0, nl));23+ outputBuf = outputBuf.substring(nl + 1);24+ }25+ return buf.length;26+ },27+ write(fd, buf, offset, length, position, callback) {28+ if (offset !== 0 || length !== buf.length || position !== null) {29+ callback(enosys());30+ return;31+ }32+ const n = this.writeSync(fd, buf);33+ callback(null, n);34+ },35+ chmod(path, mode, callback) { callback(enosys()); },36+ chown(path, uid, gid, callback) { callback(enosys()); },37+ close(fd, callback) { callback(enosys()); },38+ fchmod(fd, mode, callback) { callback(enosys()); },39+ fchown(fd, uid, gid, callback) { callback(enosys()); },40+ fstat(fd, callback) { callback(enosys()); },41+ fsync(fd, callback) { callback(null); },42+ ftruncate(fd, length, callback) { callback(enosys()); },43+ lchown(path, uid, gid, callback) { callback(enosys()); },44+ link(path, link, callback) { callback(enosys()); },45+ lstat(path, callback) { callback(enosys()); },46+ mkdir(path, perm, callback) { callback(enosys()); },47+ open(path, flags, mode, callback) { callback(enosys()); },48+ read(fd, buffer, offset, length, position, callback) { callback(enosys()); },49+ readdir(path, callback) { callback(enosys()); },50+ readlink(path, callback) { callback(enosys()); },51+ rename(from, to, callback) { callback(enosys()); },52+ rmdir(path, callback) { callback(enosys()); },53+ stat(path, callback) { callback(enosys()); },54+ symlink(path, link, callback) { callback(enosys()); },55+ truncate(path, length, callback) { callback(enosys()); },56+ unlink(path, callback) { callback(enosys()); },57+ utimes(path, atime, mtime, callback) { callback(enosys()); },58+ };59+ }60+61+ if (!globalThis.process) {62+ globalThis.process = {63+ getuid() { return -1; },64+ getgid() { return -1; },65+ geteuid() { return -1; },66+ getegid() { return -1; },67+ getgroups() { throw enosys(); },68+ pid: -1,69+ ppid: -1,70+ umask() { throw enosys(); },71+ cwd() { throw enosys(); },72+ chdir() { throw enosys(); },73+ }74+ }75+76+ if (!globalThis.path) {77+ globalThis.path = {78+ resolve(...pathSegments) {79+ return pathSegments.join("/");80+ }81+ }82+ }83+84+ if (!globalThis.crypto) {85+ throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");86+ }87+88+ if (!globalThis.performance) {89+ throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");90+ }91+92+ if (!globalThis.TextEncoder) {93+ throw new Error("globalThis.TextEncoder is not available, polyfill required");94+ }95+96+ if (!globalThis.TextDecoder) {97+ throw new Error("globalThis.TextDecoder is not available, polyfill required");98+ }99+100+ const encoder = new TextEncoder("utf-8");101+ const decoder = new TextDecoder("utf-8");102+103+ globalThis.Go = class {104+ constructor() {105+ this.argv = ["js"];106+ this.env = {};107+ this.exit = (code) => {108+ if (code !== 0) {109+ console.warn("exit code:", code);110+ }111+ };112+ this._exitPromise = new Promise((resolve) => {113+ this._resolveExitPromise = resolve;114+ });115+ this._pendingEvent = null;116+ this._scheduledTimeouts = new Map();117+ this._nextCallbackTimeoutID = 1;118+119+ const setInt64 = (addr, v) => {120+ this.mem.setUint32(addr + 0, v, true);121+ this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);122+ }123+124+ const setInt32 = (addr, v) => {125+ this.mem.setUint32(addr + 0, v, true);126+ }127+128+ const getInt64 = (addr) => {129+ const low = this.mem.getUint32(addr + 0, true);130+ const high = this.mem.getInt32(addr + 4, true);131+ return low + high * 4294967296;132+ }133+134+ const loadValue = (addr) => {135+ const f = this.mem.getFloat64(addr, true);136+ if (f === 0) {137+ return undefined;138+ }139+ if (!isNaN(f)) {140+ return f;141+ }142+143+ const id = this.mem.getUint32(addr, true);144+ return this._values[id];145+ }146+147+ const storeValue = (addr, v) => {148+ const nanHead = 0x7FF80000;149+150+ if (typeof v === "number" && v !== 0) {151+ if (isNaN(v)) {152+ this.mem.setUint32(addr + 4, nanHead, true);153+ this.mem.setUint32(addr, 0, true);154+ return;155+ }156+ this.mem.setFloat64(addr, v, true);157+ return;158+ }159+160+ if (v === undefined) {161+ this.mem.setFloat64(addr, 0, true);162+ return;163+ }164+165+ let id = this._ids.get(v);166+ if (id === undefined) {167+ id = this._idPool.pop();168+ if (id === undefined) {169+ id = this._values.length;170+ }171+ this._values[id] = v;172+ this._goRefCounts[id] = 0;173+ this._ids.set(v, id);174+ }175+ this._goRefCounts[id]++;176+ let typeFlag = 0;177+ switch (typeof v) {178+ case "object":179+ if (v !== null) {180+ typeFlag = 1;181+ }182+ break;183+ case "string":184+ typeFlag = 2;185+ break;186+ case "symbol":187+ typeFlag = 3;188+ break;189+ case "function":190+ typeFlag = 4;191+ break;192+ }193+ this.mem.setUint32(addr + 4, nanHead | typeFlag, true);194+ this.mem.setUint32(addr, id, true);195+ }196+197+ const loadSlice = (addr) => {198+ const array = getInt64(addr + 0);199+ const len = getInt64(addr + 8);200+ return new Uint8Array(this._inst.exports.mem.buffer, array, len);201+ }202+203+ const loadSliceOfValues = (addr) => {204+ const array = getInt64(addr + 0);205+ const len = getInt64(addr + 8);206+ const a = new Array(len);207+ for (let i = 0; i < len; i++) {208+ a[i] = loadValue(array + i * 8);209+ }210+ return a;211+ }212+213+ const loadString = (addr) => {214+ const saddr = getInt64(addr + 0);215+ const len = getInt64(addr + 8);216+ return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));217+ }218+219+ const testCallExport = (a, b) => {220+ this._inst.exports.testExport0();221+ return this._inst.exports.testExport(a, b);222+ }223+224+ const timeOrigin = Date.now() - performance.now();225+ this.importObject = {226+ _gotest: {227+ add: (a, b) => a + b,228+ callExport: testCallExport,229+ },230+ gojs: {231+ // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)232+ // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported233+ // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).234+ // This changes the SP, thus we have to update the SP used by the imported function.235+236+ // func wasmExit(code int32)237+ "runtime.wasmExit": (sp) => {238+ sp >>>= 0;239+ const code = this.mem.getInt32(sp + 8, true);240+ this.exited = true;241+ delete this._inst;242+ delete this._values;243+ delete this._goRefCounts;244+ delete this._ids;245+ delete this._idPool;246+ this.exit(code);247+ },248+249+ // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)250+ "runtime.wasmWrite": (sp) => {251+ sp >>>= 0;252+ const fd = getInt64(sp + 8);253+ const p = getInt64(sp + 16);254+ const n = this.mem.getInt32(sp + 24, true);255+ fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));256+ },257+258+ // func resetMemoryDataView()259+ "runtime.resetMemoryDataView": (sp) => {260+ sp >>>= 0;261+ this.mem = new DataView(this._inst.exports.mem.buffer);262+ },263+264+ // func nanotime1() int64265+ "runtime.nanotime1": (sp) => {266+ sp >>>= 0;267+ setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);268+ },269+270+ // func walltime() (sec int64, nsec int32)271+ "runtime.walltime": (sp) => {272+ sp >>>= 0;273+ const msec = (new Date).getTime();274+ setInt64(sp + 8, msec / 1000);275+ this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);276+ },277+278+ // func scheduleTimeoutEvent(delay int64) int32279+ "runtime.scheduleTimeoutEvent": (sp) => {280+ sp >>>= 0;281+ const id = this._nextCallbackTimeoutID;282+ this._nextCallbackTimeoutID++;283+ this._scheduledTimeouts.set(id, setTimeout(284+ () => {285+ this._resume();286+ while (this._scheduledTimeouts.has(id)) {287+ // for some reason Go failed to register the timeout event, log and try again288+ // (temporary workaround for https://github.com/golang/go/issues/28975)289+ console.warn("scheduleTimeoutEvent: missed timeout event");290+ this._resume();291+ }292+ },293+ getInt64(sp + 8),294+ ));295+ this.mem.setInt32(sp + 16, id, true);296+ },297+298+ // func clearTimeoutEvent(id int32)299+ "runtime.clearTimeoutEvent": (sp) => {300+ sp >>>= 0;301+ const id = this.mem.getInt32(sp + 8, true);302+ clearTimeout(this._scheduledTimeouts.get(id));303+ this._scheduledTimeouts.delete(id);304+ },305+306+ // func getRandomData(r []byte)307+ "runtime.getRandomData": (sp) => {308+ sp >>>= 0;309+ crypto.getRandomValues(loadSlice(sp + 8));310+ },311+312+ // func finalizeRef(v ref)313+ "syscall/js.finalizeRef": (sp) => {314+ sp >>>= 0;315+ const id = this.mem.getUint32(sp + 8, true);316+ this._goRefCounts[id]--;317+ if (this._goRefCounts[id] === 0) {318+ const v = this._values[id];319+ this._values[id] = null;320+ this._ids.delete(v);321+ this._idPool.push(id);322+ }323+ },324+325+ // func stringVal(value string) ref326+ "syscall/js.stringVal": (sp) => {327+ sp >>>= 0;328+ storeValue(sp + 24, loadString(sp + 8));329+ },330+331+ // func valueGet(v ref, p string) ref332+ "syscall/js.valueGet": (sp) => {333+ sp >>>= 0;334+ const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));335+ sp = this._inst.exports.getsp() >>> 0; // see comment above336+ storeValue(sp + 32, result);337+ },338+339+ // func valueSet(v ref, p string, x ref)340+ "syscall/js.valueSet": (sp) => {341+ sp >>>= 0;342+ Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));343+ },344+345+ // func valueDelete(v ref, p string)346+ "syscall/js.valueDelete": (sp) => {347+ sp >>>= 0;348+ Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));349+ },350+351+ // func valueIndex(v ref, i int) ref352+ "syscall/js.valueIndex": (sp) => {353+ sp >>>= 0;354+ storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));355+ },356+357+ // valueSetIndex(v ref, i int, x ref)358+ "syscall/js.valueSetIndex": (sp) => {359+ sp >>>= 0;360+ Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));361+ },362+363+ // func valueCall(v ref, m string, args []ref) (ref, bool)364+ "syscall/js.valueCall": (sp) => {365+ sp >>>= 0;366+ try {367+ const v = loadValue(sp + 8);368+ const m = Reflect.get(v, loadString(sp + 16));369+ const args = loadSliceOfValues(sp + 32);370+ const result = Reflect.apply(m, v, args);371+ sp = this._inst.exports.getsp() >>> 0; // see comment above372+ storeValue(sp + 56, result);373+ this.mem.setUint8(sp + 64, 1);374+ } catch (err) {375+ sp = this._inst.exports.getsp() >>> 0; // see comment above376+ storeValue(sp + 56, err);377+ this.mem.setUint8(sp + 64, 0);378+ }379+ },380+381+ // func valueInvoke(v ref, args []ref) (ref, bool)382+ "syscall/js.valueInvoke": (sp) => {383+ sp >>>= 0;384+ try {385+ const v = loadValue(sp + 8);386+ const args = loadSliceOfValues(sp + 16);387+ const result = Reflect.apply(v, undefined, args);388+ sp = this._inst.exports.getsp() >>> 0; // see comment above389+ storeValue(sp + 40, result);390+ this.mem.setUint8(sp + 48, 1);391+ } catch (err) {392+ sp = this._inst.exports.getsp() >>> 0; // see comment above393+ storeValue(sp + 40, err);394+ this.mem.setUint8(sp + 48, 0);395+ }396+ },397+398+ // func valueNew(v ref, args []ref) (ref, bool)399+ "syscall/js.valueNew": (sp) => {400+ sp >>>= 0;401+ try {402+ const v = loadValue(sp + 8);403+ const args = loadSliceOfValues(sp + 16);404+ const result = Reflect.construct(v, args);405+ sp = this._inst.exports.getsp() >>> 0; // see comment above406+ storeValue(sp + 40, result);407+ this.mem.setUint8(sp + 48, 1);408+ } catch (err) {409+ sp = this._inst.exports.getsp() >>> 0; // see comment above410+ storeValue(sp + 40, err);411+ this.mem.setUint8(sp + 48, 0);412+ }413+ },414+415+ // func valueLength(v ref) int416+ "syscall/js.valueLength": (sp) => {417+ sp >>>= 0;418+ setInt64(sp + 16, parseInt(loadValue(sp + 8).length));419+ },420+421+ // valuePrepareString(v ref) (ref, int)422+ "syscall/js.valuePrepareString": (sp) => {423+ sp >>>= 0;424+ const str = encoder.encode(String(loadValue(sp + 8)));425+ storeValue(sp + 16, str);426+ setInt64(sp + 24, str.length);427+ },428+429+ // valueLoadString(v ref, b []byte)430+ "syscall/js.valueLoadString": (sp) => {431+ sp >>>= 0;432+ const str = loadValue(sp + 8);433+ loadSlice(sp + 16).set(str);434+ },435+436+ // func valueInstanceOf(v ref, t ref) bool437+ "syscall/js.valueInstanceOf": (sp) => {438+ sp >>>= 0;439+ this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);440+ },441+442+ // func copyBytesToGo(dst []byte, src ref) (int, bool)443+ "syscall/js.copyBytesToGo": (sp) => {444+ sp >>>= 0;445+ const dst = loadSlice(sp + 8);446+ const src = loadValue(sp + 32);447+ if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {448+ this.mem.setUint8(sp + 48, 0);449+ return;450+ }451+ const toCopy = src.subarray(0, dst.length);452+ dst.set(toCopy);453+ setInt64(sp + 40, toCopy.length);454+ this.mem.setUint8(sp + 48, 1);455+ },456+457+ // func copyBytesToJS(dst ref, src []byte) (int, bool)458+ "syscall/js.copyBytesToJS": (sp) => {459+ sp >>>= 0;460+ const dst = loadValue(sp + 8);461+ const src = loadSlice(sp + 16);462+ if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {463+ this.mem.setUint8(sp + 48, 0);464+ return;465+ }466+ const toCopy = src.subarray(0, dst.length);467+ dst.set(toCopy);468+ setInt64(sp + 40, toCopy.length);469+ this.mem.setUint8(sp + 48, 1);470+ },471+472+ "debug": (value) => {473+ console.log(value);474+ },475+ }476+ };477+ }478+479+ async run(instance) {480+ if (!(instance instanceof WebAssembly.Instance)) {481+ throw new Error("Go.run: WebAssembly.Instance expected");482+ }483+ this._inst = instance;484+ this.mem = new DataView(this._inst.exports.mem.buffer);485+ this._values = [ // JS values that Go currently has references to, indexed by reference id486+ NaN,487+ 0,488+ null,489+ true,490+ false,491+ globalThis,492+ this,493+ ];494+ this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id495+ this._ids = new Map([ // mapping from JS values to reference ids496+ [0, 1],497+ [null, 2],498+ [true, 3],499+ [false, 4],500+ [globalThis, 5],501+ [this, 6],502+ ]);503+ this._idPool = []; // unused ids that have been garbage collected504+ this.exited = false; // whether the Go program has exited505+506+ // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.507+ let offset = 4096;508+509+ const strPtr = (str) => {510+ const ptr = offset;511+ const bytes = encoder.encode(str + "\0");512+ new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);513+ offset += bytes.length;514+ if (offset % 8 !== 0) {515+ offset += 8 - (offset % 8);516+ }517+ return ptr;518+ };519+520+ const argc = this.argv.length;521+522+ const argvPtrs = [];523+ this.argv.forEach((arg) => {524+ argvPtrs.push(strPtr(arg));525+ });526+ argvPtrs.push(0);527+528+ const keys = Object.keys(this.env).sort();529+ keys.forEach((key) => {530+ argvPtrs.push(strPtr(`${key}=${this.env[key]}`));531+ });532+ argvPtrs.push(0);533+534+ const argv = offset;535+ argvPtrs.forEach((ptr) => {536+ this.mem.setUint32(offset, ptr, true);537+ this.mem.setUint32(offset + 4, 0, true);538+ offset += 8;539+ });540+541+ // The linker guarantees global data starts from at least wasmMinDataAddr.542+ // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.543+ const wasmMinDataAddr = 4096 + 8192;544+ if (offset >= wasmMinDataAddr) {545+ throw new Error("total length of command line and environment variables exceeds limit");546+ }547+548+ this._inst.exports.run(argc, argv);549+ if (this.exited) {550+ this._resolveExitPromise();551+ }552+ await this._exitPromise;553+ }554+555+ _resume() {556+ if (this.exited) {557+ throw new Error("Go program has already exited");558+ }559+ this._inst.exports.resume();560+ if (this.exited) {561+ this._resolveExitPromise();562+ }563+ }564+565+ _makeFuncWrapper(id) {566+ const go = this;567+ return function () {568+ const event = { id: id, this: this, args: arguments };569+ go._pendingEvent = event;570+ go._resume();571+ return event.result;572+ };573+ }574+ }575+})();
M diagramzip-render/package.json +1 -0
A diagramzip-render/scripts/build-d2-wasm.mjs +24 -0
M diagramzip-render/scripts/deploy-compatibility-units.mjs +1 -2
M diagramzip-render/scripts/deploy-python-units.mjs +1 -0
M diagramzip-render/scripts/deploy-worker-units.mjs +2 -4
M diagramzip-render/scripts/smoke-units.mjs +24 -2
@@ -47,15 +47,21 @@ const sharedUnits = {
4747 vega: 'vega-family',4848 vegalite: 'vega-family',4949 wireviz: 'wireviz',50+ structurizr: 'structurizr',51+ svgbob: 'svgbob-family',52+ ditaa: 'svgbob-family',5053 }5154 5255 const pythonEngines = new Set(['blockdiag', 'seqdiag', 'actdiag', 'nwdiag', 'packetdiag', 'rackdiag'])5356 const graphvizEngines = new Set(['graphviz', 'erd', 'dbml'])5457 const pikchrEngines = new Set(['pikchr'])55-const svgbobEngines = new Set(['svgbob'])58+const svgbobEngines = new Set(['svgbob', 'ditaa'])5659 const goatEngines = new Set(['goat'])5760 const wirevizEngines = new Set(['wireviz'])5861 const plantumlEngines = new Set(['plantuml', 'c4plantuml'])62+const structurizrEngines = new Set(['structurizr'])63+const d2Engines = new Set(['d2'])64+const symbolatorEngines = new Set(['symbolator'])5965 6066 async function smokeClientUnit(engine) {6167 const base = `https://${engine}.render.diagram.zip`
@@ -105,6 +111,10 @@ async function smoke([engine, filename]) {
105111 ? `${expectedUnit},dbml,graphviz`106112 : engine === 'wireviz'107113 ? `${expectedUnit},graphviz-family,graphviz`114+ : engine === 'structurizr'115+ ? `${expectedUnit},plantuml-family,plantuml`116+ : engine === 'ditaa'117+ ? `${expectedUnit},svgbob`108118 : expectedUnit109119 if (pipeline !== expectedPipeline) {110120 throw new Error(`${engine}: unexpected pipeline ${pipeline}`)
@@ -137,7 +147,7 @@ async function smoke([engine, filename]) {
137147 if (response.headers.get('X-Diagram-Renderer') !== 'edge-wasm') {138148 throw new Error(`${engine}: expected edge-wasm renderer, received ${response.headers.get('X-Diagram-Renderer')}`)139149 }140- if (!response.headers.get('X-Renderer-Build')?.startsWith('svgbob-0.7.6-edge-wasm-')) {150+ if (!response.headers.get('X-Renderer-Build')?.startsWith('svgbob-0.7.6-ditaa-translation-')) {141151 throw new Error(`${engine}: unexpected Svgbob Worker build ${response.headers.get('X-Renderer-Build')}`)142152 }143153 }
@@ -161,6 +171,18 @@ async function smoke([engine, filename]) {
161171 throw new Error(`${engine}: unexpected PlantUML-family Worker build ${response.headers.get('X-Renderer-Build')}`)162172 }163173 }174+ if (structurizrEngines.has(engine)) {175+ if (response.headers.get('X-Diagram-Renderer') !== 'edge-js') throw new Error(`${engine}: expected edge-js renderer, received ${response.headers.get('X-Diagram-Renderer')}`)176+ if (!response.headers.get('X-Renderer-Build')?.startsWith('structurizr-6.2.2-plantuml-translation-')) throw new Error(`${engine}: unexpected Structurizr Worker build ${response.headers.get('X-Renderer-Build')}`)177+ }178+ if (d2Engines.has(engine)) {179+ if (response.headers.get('X-Diagram-Renderer') !== 'edge-wasm') throw new Error(`${engine}: expected edge-wasm renderer, received ${response.headers.get('X-Diagram-Renderer')}`)180+ if (!response.headers.get('X-Renderer-Build')?.startsWith('d2-0.7.1-custom-grid-')) throw new Error(`${engine}: unexpected D2 Worker build ${response.headers.get('X-Renderer-Build')}`)181+ }182+ if (symbolatorEngines.has(engine)) {183+ if (response.headers.get('X-Diagram-Renderer') !== 'edge-python') throw new Error(`${engine}: expected edge-python renderer, received ${response.headers.get('X-Diagram-Renderer')}`)184+ if (!response.headers.get('X-Renderer-Build')?.startsWith('symbolator-python-')) throw new Error(`${engine}: unexpected Symbolator Worker build ${response.headers.get('X-Renderer-Build')}`)185+ }164186 return `${engine.padEnd(10)} ${response.headers.get('X-Diagram-Cache')?.padEnd(4)} ${body.length} bytes`165187 }166188
M diagramzip-render/src/adapters/edge.ts +2 -0
A diagramzip-render/src/adapters/edge/d2.ts +76 -0
@@ -0,0 +1,76 @@
1+import '../../../d2-wasm/wasm_exec.js'2+import wasmModule from '../../../d2-wasm/d2-custom.wasm'3+import { RenderError } from '../../errors'4+import type { RendererAdapter } from '../../types'5+import { edgeResult } from './types'6+7+const VERSION = 'd2@0.7.1/custom-grid-1'8+const MAX_SOURCE_BYTES = 512 * 10249+const SUPPORTED_OPTIONS = new Set(['layout', 'animate-interval'])10+11+type GoRuntime = { importObject: WebAssembly.Imports; run(instance: WebAssembly.Instance): Promise<unknown> }12+type D2Global = typeof globalThis & { d2CustomRender?: (source: string, options?: string) => string }13+type D2Response = { svg?: string; error?: string }14+15+let initPromise: Promise<void> | undefined16+let renderTail: Promise<void> = Promise.resolve()17+18+async function init(): Promise<void> {19+ if (initPromise) return initPromise20+ initPromise = (async () => {21+ const go = new (globalThis as unknown as { Go: new () => GoRuntime }).Go()22+ const result = await WebAssembly.instantiate(wasmModule, go.importObject) as unknown as WebAssembly.Instance | { instance: WebAssembly.Instance }23+ const instance = result instanceof WebAssembly.Instance ? result : result.instance24+ void go.run(instance)25+ for (let i = 0; i < 100; i++) {26+ if ((globalThis as D2Global).d2CustomRender) return27+ await new Promise(resolve => setTimeout(resolve, 1))28+ }29+ throw new Error('D2 Wasm runtime did not initialize.')30+ })()31+ return initPromise32+}33+34+function withRenderLock<T>(operation: () => Promise<T>): Promise<T> {35+ const previous = renderTail36+ let release!: () => void37+ renderTail = new Promise(resolve => { release = resolve })38+ return previous.then(async () => {39+ try { return await operation() } finally { release() }40+ })41+}42+43+export const d2Adapter: RendererAdapter = {44+ id: 'd2', runtime: 'edge-wasm', version: VERSION,45+ async render(request, signal) {46+ if (signal.aborted) throw signal.reason47+ if (new TextEncoder().encode(request.source).byteLength > MAX_SOURCE_BYTES) {48+ throw new RenderError(413, 'source_too_large', 'D2 source exceeds the 512 KiB limit.')49+ }50+ for (const name of Object.keys(request.options)) {51+ if (!SUPPORTED_OPTIONS.has(name)) {52+ throw new RenderError(400, 'unsupported_options', `Unsupported D2 option: ${name}.`)53+ }54+ }55+ if (request.options.layout !== undefined && request.options.layout.toLowerCase() !== 'grid') {56+ throw new RenderError(400, 'unsupported_options', 'D2 only supports the deterministic grid layout in the edge unit; dagre and elk are unavailable.')57+ }58+ const animateInterval = request.options['animate-interval']59+ if (animateInterval !== undefined && (!/^\d+$/.test(animateInterval) || Number(animateInterval) < 1 || Number(animateInterval) > 60_000)) {60+ throw new RenderError(400, 'invalid_options', 'D2 animate-interval must be an integer from 1 to 60000 milliseconds.')61+ }62+ await init()63+ return withRenderLock(async () => {64+ if (signal.aborted) throw signal.reason65+ const render = (globalThis as D2Global).d2CustomRender66+ if (!render) throw new RenderError(500, 'renderer_unavailable', 'D2 Wasm renderer is unavailable.')67+ let result: D2Response68+ try { result = JSON.parse(render(request.source, animateInterval === undefined ? '' : JSON.stringify({ animateInterval: Number(animateInterval) }))) as D2Response } catch (error) {69+ throw new RenderError(422, 'render_failed', error instanceof Error ? error.message : String(error))70+ }71+ if (result.error) throw new RenderError(422, 'render_failed', result.error.slice(0, 500))72+ if (!result.svg || result.svg.trim() === '') throw new RenderError(422, 'empty_render', 'D2 returned no SVG.')73+ return edgeResult('d2', VERSION, result.svg, 'edge-wasm')74+ })75+ },76+}
A diagramzip-render/src/adapters/edge/ditaa.ts +84 -0
@@ -0,0 +1,84 @@
1+import { RenderError } from '../../errors'2+import type { RendererAdapter } from '../../types'3+import { render as renderSvgbob } from '../../vendor/svgbob_wasm.js'4+import { edgeResult } from './types'5+6+// Ditaa's maintained distribution is a native/Java application. This adapter7+// intentionally implements the portable part of the format: ASCII geometry,8+// labels, and the common connector characters. The resulting source is fed9+// to the already edge-native svgbob renderer so there is no second runtime.10+const VERSION = 'ditaa-ascii-via-svgbob@0.7.6/edge-wasm-1'11+const MAX_SOURCE_BYTES = 256 * 102412+const MAX_OPTION_BYTES = 25613+const COLOR = /^(?:#[0-9a-f]{3,8}|[a-z]+|transparent)$/i14+const DITAA_TAG = /\{([a-z][a-z0-9_-]{0,15})\}/gi15+const SHAPE_TAGS = new Set(['d', 's', 'io', 'tr', 'o', 'r', 'db', 'mo', 'cloud', 'document', 'storage', 'input', 'output'])16+const OPTION_NAMES = new Set([17+ 'scale', 'tabs', 'background', 'transparent',18+ 'no-antialias', 'no-separation', 'round-corners', 'no-shadows',19+])20+21+export const DITAA_KNOWN_LOSSES = [22+ 'Ditaa shape tags ({d}, {s}, {io}, and related tags) are removed but their shapes are not translated.',23+ 'Ditaa color tags ({cXXX}) are removed; edge output uses the svgbob monochrome style.',24+ 'Ditaa shadows, rounded corners, anti-aliasing, and edge separation are not available in the svgbob pipeline.',25+] as const26+27+function optionNumber(options: Record<string, string>, name: string, fallback: number, maximum: number, integer = false): number {28+ const value = options[name]29+ if (value === undefined) return fallback30+ const parsed = Number(value)31+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed > maximum || (integer && !Number.isInteger(parsed))) {32+ throw new RenderError(400, 'invalid_options', `Ditaa option '${name}' must be a finite positive number.`)33+ }34+ return parsed35+}36+37+function optionColor(options: Record<string, string>): string {38+ if (Object.hasOwn(options, 'transparent')) return 'transparent'39+ const raw = options.background40+ if (raw === undefined) return 'transparent'41+ const value = /^[0-9a-f]{6}$/i.test(raw) ? `#${raw}` : raw42+ if (!COLOR.test(value) || new TextEncoder().encode(value).byteLength > MAX_OPTION_BYTES) {43+ throw new RenderError(400, 'invalid_options', "Ditaa option 'background' must be a CSS color or six hexadecimal digits.")44+ }45+ return value46+}47+48+function normalizeSource(source: string, options: Record<string, string>): string {49+ const tabs = optionNumber(options, 'tabs', 8, 32, true)50+ const expanded = source.replaceAll('\t', ' '.repeat(tabs))51+ // Ditaa tags are drawing directives, not labels. Dropping them retains the52+ // readable label and prevents svgbob from treating braces as geometry.53+ return expanded.replace(DITAA_TAG, (whole, name: string) => {54+ const normalized = name.toLowerCase()55+ // Keep brace-delimited label text that is not a known Ditaa directive.56+ if (SHAPE_TAGS.has(normalized) || /^c(?:[a-z]{3}|[0-9a-f]{3,6})$/i.test(name)) return ''57+ return whole58+ })59+}60+61+export const ditaaAdapter: RendererAdapter = {62+ id: 'ditaa', runtime: 'edge-wasm', version: VERSION,63+ async render(request, signal) {64+ if (signal.aborted) throw signal.reason65+ if (new TextEncoder().encode(request.source).byteLength > MAX_SOURCE_BYTES) {66+ throw new RenderError(413, 'source_too_large', 'Ditaa source exceeds the 256 KiB limit.')67+ }68+ for (const name of Object.keys(request.options)) {69+ if (!OPTION_NAMES.has(name)) throw new RenderError(400, 'unsupported_options', `Unsupported Ditaa option: ${name}.`)70+ }71+ const source = normalizeSource(request.source, request.options)72+ const scale = optionNumber(request.options, 'scale', 1, 16)73+ const background = optionColor(request.options)74+ try {75+ const body = renderSvgbob(source, background, '#000000', 'Iosevka Fixed, monospace', 14, scale, 2)76+ if (!body.includes('<svg')) throw new RenderError(422, 'render_failed', 'Ditaa returned no SVG.')77+ return edgeResult('ditaa', VERSION, body, 'edge-wasm')78+ } catch (error) {79+ if (error instanceof RenderError) throw error80+ const message = error instanceof Error ? error.message : String(error)81+ throw new RenderError(422, 'render_failed', message.slice(0, 500) || 'Ditaa could not render this source.')82+ }83+ },84+}
M diagramzip-render/src/adapters/edge/types.ts +3 -3
M diagramzip-render/src/catalog.ts +32 -3
@@ -25,7 +25,7 @@ const targetRuntime: Record<EngineId, EngineRuntime> = {
2525 bytefield: 'edge-js',2626 dbml: 'edge-wasm',2727 diagramsnet: 'client',28- ditaa: 'origin',28+ ditaa: 'edge-wasm',2929 erd: 'edge-wasm',3030 excalidraw: 'client',3131 goat: 'edge-wasm',
@@ -33,7 +33,7 @@ const targetRuntime: Record<EngineId, EngineRuntime> = {
3333 pikchr: 'edge-wasm',3434 structurizr: 'edge-js',3535 svgbob: 'edge-wasm',36- symbolator: 'origin',36+ symbolator: 'edge-python',3737 tikz: 'origin',3838 umlet: 'client',3939 vega: 'edge-js',
@@ -67,6 +67,10 @@ const activeRuntime: Partial<Record<EngineId, EngineRuntime>> = {
6767 goat: 'edge-wasm',6868 erd: 'edge-wasm',6969 wireviz: 'edge-python',70+ structurizr: 'edge-js',71+ d2: 'edge-wasm',72+ ditaa: 'edge-wasm',73+ symbolator: 'edge-python',7074 }7175 7276 const versions: Partial<Record<EngineId, string>> = {
@@ -94,6 +98,10 @@ const versions: Partial<Record<EngineId, string>> = {
9498 goat: 'goat@0.5.1/edge-wasm-1',9599 erd: 'erd@0.2.1.0+graphviz@15.1.1/edge-wasm-1',96100 wireviz: 'wireviz@0.3.2/python-translator-1',101+ structurizr: 'structurizr@6.2.2+plantuml@1.2026.6/translation-1',102+ d2: 'd2@0.7.1/custom-grid-1',103+ ditaa: 'ditaa-ascii+svgbob@0.7.6/edge-wasm-1',104+ symbolator: 'symbolator@1.2.2/python-translation-1',97105 }98106 99107 const losses: Partial<Record<EngineId, readonly string[]>> = {
@@ -146,7 +154,11 @@ const losses: Partial<Record<EngineId, readonly string[]>> = {
146154 'Font subsetting is disabled by the sandbox policy, so exports embed the complete self-hosted font.',147155 ],148156 nomnoml: ['Only SVG is in the v2 rendering contract.'],149- symbolator: ['External links are removed during SVG sanitization.'],157+ symbolator: [158+ 'Only SVG is supported.',159+ 'Native Cairo/Pango font shaping is replaced by deterministic browser-font estimates, so layout and typography differ from Symbolator 1.2.2.',160+ 'Filesystem library scanning and persisted array-type caches are unavailable; each request contains one bounded HDL source.',161+ ],150162 vega: ['URL-backed data and images are rejected; data must be embedded as values.', 'Only SVG is in the v2 rendering contract.'],151163 vegalite: ['URL-backed data and images are rejected; data must be embedded as values.', 'Only SVG is in the v2 rendering contract.'],152164 wavedrom: ['Only the six bundled Kroki skins are accepted.', 'Only SVG is in the v2 rendering contract.'],
@@ -167,6 +179,23 @@ const losses: Partial<Record<EngineId, readonly string[]>> = {
167179 'The upstream source reports WireViz 0.3.2; the Dockerfile release label is 0.3.3.',168180 'The translated graph uses GraphViz 15.1.1 versus the compatibility image GraphViz 14.1.3; output differences are possible.',169181 ],182+ structurizr: [183+ 'Only SVG is supported.',184+ 'Structurizr scripts, docs, plugins, filesystem access, and arbitrary remote themes are rejected.',185+ 'The bounded lowering preserves supported views, relationships, structural styles, and legends, but layout and typography may differ from native Structurizr export.',186+ ],187+ d2: [188+ 'Only SVG is supported.',189+ 'The edge build uses official D2 v0.7.1 parser/compiler/SVG renderer with a deterministic grid layout and straight-line routing; Dagre and ELK layouts are unavailable.',190+ 'D2 syntax, labels, shapes, containers, steps/scenarios, and CSS edge animation are retained, but layout quality differs substantially from Dagre/ELK.',191+ 'The supported animation-interval option is bounded to 1–60000 milliseconds; animated edges and multi-board SVG output remain supported.',192+ ],193+ ditaa: [194+ 'Only SVG is supported.',195+ 'Ditaa ASCII geometry, labels, and connectors are translated through the shared Svgbob 0.7.6 Worker dependency.',196+ 'Ditaa shape and color directives are removed; specialized shapes and per-shape colors are not retained.',197+ 'Shadows, rounded corners, anti-aliasing, and edge separation flags are accepted for compatibility but have no effect in the Svgbob pipeline.',198+ ],170199 }171200 172201 export const ENGINE_CATALOG: readonly EngineCatalogEntry[] = ENGINE_IDS.map(id => ({
A diagramzip-render/src/raw.d.ts +4 -0
A diagramzip-render/src/structurizr.ts +231 -0
@@ -0,0 +1,231 @@
1+import { RenderError } from './errors'2+3+export type StructurizrKind = 'person' | 'softwareSystem' | 'container' | 'component' | 'deploymentNode' | 'infrastructureNode' | 'containerInstance' | 'softwareSystemInstance'4+export interface StructurizrElement { id: string; kind: StructurizrKind; name: string; description: string; technology: string; tags: string[]; parent?: string; modelRef?: string; environment?: string }5+export interface StructurizrRelationship { from: string; to: string; description: string; technology: string }6+export interface StructurizrStyle { background?: string; color?: string; fontSize?: number; shape?: string; opacity?: number }7+export interface StructurizrView { kind: string; key: string; scope?: string; includes: string[]; relationships: StructurizrRelationship[]; direction?: string; animations: string[] }8+export interface StructurizrWorkspace { name: string; elements: StructurizrElement[]; relationships: StructurizrRelationship[]; views: StructurizrView[]; styles: Map<string, StructurizrStyle>; themes: string[] }9+10+const MAX_LINES = 20_00011+const MAX_ELEMENTS = 4_00012+const MAX_RELATIONSHIPS = 12_00013+const MAX_NESTING = 4814+const DECLARATIONS = new Set(['person', 'softwareSystem', 'softwaresystem', 'container', 'component', 'deploymentNode', 'infrastructureNode', 'containerInstance', 'softwareSystemInstance'])15+const VIEW_KINDS = new Set(['systemlandscape', 'systemcontext', 'container', 'component', 'dynamic', 'deployment'])16+17+function fail(message: string): never { throw new RenderError(422, 'invalid_structurizr', message) }18+19+function stripComments(source: string): string[] {20+ const lines: string[] = []; let block = false21+ for (const original of source.replaceAll('\r\n', '\n').replaceAll('\r', '\n').split('\n')) {22+ let line = ''; let quote = false23+ for (let i = 0; i < original.length; i++) {24+ const two = original.slice(i, i + 2)25+ if (block) { if (two === '*/') { block = false; i++ } continue }26+ if (!quote && two === '/*') { block = true; i++; continue }27+ const ch = original[i]!28+ if (ch === '"') { quote = !quote; line += ch; continue }29+ if (!quote && ch === '#' && /\s|$/.test(original[i + 1] ?? '')) break30+ line += ch31+ }32+ lines.push(line)33+ }34+ if (block) fail('Unterminated block comment.')35+ return lines36+}37+38+function args(text: string): string[] {39+ const result: string[] = []; let value = ''; let quote = false; let escaped = false40+ const flush = () => { if (value.trim()) result.push(value.trim()); value = '' }41+ for (const ch of text.trim()) {42+ if (escaped) { value += ch; escaped = false; continue }43+ if (quote && ch === '\\') { value += ch; escaped = true; continue }44+ if (ch === '"') { quote = !quote; value += ch; continue }45+ if (!quote && /\s/.test(ch)) { flush(); continue }46+ value += ch47+ }48+ if (quote) fail('Unterminated string literal.')49+ flush(); return result50+}51+52+function unquote(value: string | undefined): string {53+ const v = value ?? ''54+ if (v.startsWith('"') && v.endsWith('"')) return v.slice(1, -1).replaceAll('\\"', '"').replaceAll('\\n', '\n').replaceAll('\\\\', '\\')55+ return v56+}57+58+function declaration(line: string): { id: string; kind: string; values: string[] } | undefined {59+ const match = line.match(/^(?:(?<id>[A-Za-z_][\w.-]*)\s*=\s*)?(?<kind>[A-Za-z][\w]*)\s+(?<rest>.*)$/)60+ if (!match || !DECLARATIONS.has(match.groups!.kind!)) return undefined61+ return { id: match.groups!.id ?? '', kind: match.groups!.kind!, values: args(match.groups!.rest!) }62+}63+64+function relation(line: string): StructurizrRelationship | undefined {65+ const match = line.match(/^(\S+)\s*->\s*(\S+)(?:\s+("(?:\\.|[^"\\])*"))?(?:\s+("(?:\\.|[^"\\])*"))?$/)66+ return match ? { from: match[1]!, to: match[2]!, description: unquote(match[3]), technology: unquote(match[4]) } : undefined67+}68+69+function styleValue(value: string): string { return unquote(value) }70+const COLORS = /^(?:#[0-9a-f]{3,8}|(?:black|white|red|green|blue|yellow|orange|purple|gray|grey|transparent))$/i71+const SHAPES = new Set(['rectangle', 'roundrectangle', 'roundedbox', 'circle', 'hexagon', 'person', 'node', 'webbrowser', 'mobiledevicelandscape', 'cylinder'])72+function validateStyle(tag: string, key: string, value: string): void {73+ if ((key === 'background' || key === 'color') && !COLORS.test(value)) fail(`Unsupported ${key} color for style ${tag}.`)74+ if (key === 'shape' && !SHAPES.has(value.toLowerCase())) fail(`Unsupported shape for style ${tag}.`)75+ if (key === 'fontSize' && (!Number.isInteger(Number(value)) || Number(value) < 1 || Number(value) > 96)) fail(`Invalid fontSize for style ${tag}.`)76+ if (key === 'opacity' && (!Number.isInteger(Number(value)) || Number(value) < 0 || Number(value) > 100)) fail(`Invalid opacity for style ${tag}.`)77+}78+79+export function parseStructurizr(source: string): StructurizrWorkspace {80+ const lines = stripComments(source); if (lines.length > MAX_LINES) fail('Structurizr source has too many lines.')81+ const workspace: StructurizrWorkspace = { name: '', elements: [], relationships: [], views: [], styles: new Map(), themes: [] }82+ const stack: Array<{ kind: string; value?: unknown; owner?: string }> = []83+ let currentView: StructurizrView | undefined84+ let currentStyle: { tag: string; relationship: boolean } | undefined85+ let generated = 086+ const context = () => stack.at(-1)?.kind ?? ''87+ const owner = () => [...stack].reverse().find(item => item.owner)?.owner88+ const push = (item: { kind: string; value?: unknown; owner?: string }) => {89+ if (stack.length >= MAX_NESTING) fail('Structurizr nesting is too deep.')90+ stack.push(item)91+ }92+ const addElement = (item: StructurizrElement) => {93+ if (++generated > MAX_ELEMENTS) fail('Structurizr workspace has too many elements.')94+ if (workspace.elements.some(existing => existing.id === item.id)) fail(`Duplicate element identifier: ${item.id}.`)95+ workspace.elements.push(item)96+ }97+ const parseBlockOpen = (line: string): string => line.replace(/\s*\{\s*$/, '').trim()98+ for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {99+ let line = lines[lineNumber]!.trim(); if (!line) continue100+ if (line === '}') {101+ const closed = stack.pop()?.kind102+ if (closed === 'view') currentView = undefined103+ if (closed === 'style') currentStyle = undefined104+ continue105+ }106+ const opens = line.endsWith('{'); if (opens) line = parseBlockOpen(line)107+ if (line.startsWith('workspace')) {108+ const values = args(line.slice('workspace'.length)); workspace.name = unquote(values[0]);109+ if (opens) push({ kind: 'workspace' }); continue110+ }111+ if (line === 'model') { if (opens) push({ kind: 'model' }); continue }112+ if (line === 'views') { if (opens) push({ kind: 'views' }); continue }113+ if (/^group\s+/i.test(line) && opens) { const values = args(line.replace(/^group\s+/i, '')); push({ kind: 'group', value: unquote(values[0]) }); continue }114+ if (/^(?:[A-Za-z_][\w.-]*\s*=\s*)?deploymentEnvironment\b/i.test(line) && opens) { const match = line.match(/^(?:(?<id>[A-Za-z_][\w.-]*)\s*=\s*)?deploymentEnvironment\s*(?<rest>.*)$/i)!; const values = args(match.groups!.rest!); push({ kind: 'deployment', value: unquote(values[0]) }); continue }115+ if (/^(?:[A-Za-z_][\w.-]*\s*=\s*)?deploymentNode\b/i.test(line) && opens) {116+ const d = declaration(line); if (!d) fail(`Invalid deployment node at line ${lineNumber + 1}.`); const values = d.values117+ const id = d.id || `deployment_${workspace.elements.length}`118+ addElement({ id, kind: 'deploymentNode', name: unquote(values[0]), description: unquote(values[1]), technology: unquote(values[2]), tags: [], environment: stack.find(item => item.kind === 'deployment')?.value as string | undefined, parent: owner() && workspace.elements.find(item => item.id === owner() && item.kind === 'deploymentNode') ? owner() : undefined }); push({ kind: 'deploymentNode', owner: id }); continue119+ }120+ if (/^(?:[A-Za-z_][\w.-]*\s*=\s*)?infrastructureNode\b/i.test(line) && opens) {121+ const d = declaration(line); if (!d) fail(`Invalid infrastructure node at line ${lineNumber + 1}.`); const values = d.values122+ const id = d.id || `infrastructure_${workspace.elements.length}`123+ addElement({ id, kind: 'infrastructureNode', name: unquote(values[0]), description: unquote(values[1]), technology: unquote(values[2]), tags: [], environment: stack.find(item => item.kind === 'deployment')?.value as string | undefined, parent: owner() && workspace.elements.find(item => item.id === owner() && item.kind === 'deploymentNode') ? owner() : undefined }); push({ kind: 'deploymentNode', owner: id }); continue124+ }125+ if (context() === 'views' && opens && !/^styles\b/i.test(line)) {126+ const parts = args(line); const kind = parts.shift()?.toLowerCase() ?? ''; if (!VIEW_KINDS.has(kind)) fail(`Unsupported Structurizr view: ${kind}.`)127+ const view: StructurizrView = { kind, key: '', scope: '', includes: [], relationships: [], animations: [] }128+ if (kind === 'systemlandscape') view.key = unquote(parts[0]) || 'SystemLandscape'129+ else if (kind === 'systemcontext' || kind === 'container' || kind === 'component' || kind === 'dynamic') { view.scope = unquote(parts[0]); view.key = unquote(parts[1]) || `${kind}_${view.scope}` }130+ else { view.scope = unquote(parts[1]) || unquote(parts[0]); view.key = unquote(parts[2]) || unquote(parts[1]) || `${kind}_${view.scope}` }131+ workspace.views.push(view); currentView = view; push({ kind: 'view' }); continue132+ }133+ const d = declaration(line)134+ if (d && ['containerInstance', 'softwareSystemInstance'].includes(d.kind)) { const values = d.values; const target = workspace.elements.find(item => item.id === unquote(values[0])); if (!target) fail(`Unknown instance target: ${values[0]}.`); addElement({ id: d.id || `${d.kind}_${workspace.elements.length}`, kind: d.kind as StructurizrKind, name: target.name, description: unquote(values[1]) || target.description, technology: unquote(values[2]) || target.technology, tags: [...target.tags], modelRef: target.id, environment: stack.find(item => item.kind === 'deployment')?.value as string | undefined, parent: owner() && workspace.elements.find(item => item.id === owner() && item.kind === 'deploymentNode') ? owner() : undefined }); continue }135+ if (d && DECLARATIONS.has(d.kind)) {136+ const kind = d.kind.toLowerCase() === 'softwaresystem' ? 'softwareSystem' : d.kind as StructurizrKind137+ const values = d.values; const current = owner() ? workspace.elements.find(item => item.id === owner()) : undefined138+ const parent = current && ['softwareSystem', 'container'].includes(current.kind) ? current.id : undefined139+ const id = d.id || `${kind}_${workspace.elements.length}`140+ addElement({ id, kind, name: unquote(values[0]), description: unquote(values[1]), technology: unquote(values[2]), tags: unquote(values[3]).split(',').map(tag => tag.trim()).filter(Boolean), parent });141+ if (opens) push({ kind: kind as string, owner: id }); continue142+ }143+ if (/^tags\s+/i.test(line)) { const tags = args(line.replace(/^tags\s+/i, '')).map(unquote); const element = owner() ? workspace.elements.find(item => item.id === owner()) : undefined; if (element) element.tags.push(...tags); continue }144+ if (/^description\s+/i.test(line)) { const element = owner() ? workspace.elements.find(item => item.id === owner()) : undefined; if (element) element.description = unquote(args(line.replace(/^description\s+/i, ''))[0]); else fail(`Description has no owning element at line ${lineNumber + 1}.`); continue }145+ if (line.startsWith('views')) continue146+ if (currentView) {147+ if (/^include\s+\*/i.test(line)) { currentView.includes.push('*'); continue }148+ if (/^include\s+/i.test(line)) { currentView.includes.push(...args(line.replace(/^include\s+/i, '')).map(unquote)); continue }149+ if (/^auto?layout\b/i.test(line)) { currentView.direction = args(line.replace(/^auto?layout\s*/i, ''))[0]; continue }150+ if (line.toLowerCase() === 'animation' && opens) { push({ kind: 'animation' }); continue }151+ const dynamicRelation = relation(line); if (dynamicRelation) { currentView.relationships.push(dynamicRelation); continue }152+ }153+ if (context() === 'animation') { const ids = line.replace(/^[0-9]+\s*:\s*/, '').split(/\s+/).filter(token => /^[A-Za-z_][\w.-]*$/.test(token)); currentView?.animations.push(...ids); continue }154+ if (context() === 'model' || context() === 'deployment' || context() === 'deploymentNode') { const r = relation(line); if (r) { if (workspace.relationships.push(r) > MAX_RELATIONSHIPS) fail('Structurizr workspace has too many relationships.'); continue } }155+ if (/^theme\s+/i.test(line) || /^themes\s+/i.test(line)) {156+ const themes = args(line.replace(/^themes?\s+/i, '')).map(unquote)157+ for (const theme of themes) if (theme !== 'default' && !/^https:\/\/static\.structurizr\.com\/themes\/(?:amazon-web-services|google-cloud-platform|kubernetes|microsoft-azure|oracle-cloud-infrastructure)(?:[-/][\w.-]+)*\/?$/i.test(theme)) fail(`Unsupported Structurizr theme: ${theme}.`)158+ workspace.themes.push(...themes); continue159+ }160+ if (context() === 'styles' && opens && /^(element|relationship)\s+/i.test(line)) { const values = args(line.replace(/^(?:element|relationship)\s+/i, '')); currentStyle = { tag: unquote(values[0]), relationship: /^relationship/i.test(line) }; push({ kind: 'style' }); continue }161+ if (context() === 'style' && currentStyle) {162+ const match = line.match(/^(\w+)\s+(.+)$/); const key = match?.[1] ?? ''; const value = styleValue(match?.[2] ?? ''); if (['fontSize', 'opacity', 'background', 'color', 'shape'].includes(key)) { validateStyle(currentStyle.tag, key, value); const style = workspace.styles.get(currentStyle.tag) ?? {}; if (key === 'fontSize' || key === 'opacity') (style as Record<string, unknown>)[key] = Number(value); else (style as Record<string, unknown>)[key] = value; workspace.styles.set(currentStyle.tag, style) } continue163+ }164+ if (line === 'styles' && opens) { push({ kind: 'styles' }); continue }165+ if (line.startsWith('!')) fail(`Structurizr directive is not supported: ${line.split(/\s/)[0]}.`)166+ if (line !== 'include *' && line !== 'autoLayout' && line !== 'autolayout') fail(`Unsupported Structurizr syntax at line ${lineNumber + 1}: ${line.slice(0, 100)}`)167+ }168+ if (stack.length) fail('Unclosed Structurizr block.')169+ if (!workspace.views.length) fail('Empty diagram, does not have any view.')170+ return workspace171+}172+173+function plantEscape(value: string): string { return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('\n', '\\n') }174+function alias(value: string): string { return `s_${value.replace(/[^A-Za-z0-9_]/g, '_')}` }175+function implicitTag(kind: StructurizrKind): string { return ({ person: 'Person', softwareSystem: 'Software System', container: 'Container', component: 'Component', deploymentNode: 'Deployment Node', infrastructureNode: 'Infrastructure Node', containerInstance: 'Container', softwareSystemInstance: 'Software System' } as Record<StructurizrKind, string>)[kind] }176+177+export function lowerStructurizr(workspace: StructurizrWorkspace, options: Record<string, string>): string {178+ if (Object.keys(options).some(key => key !== 'view-key' && key !== 'output')) fail('Unsupported Structurizr option.')179+ const viewKey = options['view-key']; const view = viewKey ? workspace.views.find(item => item.key === viewKey) : workspace.views[0]180+ if (!view) fail(`Unable to find view for key: ${viewKey}.`)181+ const output = (options.output ?? 'diagram').trim() || 'diagram'; if (output !== 'diagram' && output !== 'legend') fail(`Unknown output option: ${output}.`)182+ if (output === 'legend') return '@startuml\nlegend\n|= Tag |= Background |\n' + [...workspace.styles.entries()].map(([tag, style]) => `| ${plantEscape(tag)} | ${plantEscape(style.background ?? '')} |`).join('\n') + '\nendlegend\n@enduml'183+ const byId = new Map(workspace.elements.map(item => [item.id, item]))184+ const selected = new Set<string>()185+ if (view.kind === 'systemlandscape') { for (const item of workspace.elements) if (item.kind === 'person' || item.kind === 'softwareSystem') selected.add(item.id) }186+ else if (view.kind === 'systemcontext') { if (view.scope && byId.has(view.scope)) selected.add(view.scope) }187+ else if (view.kind === 'container') { if (view.scope && byId.has(view.scope)) for (const item of workspace.elements) if (item.parent === view.scope && item.kind === 'container') selected.add(item.id) }188+ else if (view.kind === 'component') { if (view.scope && byId.has(view.scope)) for (const item of workspace.elements) if (item.parent === view.scope && item.kind === 'component') selected.add(item.id) }189+ else if (view.kind === 'deployment') { const env = view.scope; for (const item of workspace.elements) if (['deploymentNode', 'infrastructureNode', 'containerInstance', 'softwareSystemInstance'].includes(item.kind) && (!env || item.environment?.toLowerCase() === env.toLowerCase())) selected.add(item.id) }190+ else for (const item of workspace.elements) selected.add(item.id)191+ for (const id of view.includes) if (id !== '*' && byId.has(id)) selected.add(id)192+ const viewRelationships = view.kind === 'dynamic' ? view.relationships : workspace.relationships193+ if (view.kind === 'dynamic') {194+ for (const item of viewRelationships) { selected.add(item.from); selected.add(item.to) }195+ } else if (view.kind !== 'deployment') {196+ const seed = new Set(selected)197+ const relatedKindAllowed = (id: string): boolean => {198+ const element = byId.get(id)199+ if (!element) return false200+ if (view.kind === 'systemcontext') return element.kind === 'person' || element.kind === 'softwareSystem'201+ if (view.kind === 'container') return element.kind === 'person' || element.kind === 'softwareSystem' || element.kind === 'container'202+ if (view.kind === 'component') return element.kind !== 'component' || element.parent === view.scope203+ return true204+ }205+ for (const item of viewRelationships) {206+ if (seed.has(item.from) && relatedKindAllowed(item.to)) selected.add(item.to)207+ if (seed.has(item.to) && relatedKindAllowed(item.from)) selected.add(item.from)208+ }209+ }210+ const lines = ['@startuml', view.direction?.toLowerCase() === 'lr' ? 'left to right direction' : 'top to bottom direction']211+ const styleIds = new Map<string, string>(); let styleIndex = 0212+ for (const [tag, style] of workspace.styles) { const id = `style_${styleIndex++}`; styleIds.set(tag, id); if (style.background) lines.push(`skinparam rectangle<<${id}>> BackgroundColor ${style.background}`); if (style.color) lines.push(`skinparam rectangle<<${id}>> FontColor ${style.color}`); if (style.fontSize) lines.push(`skinparam rectangle<<${id}>> FontSize ${style.fontSize}`); if (style.opacity !== undefined) lines.push(`skinparam rectangle<<${id}>> Transparency ${100 - style.opacity}`); if (style.shape === 'roundrectangle' || style.shape === 'roundedbox') lines.push(`skinparam rectangle<<${id}>> RoundCorner 20`) }213+ for (const element of workspace.elements) {214+ if (!selected.has(element.id)) continue215+ const text = `${plantEscape(element.name)}${element.description ? `\\n[${plantEscape(element.description)}]` : ''}`216+ const tags = [implicitTag(element.kind), ...element.tags].filter((tag, index, all) => all.indexOf(tag) === index)217+ const stereotypes = tags.map(tag => styleIds.get(tag)).filter(Boolean).map(id => `<<${id}>>`).join('')218+ const style = tags.map(tag => workspace.styles.get(tag)).find(Boolean)219+ const color = style?.background ? ` ${style.background}` : ''220+ const shape = tags.map(tag => workspace.styles.get(tag)?.shape).find(Boolean)?.toLowerCase()221+ const type = element.kind === 'person' || shape === 'person' ? 'actor' : element.kind === 'infrastructureNode' || element.kind === 'deploymentNode' || shape === 'node' ? 'node' : shape === 'circle' ? 'circle' : shape === 'cylinder' ? 'database' : 'rectangle'222+ lines.push(`${type} "${text}" as ${alias(element.id)}${stereotypes}${color}`)223+ }224+ const relationships = viewRelationships225+ for (const item of relationships) {226+ if (!selected.has(item.from) || !selected.has(item.to)) continue227+ const label = plantEscape(item.description + (item.technology ? ` [${item.technology}]` : ''))228+ lines.push(`${alias(item.from)} --> ${alias(item.to)}${label ? ` : ${label}` : ''}`)229+ }230+ lines.push('@enduml'); return lines.join('\n')231+}
A diagramzip-render/src/units/d2.ts +14 -0
A diagramzip-render/src/units/structurizr.ts +68 -0
@@ -0,0 +1,68 @@
1+import { parseStructurizr, lowerStructurizr } from '../structurizr'2+import { RenderError, RequestError } from '../errors'3+import { parseUnitRenderRequest } from '../validation'4+5+const UNIT_ID = 'structurizr'6+const VERSION = 'structurizr@6.2.2/plantuml@1.2026.6-translation-1'7+const EXPOSED = 'Cache-Control, Content-Type, X-Diagram-Cache, X-Diagram-Engine, X-Diagram-Engine-Version, X-Diagram-Pipeline, X-Diagram-Renderer, X-Diagram-Unit, X-Renderer-Build'8+9+interface StructurizrEnv { RENDERER_BUILD: string; PLANTUML: Fetcher }10+11+function json(value: unknown, init: ResponseInit = {}): Response {12+ const headers = new Headers(init.headers)13+ headers.set('Content-Type', 'application/json; charset=utf-8')14+ headers.set('Cache-Control', 'no-store')15+ headers.set('Access-Control-Allow-Origin', '*')16+ return Response.json(value, { ...init, headers })17+}18+19+function errorResponse(error: unknown): Response {20+ if (error instanceof RequestError || error instanceof RenderError) return json({ error: { code: error.code, message: error.message } }, { status: error.status })21+ return json({ error: { code: 'internal_error', message: 'The Structurizr diagram could not be rendered.' } }, { status: 500 })22+}23+24+async function cacheKey(input: unknown, build: string): Promise<Request> {25+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(input)))26+ let binary = ''; for (const byte of new Uint8Array(digest)) binary += String.fromCharCode(byte)27+ const hash = btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')28+ return new Request(`https://diagramzip-structurizr-cache.invalid/${encodeURIComponent(build)}/${hash}`)29+}30+31+export default {32+ async fetch(request: Request, env: StructurizrEnv, ctx: ExecutionContext): Promise<Response> {33+ const url = new URL(request.url)34+ if (request.method === 'OPTIONS') return new Response(null, { status: 204, headers: { 'Access-Control-Allow-Headers': 'Accept, Content-Type', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Origin': '*', 'Access-Control-Max-Age': '86400' } })35+ try {36+ if (request.method === 'GET' && url.pathname === '/v1/health') return json({ ok: true, unit: UNIT_ID, id: UNIT_ID })37+ if (request.method === 'GET' && url.pathname === '/v1/capabilities') return json({38+ unit: UNIT_ID, id: UNIT_ID, kind: 'translate', format: 'svg', runtime: 'edge-js', version: VERSION,39+ build: env.RENDERER_BUILD, pipeline: [UNIT_ID, 'plantuml-family', 'plantuml'],40+ knownLosses: ['Only SVG is supported.', 'Structurizr scripts, docs, plugins, filesystem access, and arbitrary remote themes are rejected.', 'Layout and typography may differ from the legacy Structurizr exporter.'],41+ }, { headers: { 'Cache-Control': 'public, max-age=300' } })42+ if (request.method !== 'POST' || url.pathname !== '/v1/svg') return json({ error: { code: 'not_found', message: 'Route not found.' } }, { status: 404 })43+ const input = await parseUnitRenderRequest(request, 'structurizr')44+ const key = await cacheKey(input, env.RENDERER_BUILD)45+ let cached: Response | undefined46+ try { cached = await caches.default.match(key) } catch (error) { console.warn(JSON.stringify({ message: 'Structurizr cache read failed', error: String(error) })) }47+ if (cached) { const headers = new Headers(cached.headers); headers.set('X-Diagram-Cache', 'HIT'); return new Response(cached.body, { status: cached.status, headers }) }48+ const lowered = lowerStructurizr(parseStructurizr(input.source), input.options)49+ const downstream = await env.PLANTUML.fetch(new Request('https://plantuml.render.diagram.zip/v1/svg', {50+ method: 'POST', headers: { Accept: 'image/svg+xml', 'Content-Type': 'application/json' },51+ body: JSON.stringify({ source: lowered, format: 'svg', options: {}, metadata: input.metadata, presentation: input.presentation }),52+ signal: request.signal,53+ }))54+ if (!downstream.ok) return new Response(downstream.body, { status: downstream.status, headers: downstream.headers })55+ const headers = new Headers(downstream.headers)56+ headers.set('Access-Control-Allow-Origin', '*'); headers.set('Access-Control-Expose-Headers', EXPOSED)57+ headers.set('X-Diagram-Engine', 'structurizr'); headers.set('X-Diagram-Engine-Version', VERSION); headers.set('X-Diagram-Unit', UNIT_ID); headers.set('X-Diagram-Pipeline', `${UNIT_ID},plantuml-family,plantuml`); headers.set('X-Diagram-Renderer', 'edge-js'); headers.set('X-Renderer-Build', env.RENDERER_BUILD); headers.set('X-Diagram-Cache', 'MISS')58+ const response = new Response(downstream.body, { status: downstream.status, headers })59+ try {60+ if (typeof caches !== 'undefined') ctx.waitUntil(caches.default.put(key, response.clone()).catch(error => console.warn(JSON.stringify({ message: 'Structurizr cache write failed', error: String(error) }))))61+ } catch (error) { console.warn(JSON.stringify({ message: 'Structurizr cache write failed', error: String(error) })) }62+ return response63+ } catch (error) {64+ if (!(error instanceof RequestError || error instanceof RenderError)) console.error(JSON.stringify({ message: 'unhandled Structurizr error', error: error instanceof Error ? error.message : String(error) }))65+ return errorResponse(error)66+ }67+ },68+} satisfies ExportedHandler<StructurizrEnv>
M diagramzip-render/src/units/svgbob.ts +16 -9
M diagramzip-render/test/catalog.test.ts +21 -0
A diagramzip-render/test/d2.test.ts +26 -0
@@ -0,0 +1,26 @@
1+import { describe, expect, it } from 'vitest'2+import { d2Adapter } from '../src/adapters/edge/d2'3+import type { RenderRequest } from '../src/types'4+5+const request = (source: string, options: Record<string, string> = {}): RenderRequest => ({6+ engine: 'd2',7+ source, format: 'svg' as const, options,8+ metadata: { title: '', description: '' },9+ presentation: { background: '', padding: 0, frame: false },10+})11+12+describe('D2 dedicated Worker adapter', () => {13+ it('rejects Dagre/ELK and out-of-range animation options explicitly', async () => {14+ await expect(d2Adapter.render(request('a -> b', { layout: 'elk' }), new AbortController().signal)).rejects.toMatchObject({ code: 'unsupported_options', status: 400 })15+ await expect(d2Adapter.render(request('a -> b', { 'animate-interval': '0' }), new AbortController().signal)).rejects.toMatchObject({ code: 'invalid_options', status: 400 })16+ await expect(d2Adapter.render(request('a -> b', { 'animate-interval': '60001' }), new AbortController().signal)).rejects.toMatchObject({ code: 'invalid_options', status: 400 })17+ })18+19+ it('renders ordinary labels and animated multi-board SVG', async () => {20+ const result = await d2Adapter.render(request('a -> b: { style.animated: true }\nsteps: {\n 1: {\n next -> done\n }\n}', { 'animate-interval': '1000' }), new AbortController().signal)21+ expect(result.body).toMatch(/<svg[\s>]/i)22+ expect(result.body).toContain('@keyframes')23+ expect(result.body).toContain('animation:')24+ expect(result.body).toContain('next')25+ })26+})
A diagramzip-render/test/ditaa.test.ts +91 -0
@@ -0,0 +1,91 @@
1+import { describe, expect, it } from 'vitest'2+import { ditaaAdapter } from '../src/adapters/edge/ditaa'3+import type { RenderRequest } from '../src/types'4+5+const request = (source: string, options: Record<string, string> = {}): RenderRequest => ({6+ engine: 'ditaa', source, format: 'svg', options,7+ metadata: { title: '', description: '' },8+ presentation: { background: '', padding: 0, frame: false },9+})10+11+describe('Ditaa edge adapter', () => {12+ it('renders the documented ASCII fixture without Java', async () => {13+ const result = await ditaaAdapter.render(request('+--------+ +-------------+\n| User |------>| diagram.zip |\n+--------+ +-------------+'), new AbortController().signal)14+ expect(result.runtime).toBe('edge-wasm')15+ expect(result.body).toContain('<svg')16+ expect(result.body).toContain('User')17+ expect(result.body).toContain('diagram.zip')18+ })19+20+ it('renders the repository components fixture with its connector labels', async () => {21+ const source = ` +--------+22+ | |23+ | User |24+ | |25+ +--------+26+ ^27+ request |28+ v29+ +-------------+30+ | |31+ | Kroki |32+ | |---+33+ +-------------+ |34+ ^ ^ | inflate35+ | | |36+ v +---------+37+ +-------------+38+ | |39+ | Ditaa |40+ | |----+41+ +-------------+ |42+ ^ | process43+ | |44+ +-------+`45+ const result = await ditaaAdapter.render(request(source), new AbortController().signal)46+ expect(result.body).toContain('User')47+ expect(result.body).toContain('Kroki')48+ expect(result.body).toContain('Ditaa')49+ expect(result.body).toContain('inflate')50+ expect(result.body).toContain('process')51+ })52+53+ it('removes Ditaa shape/color directives while retaining labels', async () => {54+ const result = await ditaaAdapter.render(request('+-------+\n| API {cBLU} {d} |\n+-------+'), new AbortController().signal)55+ expect(result.body).toContain('API')56+ expect(result.body).not.toContain('cBLU')57+ expect(result.body).not.toContain('{d}')58+ })59+60+ it('supports bounded scale, tabs, and transparent/background options', async () => {61+ const result = await ditaaAdapter.render(request('\t+---+\n\t| A |\n\t+---+', { tabs: '4', scale: '2', background: 'F0F0F0' }), new AbortController().signal)62+ expect(result.body).toContain('#F0F0F0')63+ expect(result.body).toContain('width="')64+ })65+66+ it('accepts legacy appearance flags as documented no-ops', async () => {67+ const result = await ditaaAdapter.render(request('+---+\n| A |\n+---+', {68+ 'no-antialias': '',69+ 'no-separation': 'true',70+ 'round-corners': '1',71+ 'no-shadows': 'yes',72+ }), new AbortController().signal)73+ expect(result.body).toContain('<svg')74+ expect(result.body).toContain('A')75+ })76+77+ it('rejects unsupported and oversized inputs', async () => {78+ await expect(ditaaAdapter.render(request('A', { foo: 'bar' }), new AbortController().signal)).rejects.toMatchObject({ status: 400, code: 'unsupported_options' })79+ await expect(ditaaAdapter.render(request('A', { scale: 'NaN' }), new AbortController().signal)).rejects.toMatchObject({ status: 400, code: 'invalid_options' })80+ await expect(ditaaAdapter.render(request('A', { scale: '16.1' }), new AbortController().signal)).rejects.toMatchObject({ status: 400, code: 'invalid_options' })81+ await expect(ditaaAdapter.render(request('\tA', { tabs: '32.1' }), new AbortController().signal)).rejects.toMatchObject({ status: 400, code: 'invalid_options' })82+ await expect(ditaaAdapter.render(request('\tA', { tabs: '1.5' }), new AbortController().signal)).rejects.toMatchObject({ status: 400, code: 'invalid_options' })83+ await expect(ditaaAdapter.render(request('x'.repeat(270_000)), new AbortController().signal)).rejects.toMatchObject({ status: 413, code: 'source_too_large' })84+ })85+86+ it('accepts named CSS backgrounds without corrupting the color', async () => {87+ const result = await ditaaAdapter.render(request('A', { background: 'white' }), new AbortController().signal)88+ expect(result.body).toContain('white')89+ expect(result.body).not.toContain('#white')90+ })91+})
M diagramzip-render/test/gateway.test.ts +1 -1
A diagramzip-render/test/structurizr.test.ts +73 -0
@@ -0,0 +1,73 @@
1+import { describe, expect, it } from 'vitest'2+import { lowerStructurizr, parseStructurizr } from '../src/structurizr'3+import awsSource from '../../server/src/test/resources/aws.structurizr?raw'4+import bigbankSource from '../../server/src/test/resources/bigbank.structurizr?raw'5+import docsSource from '../../server/src/test/resources/docs.structurizr?raw'6+import gettingStartedSource from '../../server/src/test/resources/gettingstarted.structurizr?raw'7+import scriptSource from '../../server/src/test/resources/script.structurizr?raw'8+9+const fixtures = {10+ 'aws.structurizr': awsSource,11+ 'bigbank.structurizr': bigbankSource,12+ 'docs.structurizr': docsSource,13+ 'gettingstarted.structurizr': gettingStartedSource,14+ 'script.structurizr': scriptSource,15+}16+const fixture = (name: keyof typeof fixtures) => fixtures[name]17+18+describe('bounded Structurizr lowering', () => {19+ it('covers the gettingstarted and bigbank view families', () => {20+ const gettingStarted = parseStructurizr(fixture('gettingstarted.structurizr'))21+ expect(gettingStarted.views).toHaveLength(1)22+ expect(lowerStructurizr(gettingStarted, {})).toContain('@startuml')23+24+ const bigbank = parseStructurizr(fixture('bigbank.structurizr'))25+ expect(bigbank.views.map(view => view.key)).toEqual(expect.arrayContaining([26+ 'SystemLandscape', 'SystemContext', 'Containers', 'Components', 'SignIn', 'DevelopmentDeployment', 'LiveDeployment',27+ ]))28+ expect(lowerStructurizr(bigbank, { 'view-key': 'Components' })).toContain('s_signinController')29+ expect(lowerStructurizr(bigbank, { 'view-key': 'Components' })).toContain('Sign In Controller')30+ expect(lowerStructurizr(bigbank, { 'view-key': 'SystemContext' })).not.toContain('s_signinController')31+ expect(lowerStructurizr(bigbank, { 'view-key': 'Containers' })).not.toContain('s_signinController')32+ expect(lowerStructurizr(bigbank, { 'view-key': 'Containers' })).toContain('s_singlePageApplication')33+ const development = lowerStructurizr(bigbank, { 'view-key': 'DevelopmentDeployment' })34+ expect(development).not.toContain('Failover')35+ expect(lowerStructurizr(bigbank, { 'view-key': 'LiveDeployment' })).toContain('Failover')36+ expect(lowerStructurizr(bigbank, { output: 'legend' })).toContain('endlegend')37+ })38+39+ it('preserves deployment ownership and rejects unsafe directives/themes', () => {40+ const aws = parseStructurizr(fixture('aws.structurizr'))41+ expect(aws.elements.find(element => element.id === 'route53')?.parent).toBe('region')42+ expect(() => parseStructurizr(fixture('script.structurizr'))).toThrow(/directive.*!script/i)43+ expect(() => parseStructurizr(fixture('docs.structurizr'))).toThrow(/directive.*!docs/i)44+ expect(() => parseStructurizr(`workspace "x" {45+model {46+ a = softwareSystem "A"47+}48+views {49+ systemLandscape {50+ include *51+ }52+ theme https://evil.example/theme.json53+}54+}`)).toThrow(/theme/i)55+ })56+57+ it('does not interpolate hostile tags into PlantUML stereotypes', () => {58+ const source = `workspace "x" {59+model {60+ a = softwareSystem "A" "" "" "bad>>tag"61+}62+views {63+ systemLandscape {64+ include *65+ }66+}67+}`68+ const output = lowerStructurizr(parseStructurizr(source), {})69+ expect(output).not.toContain('bad>>tag')70+ expect(output).not.toContain('<<bad')71+ expect(() => parseStructurizr(source.replace('views {', 'styles { element "Person" { color #fff; } }\nviews {'))).toThrow()72+ })73+})
D diagramzip-render/wrangler.compatibility/d2.jsonc +0 -1
D diagramzip-render/wrangler.compatibility/ditaa.jsonc +0 -1
D diagramzip-render/wrangler.compatibility/structurizr.jsonc +0 -1
D diagramzip-render/wrangler.compatibility/symbolator.jsonc +0 -1
A diagramzip-render/wrangler.units/d2.jsonc +1 -0
A diagramzip-render/wrangler.units/structurizr.jsonc +14 -0
M diagramzip-render/wrangler.units/svgbob.jsonc +6 -3
M diagramzip/src/renderer-units.js +1 -1
M diagramzip/test/renderer-units.test.js +2 -1
M server/src/main/resources/web/diagramzip/assets/index-CzBQjn0p.js +5 -1
@@ -1632,13 +1632,17 @@ Rel(bob, tandem, "Completes tasks")
16321632 `,e.NAME_START_CHAR=`:A-Z_a-zƀ-ƖƘ-öø-˿Ͱ-ͽͿ-įææā€Œā€ā°-ā†ā°€-⿯、-ķŸæļ¤€-ļ·ļ·°-�𐀀-óÆææ`,e.NAME_CHAR=`-`+e.NAME_START_CHAR+`.0-9Ā·Ģ€-ͯ‿-⁀`,e.CHAR_RE=RegExp(`^[`+e.CHAR+`]$`,`u`),e.S_RE=RegExp(`^[`+e.S+`]+$`,`u`),e.NAME_START_CHAR_RE=RegExp(`^[`+e.NAME_START_CHAR+`]$`,`u`),e.NAME_CHAR_RE=RegExp(`^[`+e.NAME_CHAR+`]$`,`u`),e.NAME_RE=RegExp(`^[`+e.NAME_START_CHAR+`][`+e.NAME_CHAR+`]*$`,`u`),e.NMTOKEN_RE=RegExp(`^[`+e.NAME_CHAR+`]+$`,`u`);var t=9,n=10,r=13,i=32;e.S_LIST=[i,n,r,t];function a(e){return e>=i&&e<=55295||e===n||e===r||e===t||e>=57344&&e<=65533||e>=65536&&e<=1114111}e.isChar=a;function o(e){return e===i||e===n||e===r||e===t}e.isS=o;function s(e){return e>=65&&e<=90||e>=97&&e<=122||e===58||e===95||e===8204||e===8205||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=767||e>=880&&e<=893||e>=895&&e<=8191||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}e.isNameStartChar=s;function c(e){return s(e)||e>=48&&e<=57||e===45||e===46||e===183||e>=768&&e<=879||e>=8255&&e<=8256}e.isNameChar=c})),an=e((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.CHAR=`-ķŸæī€€-�𐀀-ōææ`,e.RESTRICTED_CHAR=`-\b\v\f--„†-Ÿ`,e.S=` \r16331633 `,e.NAME_START_CHAR=`:A-Z_a-zƀ-ƖƘ-öø-˿Ͱ-ͽͿ-įææā€Œā€ā°-ā†ā°€-⿯、-ķŸæļ¤€-ļ·ļ·°-�𐀀-óÆææ`,e.NAME_CHAR=`-`+e.NAME_START_CHAR+`.0-9Ā·Ģ€-ͯ‿-⁀`,e.CHAR_RE=RegExp(`^[`+e.CHAR+`]$`,`u`),e.RESTRICTED_CHAR_RE=RegExp(`^[`+e.RESTRICTED_CHAR+`]$`,`u`),e.S_RE=RegExp(`^[`+e.S+`]+$`,`u`),e.NAME_START_CHAR_RE=RegExp(`^[`+e.NAME_START_CHAR+`]$`,`u`),e.NAME_CHAR_RE=RegExp(`^[`+e.NAME_CHAR+`]$`,`u`),e.NAME_RE=RegExp(`^[`+e.NAME_START_CHAR+`][`+e.NAME_CHAR+`]*$`,`u`),e.NMTOKEN_RE=RegExp(`^[`+e.NAME_CHAR+`]+$`,`u`);var t=9,n=10,r=13,i=32;e.S_LIST=[i,n,r,t];function a(e){return e>=1&&e<=55295||e>=57344&&e<=65533||e>=65536&&e<=1114111}e.isChar=a;function o(e){return e>=1&&e<=8||e===11||e===12||e>=14&&e<=31||e>=127&&e<=132||e>=134&&e<=159}e.isRestrictedChar=o;function s(e){return e===9||e===10||e===13||e>31&&e<127||e===133||e>159&&e<=55295||e>=57344&&e<=65533||e>=65536&&e<=1114111}e.isCharAndNotRestricted=s;function c(e){return e===i||e===n||e===r||e===t}e.isS=c;function l(e){return e>=65&&e<=90||e>=97&&e<=122||e===58||e===95||e===8204||e===8205||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=767||e>=880&&e<=893||e>=895&&e<=8191||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}e.isNameStartChar=l;function u(e){return l(e)||e>=48&&e<=57||e===45||e===46||e===183||e>=768&&e<=879||e>=8255&&e<=8256}e.isNameChar=u})),on=e((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.NC_NAME_START_CHAR=`A-Z_a-zƀ-ƖƘ-öø-˿Ͱ-ͽͿ-įææā€Œ-ā€ā°-ā†ā°€-⿯、-ķŸæļ¤€-ļ·ļ·°-�𐀀-óÆææ`,e.NC_NAME_CHAR=`-`+e.NC_NAME_START_CHAR+`.0-9Ā·Ģ€-ͯ‿-⁀`,e.NC_NAME_START_CHAR_RE=RegExp(`^[`+e.NC_NAME_START_CHAR+`]$`,`u`),e.NC_NAME_CHAR_RE=RegExp(`^[`+e.NC_NAME_CHAR+`]$`,`u`),e.NC_NAME_RE=RegExp(`^[`+e.NC_NAME_START_CHAR+`][`+e.NC_NAME_CHAR+`]*$`,`u`);function t(e){return e>=65&&e<=90||e===95||e>=97&&e<=122||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=767||e>=880&&e<=893||e>=895&&e<=8191||e>=8204&&e<=8205||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}e.isNCNameStartChar=t;function n(e){return t(e)||e===45||e===46||e>=48&&e<=57||e===183||e>=768&&e<=879||e>=8255&&e<=8256}e.isNCNameChar=n})),sn=e((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.SaxesParser=e.EVENTS=void 0;var t=rn(),n=an(),r=on(),i=t.isS,a=t.isChar,o=t.isNameStartChar,s=t.isNameChar,c=t.S_LIST,l=t.NAME_RE,u=n.isChar,d=r.isNCNameStartChar,f=r.isNCNameChar,p=r.NC_NAME_RE,m=`http://www.w3.org/XML/1998/namespace`,h=`http://www.w3.org/2000/xmlns/`,ee={__proto__:null,xml:m,xmlns:h},te={__proto__:null,amp:`&`,gt:`>`,lt:`<`,quot:`"`,apos:`'`},g=-1,_=-2,ne=0,re=1,ie=2,ae=3,v=4,oe=5,se=6,ce=7,le=8,ue=9,de=10,fe=11,pe=12,y=13,me=14,he=15,ge=16,_e=17,ve=18,ye=19,be=20,xe=21,Se=22,Ce=23,we=24,Te=25,Ee=26,De=27,Oe=28,ke=29,Ae=30,je=31,Me=32,b=33,Ne=34,Pe=35,x=36,S=37,C=38,w=39,Fe=40,Ie=41,Le=42,Re=43,ze=44,Be=9,T=10,Ve=13,He=32,Ue=33,We=34,Ge=38,Ke=39,qe=45,E=47,Je=59,D=60,O=61,k=62,A=63,j=91,M=93,N=133,P=8232,F=e=>e===We||e===Ke,Ye=[We,Ke],Xe=[...Ye,j,k],Ze=[...Ye,D,M],Qe=[O,A,...c],I=[...c,k,Ge,D];function $e(e,t,n){switch(t){case`xml`:n!==m&&e.fail(`xml prefix must be bound to ${m}.`);break;case`xmlns`:n!==h&&e.fail(`xmlns prefix must be bound to ${h}.`)}switch(n){case h:e.fail(t===``?`the default namespace may not be set to ${n}.`:`may not assign a prefix (even "xmlns") to the URI \16341634 <<<<<<<< HEAD:server/src/main/resources/web/diagramzip/assets/index-CzBQjn0p.js1635+<<<<<<<< HEAD:server/src/main/resources/web/diagramzip/assets/index-CzBQjn0p.js16351636 ${h}.`);break;case m:switch(t){case`xml`:break;case``:e.fail(`the default namespace may not be set to ${n}.`);break;default:e.fail(`may not assign the xml namespace to another prefix.`)}}}function L(e,t){for(let n of Object.keys(t))$e(e,n,t[n])}var et=e=>p.test(e),tt=e=>l.test(e),R=0,nt=1,rt=2;e.EVENTS=[`xmldecl`,`text`,`processinginstruction`,`doctype`,`comment`,`opentagstart`,`attribute`,`opentag`,`closetag`,`cdata`,`error`,`end`,`ready`];var z={xmldecl:`xmldeclHandler`,text:`textHandler`,processinginstruction:`piHandler`,doctype:`doctypeHandler`,comment:`commentHandler`,opentagstart:`openTagStartHandler`,attribute:`attributeHandler`,opentag:`openTagHandler`,closetag:`closeTagHandler`,cdata:`cdataHandler`,error:`errorHandler`,end:`endHandler`,ready:`readyHandler`};e.SaxesParser=class{constructor(e){this.opt=e??{},this.fragmentOpt=!!this.opt.fragment;let t=this.xmlnsOpt=!!this.opt.xmlns;if(this.trackPosition=this.opt.position!==!1,this.fileName=this.opt.fileName,t){this.nameStartCheck=d,this.nameCheck=f,this.isName=et,this.processAttribs=this.processAttribsNS,this.pushAttrib=this.pushAttribNS,this.ns=Object.assign({__proto__:null},ee);let e=this.opt.additionalNamespaces;e!=null&&(L(this,e),Object.assign(this.ns,e))}else this.nameStartCheck=o,this.nameCheck=s,this.isName=tt,this.processAttribs=this.processAttribsPlain,this.pushAttrib=this.pushAttribPlain;this.stateTable=[this.sBegin,this.sBeginWhitespace,this.sDoctype,this.sDoctypeQuote,this.sDTD,this.sDTDQuoted,this.sDTDOpenWaka,this.sDTDOpenWakaBang,this.sDTDComment,this.sDTDCommentEnding,this.sDTDCommentEnded,this.sDTDPI,this.sDTDPIEnding,this.sText,this.sEntity,this.sOpenWaka,this.sOpenWakaBang,this.sComment,this.sCommentEnding,this.sCommentEnded,this.sCData,this.sCDataEnding,this.sCDataEnding2,this.sPIFirstChar,this.sPIRest,this.sPIBody,this.sPIEnding,this.sXMLDeclNameStart,this.sXMLDeclName,this.sXMLDeclEq,this.sXMLDeclValueStart,this.sXMLDeclValue,this.sXMLDeclSeparator,this.sXMLDeclEnding,this.sOpenTag,this.sOpenTagSlash,this.sAttrib,this.sAttribName,this.sAttribNameSawWhite,this.sAttribValue,this.sAttribValueQuoted,this.sAttribValueClosed,this.sAttribValueUnquoted,this.sCloseTag,this.sCloseTagSawWhite],this._init()}get closed(){return this._closed}_init(){var e;this.openWakaBang=``,this.text=``,this.name=``,this.piTarget=``,this.entity=``,this.q=null,this.tags=[],this.tag=null,this.topNS=null,this.chunk=``,this.chunkPosition=0,this.i=0,this.prevI=0,this.carriedFromPrevious=void 0,this.forbiddenState=R,this.attribList=[];let{fragmentOpt:t}=this;this.state=t?y:ne,this.reportedTextBeforeRoot=this.reportedTextAfterRoot=this.closedRoot=this.sawRoot=t,this.xmlDeclPossible=!t,this.xmlDeclExpects=[`version`],this.entityReturnState=void 0;let{defaultXMLVersion:n}=this.opt;if(n===void 0){if(this.opt.forceXMLVersion===!0)throw Error(`forceXMLVersion set but defaultXMLVersion is not set`);n=`1.0`}this.setXMLVersion(n),this.positionAtNewLine=0,this.doctype=!1,this._closed=!1,this.xmlDecl={version:void 0,encoding:void 0,standalone:void 0},this.line=1,this.column=0,this.ENTITIES=Object.create(te),(e=this.readyHandler)==null||e.call(this)}get position(){return this.chunkPosition+this.i}get columnIndex(){return this.position-this.positionAtNewLine}on(e,t){this[z[e]]=t}off(e){this[z[e]]=void 0}makeError(e){let t=this.fileName??``;return this.trackPosition&&(t.length>0&&(t+=`:`),t+=`${this.line}:${this.column}`),t.length>0&&(t+=`: `),Error(t+e)}fail(e){let t=this.makeError(e),n=this.errorHandler;if(n===void 0)throw t;return n(t),this}write(e){if(this.closed)return this.fail(`cannot write after close; assign an onready handler.`);let t=!1;e===null?(t=!0,e=``):typeof e==`object`&&(e=e.toString()),this.carriedFromPrevious!==void 0&&(e=`${this.carriedFromPrevious}${e}`,this.carriedFromPrevious=void 0);let n=e.length,r=e.charCodeAt(n-1);!t&&(r===Ve||r>=55296&&r<=56319)&&(this.carriedFromPrevious=e[n-1],n--,e=e.slice(0,n));let{stateTable:i}=this;for(this.chunk=e,this.i=0;this.i<n;)i[this.state].call(this);return this.chunkPosition+=n,t?this.end():this}close(){return this.write(null)}getCode10(){let{chunk:e,i:t}=this;if(this.prevI=t,this.i=t+1,t>=e.length)return g;let n=e.charCodeAt(t);if(this.column++,n<55296){if(n>=He||n===Be)return n;switch(n){case T:return this.line++,this.column=0,this.positionAtNewLine=this.position,T;case Ve:return e.charCodeAt(t+1)===T&&(this.i=t+2),this.line++,this.column=0,this.positionAtNewLine=this.position,_;default:return this.fail(`disallowed character.`),n}}if(n>56319)return n>=57344&&n<=65533||this.fail(`disallowed character.`),n;let r=65536+(n-55296)*1024+(e.charCodeAt(t+1)-56320);return this.i=t+2,r>1114111&&this.fail(`disallowed character.`),r}getCode11(){let{chunk:e,i:t}=this;if(this.prevI=t,this.i=t+1,t>=e.length)return g;let n=e.charCodeAt(t);if(this.column++,n<55296){if(n>31&&n<127||n>159&&n!==P||n===Be)return n;switch(n){case T:return this.line++,this.column=0,this.positionAtNewLine=this.position,T;case Ve:{let n=e.charCodeAt(t+1);(n===T||n===N)&&(this.i=t+2)}case N:case P:return this.line++,this.column=0,this.positionAtNewLine=this.position,_;default:return this.fail(`disallowed character.`),n}}if(n>56319)return n>=57344&&n<=65533||this.fail(`disallowed character.`),n;let r=65536+(n-55296)*1024+(e.charCodeAt(t+1)-56320);return this.i=t+2,r>1114111&&this.fail(`disallowed character.`),r}getCodeNorm(){let e=this.getCode();return e===_?T:e}unget(){this.i=this.prevI,this.column--}captureTo(e){let{i:t}=this,{chunk:n}=this;for(;;){let r=this.getCode(),i=r===_,a=i?T:r;if(a===g||e.includes(a))return this.text+=n.slice(t,this.prevI),a;i&&(this.text+=`${n.slice(t,this.prevI)}\n`,t=this.i)}}captureToChar(e){let{i:t}=this,{chunk:n}=this;for(;;){let r=this.getCode();switch(r){case _:this.text+=`${n.slice(t,this.prevI)}\n`,t=this.i,r=T;break;case g:return this.text+=n.slice(t),!1}if(r===e)return this.text+=n.slice(t,this.prevI),!0}}captureNameChars(){let{chunk:e,i:t}=this;for(;;){let n=this.getCode();if(n===g)return this.name+=e.slice(t),g;if(!s(n))return this.name+=e.slice(t,this.prevI),n===_?T:n}}skipSpaces(){for(;;){let e=this.getCodeNorm();if(e===g||!i(e))return e}}setXMLVersion(e){this.currentXMLVersion=e,e===`1.0`?(this.isChar=a,this.getCode=this.getCode10):(this.isChar=u,this.getCode=this.getCode11)}sBegin(){this.chunk.charCodeAt(0)===65279&&(this.i++,this.column++),this.state=re}sBeginWhitespace(){let e=this.i,t=this.skipSpaces();switch(this.prevI!==e&&(this.xmlDeclPossible=!1),t){case D:if(this.state=he,this.text.length!==0)throw Error(`no-empty text at start`);break;case g:break;default:this.unget(),this.state=y,this.xmlDeclPossible=!1}}sDoctype(){var e;let t=this.captureTo(Xe);switch(t){case k:(e=this.doctypeHandler)==null||e.call(this,this.text),this.text=``,this.state=y,this.doctype=!0;break;case g:break;default:this.text+=String.fromCodePoint(t),t===j?this.state=v:F(t)&&(this.state=ae,this.q=t)}}sDoctypeQuote(){let e=this.q;this.captureToChar(e)&&(this.text+=String.fromCodePoint(e),this.q=null,this.state=ie)}sDTD(){let e=this.captureTo(Ze);e!==g&&(this.text+=String.fromCodePoint(e),e===M?this.state=ie:e===D?this.state=se:F(e)&&(this.state=oe,this.q=e))}sDTDQuoted(){let e=this.q;this.captureToChar(e)&&(this.text+=String.fromCodePoint(e),this.state=v,this.q=null)}sDTDOpenWaka(){let e=this.getCodeNorm();switch(this.text+=String.fromCodePoint(e),e){case Ue:this.state=ce,this.openWakaBang=``;break;case A:this.state=fe;break;default:this.state=v}}sDTDOpenWakaBang(){let e=String.fromCodePoint(this.getCodeNorm()),t=this.openWakaBang+=e;this.text+=e,t!==`-`&&(this.state=t===`--`?le:v,this.openWakaBang=``)}sDTDComment(){this.captureToChar(qe)&&(this.text+=`-`,this.state=ue)}sDTDCommentEnding(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),this.state=e===qe?de:le}sDTDCommentEnded(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),e===k?this.state=v:(this.fail(`malformed comment.`),this.state=le)}sDTDPI(){this.captureToChar(A)&&(this.text+=`?`,this.state=pe)}sDTDPIEnding(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),e===k&&(this.state=v)}sText(){this.tags.length===0?this.handleTextOutsideRoot():this.handleTextInRoot()}sEntity(){let{i:e}=this,{chunk:t}=this;loop:for(;;)switch(this.getCode()){case _:this.entity+=`${t.slice(e,this.prevI)}\n`,e=this.i;break;case Je:{let{entityReturnState:n}=this,r=this.entity+t.slice(e,this.prevI);this.state=n;let i;r===``?(this.fail(`empty entity name.`),i=`&;`):(i=this.parseEntity(r),this.entity=``),(n!==y||this.textHandler!==void 0)&&(this.text+=i);break loop}case g:this.entity+=t.slice(e);break loop}}sOpenWaka(){let e=this.getCode();if(o(e))this.state=Ne,this.unget(),this.xmlDeclPossible=!1;else switch(e){case E:this.state=Re,this.xmlDeclPossible=!1;break;case Ue:this.state=ge,this.openWakaBang=``,this.xmlDeclPossible=!1;break;case A:this.state=Ce;break;default:this.fail(`disallowed character in tag name`),this.state=y,this.xmlDeclPossible=!1}}sOpenWakaBang(){switch(this.openWakaBang+=String.fromCodePoint(this.getCodeNorm()),this.openWakaBang){case`[CDATA[`:!this.sawRoot&&!this.reportedTextBeforeRoot&&(this.fail(`text data outside of root node.`),this.reportedTextBeforeRoot=!0),this.closedRoot&&!this.reportedTextAfterRoot&&(this.fail(`text data outside of root node.`),this.reportedTextAfterRoot=!0),this.state=be,this.openWakaBang=``;break;case`--`:this.state=_e,this.openWakaBang=``;break;case`DOCTYPE`:this.state=ie,(this.doctype||this.sawRoot)&&this.fail(`inappropriately located doctype declaration.`),this.openWakaBang=``;break;default:this.openWakaBang.length>=7&&this.fail(`incorrect syntax.`)}}sComment(){this.captureToChar(qe)&&(this.state=ve)}sCommentEnding(){var e;let t=this.getCodeNorm();t===qe?(this.state=ye,(e=this.commentHandler)==null||e.call(this,this.text),this.text=``):(this.text+=`-${String.fromCodePoint(t)}`,this.state=_e)}sCommentEnded(){let e=this.getCodeNorm();e===k?this.state=y:(this.fail(`malformed comment.`),this.text+=`--${String.fromCodePoint(e)}`,this.state=_e)}sCData(){this.captureToChar(M)&&(this.state=xe)}sCDataEnding(){let e=this.getCodeNorm();e===M?this.state=Se:(this.text+=`]${String.fromCodePoint(e)}`,this.state=be)}sCDataEnding2(){var e;let t=this.getCodeNorm();switch(t){case k:(e=this.cdataHandler)==null||e.call(this,this.text),this.text=``,this.state=y;break;case M:this.text+=`]`;break;default:this.text+=`]]${String.fromCodePoint(t)}`,this.state=be}}sPIFirstChar(){let e=this.getCodeNorm();this.nameStartCheck(e)?(this.piTarget+=String.fromCodePoint(e),this.state=we):e===A||i(e)?(this.fail(`processing instruction without a target.`),this.state=e===A?Ee:Te):(this.fail(`disallowed character in processing instruction name.`),this.piTarget+=String.fromCodePoint(e),this.state=we)}sPIRest(){let{chunk:e,i:t}=this;for(;;){let n=this.getCodeNorm();if(n===g){this.piTarget+=e.slice(t);return}if(!this.nameCheck(n)){this.piTarget+=e.slice(t,this.prevI);let r=n===A;r||i(n)?this.piTarget===`xml`?(this.xmlDeclPossible||this.fail(`an XML declaration must be at the start of the document.`),this.state=r?b:De):this.state=r?Ee:Te:(this.fail(`disallowed character in processing instruction name.`),this.piTarget+=String.fromCodePoint(n));break}}}sPIBody(){if(this.text.length===0){let e=this.getCodeNorm();e===A?this.state=Ee:i(e)||(this.text=String.fromCodePoint(e))}else this.captureToChar(A)&&(this.state=Ee)}sPIEnding(){var e;let t=this.getCodeNorm();if(t===k){let{piTarget:t}=this;t.toLowerCase()===`xml`&&this.fail(`the XML declaration must appear at the start of the document.`),(e=this.piHandler)==null||e.call(this,{target:t,body:this.text}),this.piTarget=this.text=``,this.state=y}else t===A?this.text+=`?`:(this.text+=`?${String.fromCodePoint(t)}`,this.state=Te);this.xmlDeclPossible=!1}sXMLDeclNameStart(){let e=this.skipSpaces();if(e===A){this.state=b;return}e!==g&&(this.state=Oe,this.name=String.fromCodePoint(e))}sXMLDeclName(){let e=this.captureTo(Qe);if(e===A){this.state=b,this.name+=this.text,this.text=``,this.fail(`XML declaration is incomplete.`);return}if(i(e)||e===O){if(this.name+=this.text,this.text=``,!this.xmlDeclExpects.includes(this.name))switch(this.name.length){case 0:this.fail(`did not expect any more name/value pairs.`);break;case 1:this.fail(`expected the name ${this.xmlDeclExpects[0]}.`);break;default:this.fail(`expected one of ${this.xmlDeclExpects.join(`, `)}`)}this.state=e===O?Ae:ke}}sXMLDeclEq(){let e=this.getCodeNorm();if(e===A){this.state=b,this.fail(`XML declaration is incomplete.`);return}i(e)||(e!==O&&this.fail(`value required.`),this.state=Ae)}sXMLDeclValueStart(){let e=this.getCodeNorm();if(e===A){this.state=b,this.fail(`XML declaration is incomplete.`);return}i(e)||(F(e)?this.q=e:(this.fail(`value must be quoted.`),this.q=He),this.state=je)}sXMLDeclValue(){let e=this.captureTo([this.q,A]);if(e===A){this.state=b,this.text=``,this.fail(`XML declaration is incomplete.`);return}if(e===g)return;let t=this.text;switch(this.text=``,this.name){case`version`:{this.xmlDeclExpects=[`encoding`,`standalone`];let e=t;this.xmlDecl.version=e,/^1\.[0-9]+$/.test(e)?this.opt.forceXMLVersion||this.setXMLVersion(e):this.fail(`version number must match /^1\\.[0-9]+$/.`);break}case`encoding`:/^[A-Za-z][A-Za-z0-9._-]*$/.test(t)||this.fail(`encoding value must match /^[A-Za-z0-9][A-Za-z0-9._-]*$/.`),this.xmlDeclExpects=[`standalone`],this.xmlDecl.encoding=t;break;case`standalone`:t!==`yes`&&t!==`no`&&this.fail(`standalone value must match "yes" or "no".`),this.xmlDeclExpects=[],this.xmlDecl.standalone=t}this.name=``,this.state=Me}sXMLDeclSeparator(){let e=this.getCodeNorm();if(e===A){this.state=b;return}i(e)||(this.fail(`whitespace required.`),this.unget()),this.state=De}sXMLDeclEnding(){var e;this.getCodeNorm()===k?(this.piTarget===`xml`?this.name!==`version`&&this.xmlDeclExpects.includes(`version`)&&this.fail(`XML declaration must contain a version.`):this.fail(`processing instructions are not allowed before root.`),(e=this.xmldeclHandler)==null||e.call(this,this.xmlDecl),this.name=``,this.piTarget=this.text=``,this.state=y):this.fail(`The character ? is disallowed anywhere in XML declarations.`),this.xmlDeclPossible=!1}sOpenTag(){var e;let t=this.captureNameChars();if(t===g)return;let n=this.tag={name:this.name,attributes:Object.create(null)};switch(this.name=``,this.xmlnsOpt&&(this.topNS=n.ns=Object.create(null)),(e=this.openTagStartHandler)==null||e.call(this,n),this.sawRoot=!0,!this.fragmentOpt&&this.closedRoot&&this.fail(`documents may contain only one root.`),t){case k:this.openTag();break;case E:this.state=Pe;break;default:i(t)||this.fail(`disallowed character in tag name.`),this.state=x}}sOpenTagSlash(){this.getCode()===k?this.openSelfClosingTag():(this.fail(`forward-slash in opening tag not followed by >.`),this.state=x)}sAttrib(){let e=this.skipSpaces();e!==g&&(o(e)?(this.unget(),this.state=S):e===k?this.openTag():e===E?this.state=Pe:this.fail(`disallowed character in attribute name.`))}sAttribName(){let e=this.captureNameChars();e===O?this.state=w:i(e)?this.state=C:e===k?(this.fail(`attribute without value.`),this.pushAttrib(this.name,this.name),this.name=this.text=``,this.openTag()):e!==g&&this.fail(`disallowed character in attribute name.`)}sAttribNameSawWhite(){let e=this.skipSpaces();switch(e){case g:return;case O:this.state=w;break;default:this.fail(`attribute without value.`),this.text=``,this.name=``,e===k?this.openTag():o(e)?(this.unget(),this.state=S):(this.fail(`disallowed character in attribute name.`),this.state=x)}}sAttribValue(){let e=this.getCodeNorm();F(e)?(this.q=e,this.state=Fe):i(e)||(this.fail(`unquoted attribute value.`),this.state=Le,this.unget())}sAttribValueQuoted(){let{q:e,chunk:t}=this,{i:n}=this;for(;;)switch(this.getCode()){case e:this.pushAttrib(this.name,this.text+t.slice(n,this.prevI)),this.name=this.text=``,this.q=null,this.state=Ie;return;case Ge:this.text+=t.slice(n,this.prevI),this.state=me,this.entityReturnState=Fe;return;case T:case _:case Be:this.text+=`${t.slice(n,this.prevI)} `,n=this.i;break;case D:this.text+=t.slice(n,this.prevI),this.fail(`disallowed character.`);return;case g:this.text+=t.slice(n);return}}sAttribValueClosed(){let e=this.getCodeNorm();i(e)?this.state=x:e===k?this.openTag():e===E?this.state=Pe:o(e)?(this.fail(`no whitespace between attributes.`),this.unget(),this.state=S):this.fail(`disallowed character in attribute name.`)}sAttribValueUnquoted(){let e=this.captureTo(I);switch(e){case Ge:this.state=me,this.entityReturnState=Le;break;case D:this.fail(`disallowed character.`);break;case g:break;default:this.text.includes(`]]>`)&&this.fail(`the string "]]>" is disallowed in char data.`),this.pushAttrib(this.name,this.text),this.name=this.text=``,e===k?this.openTag():this.state=x}}sCloseTag(){let e=this.captureNameChars();e===k?this.closeTag():i(e)?this.state=ze:e!==g&&this.fail(`disallowed character in closing tag.`)}sCloseTagSawWhite(){switch(this.skipSpaces()){case k:this.closeTag();break;case g:break;default:this.fail(`disallowed character in closing tag.`)}}handleTextInRoot(){let{i:e,forbiddenState:t}=this,{chunk:n,textHandler:r}=this;scanLoop:for(;;)switch(this.getCode()){case D:if(this.state=he,r!==void 0){let{text:t}=this,i=n.slice(e,this.prevI);t.length===0?i.length!==0&&r(i):(r(t+i),this.text=``)}t=R;break scanLoop;case Ge:this.state=me,this.entityReturnState=y,r!==void 0&&(this.text+=n.slice(e,this.prevI)),t=R;break scanLoop;case M:switch(t){case R:t=nt;break;case nt:t=rt;break;case rt:break;default:throw Error(`impossible state`)}break;case k:t===rt&&this.fail(`the string "]]>" is disallowed in char data.`),t=R;break;case _:r!==void 0&&(this.text+=`${n.slice(e,this.prevI)}\n`),e=this.i,t=R;break;case g:r!==void 0&&(this.text+=n.slice(e));break scanLoop;default:t=R}this.forbiddenState=t}handleTextOutsideRoot(){let{i:e}=this,{chunk:t,textHandler:n}=this,r=!1;outRootLoop:for(;;){let a=this.getCode();switch(a){case D:if(this.state=he,n!==void 0){let{text:r}=this,i=t.slice(e,this.prevI);r.length===0?i.length!==0&&n(i):(n(r+i),this.text=``)}break outRootLoop;case Ge:this.state=me,this.entityReturnState=y,n!==void 0&&(this.text+=t.slice(e,this.prevI)),r=!0;break outRootLoop;case _:n!==void 0&&(this.text+=`${t.slice(e,this.prevI)}\n`),e=this.i;break;case g:n!==void 0&&(this.text+=t.slice(e));break outRootLoop;default:i(a)||(r=!0)}}r&&(!this.sawRoot&&!this.reportedTextBeforeRoot&&(this.fail(`text data outside of root node.`),this.reportedTextBeforeRoot=!0),this.closedRoot&&!this.reportedTextAfterRoot&&(this.fail(`text data outside of root node.`),this.reportedTextAfterRoot=!0))}pushAttribNS(e,t){var n;let{prefix:r,local:i}=this.qname(e),a={name:e,prefix:r,local:i,value:t};if(this.attribList.push(a),(n=this.attributeHandler)==null||n.call(this,a),r===`xmlns`){let e=t.trim();this.currentXMLVersion===`1.0`&&e===``&&this.fail(`invalid attempt to undefine prefix in XML 1.0`),this.topNS[i]=e,$e(this,i,e)}else if(e===`xmlns`){let e=t.trim();this.topNS[``]=e,$e(this,``,e)}}pushAttribPlain(e,t){var n;let r={name:e,value:t};this.attribList.push(r),(n=this.attributeHandler)==null||n.call(this,r)}end(){var e,t;this.sawRoot||this.fail(`document must contain a root element.`);let{tags:n}=this;for(;n.length>0;){let e=n.pop();this.fail(`unclosed tag: ${e.name}`)}this.state!==ne&&this.state!==y&&this.fail(`unexpected end.`);let{text:r}=this;return r.length!==0&&((e=this.textHandler)==null||e.call(this,r),this.text=``),this._closed=!0,(t=this.endHandler)==null||t.call(this),this._init(),this}resolve(e){var t;let n=this.topNS[e];if(n!==void 0)return n;let{tags:r}=this;for(let t=r.length-1;t>=0;t--)if(n=r[t].ns[e],n!==void 0)return n;return n=this.ns[e],n===void 0?(t=this.opt).resolvePrefix?.call(t,e):n}qname(e){let t=e.indexOf(`:`);if(t===-1)return{prefix:``,local:e};let n=e.slice(t+1),r=e.slice(0,t);return(r===``||n===``||n.includes(`:`))&&this.fail(`malformed name: ${e}.`),{prefix:r,local:n}}processAttribsNS(){let{attribList:e}=this,t=this.tag;{let{prefix:e,local:n}=this.qname(t.name);t.prefix=e,t.local=n;let r=t.uri=this.resolve(e)??``;e!==``&&(e===`xmlns`&&this.fail(`tags may not have "xmlns" as prefix.`),r===``&&(this.fail(`unbound namespace prefix: ${JSON.stringify(e)}.`),t.uri=e))}if(e.length===0)return;let{attributes:n}=t,r=new Set;for(let t of e){let{name:e,prefix:i,local:a}=t,o,s;i===``?(o=e===`xmlns`?h:``,s=e):(o=this.resolve(i),o===void 0&&(this.fail(`unbound namespace prefix: ${JSON.stringify(i)}.`),o=i),s=`{${o}}${a}`),r.has(s)&&this.fail(`duplicate attribute: ${s}.`),r.add(s),t.uri=o,n[e]=t}this.attribList=[]}processAttribsPlain(){let{attribList:e}=this,t=this.tag.attributes;for(let{name:n,value:r}of e)t[n]!==void 0&&this.fail(`duplicate attribute: ${n}.`),t[n]=r;this.attribList=[]}openTag(){var e;this.processAttribs();let{tags:t}=this,n=this.tag;n.isSelfClosing=!1,(e=this.openTagHandler)==null||e.call(this,n),t.push(n),this.state=y,this.name=``}openSelfClosingTag(){var e,t;this.processAttribs();let{tags:n}=this,r=this.tag;r.isSelfClosing=!0,(e=this.openTagHandler)==null||e.call(this,r),(t=this.closeTagHandler)==null||t.call(this,r),(this.tag=n[n.length-1]??null)===null&&(this.closedRoot=!0),this.state=y,this.name=``}closeTag(){let{tags:e,name:t}=this;if(this.state=y,this.name=``,t===``){this.fail(`weird empty close tag.`),this.text+=`</>`;return}let n=this.closeTagHandler,r=e.length;for(;r-->0;){let r=this.tag=e.pop();if(this.topNS=r.ns,n?.(r),r.name===t)break;this.fail(`unexpected close tag.`)}r===0?this.closedRoot=!0:r<0&&(this.fail(`unmatched closing tag: ${t}.`),this.text+=`</${t}>`)}parseEntity(e){if(e[0]!==`#`){let t=this.ENTITIES[e];return t===void 0?(this.fail(this.isName(e)?`undefined entity.`:`disallowed character in entity name.`),`&${e};`):t}let t=NaN;return e[1]===`x`&&/^#x[0-9a-f]+$/i.test(e)?t=parseInt(e.slice(2),16):/^#[0-9]+$/.test(e)&&(t=parseInt(e.slice(1),10)),this.isChar(t)?String.fromCodePoint(t):(this.fail(`malformed character entity.`),`&${e};`)}}}))(),cn=4194304,ln=new Set([`script`,`iframe`,`object`,`embed`,`audio`,`video`]),un=new Set([`foreignobject`,`div`,`span`,`p`,`br`,`b`,`strong`,`i`,`em`,`small`,`sub`,`sup`,`code`,`ul`,`ol`,`li`,`a`]),dn=/^data:image\/(?:png|jpeg|gif|webp);base64,[a-z0-9+/=\s]+$/i,fn=/^data:(?:font\/(?:woff2?|opentype|truetype)|application\/(?:x-)?font-woff2?);base64,[a-z0-9+/=\s]+$/i,pn=class extends Error{constructor(e){super(e),this.name=`ClientSvgError`}};function mn(e){let t=e.trim();return t.startsWith(`#`)||dn.test(t)||fn.test(t)}function hn(e){if(/@import|expression\s*\(|javascript\s*:|data\s*:\s*text\/html|behavior\s*:|-moz-binding/i.test(e))return!0;let t=/url\s*\(\s*(['"]?)(.*?)\1\s*\)/gi;for(let n of e.matchAll(t))if(!mn(n[2]??``))return!0;return/url\s*\(/i.test(e.replace(t,``))}function gn(e){let t=e.trim();return t.startsWith(`#`)||dn.test(t)}function _n(e,t){let n=e.toLowerCase();return n.startsWith(`on`)?!1:n===`href`||n===`xlink:href`||n===`src`?gn(t):[`style`,`filter`,`fill`,`stroke`,`clip-path`,`mask`].includes(n)?!hn(t):n!==`xml:base`&&!/javascript\s*:/i.test(t)}function vn(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`)}function yn(e){return vn(e).replaceAll(`"`,`&quot;`)}function bn(e){if(e.type===`text`)return vn(e.value);let t=[...e.attributes].map(([e,t])=>` ${e}="${yn(t)}"`).join(``);return`<${e.name}${t}>${e.children.map(bn).join(``)}</${e.name}>`}function xn(e){return e.name.toLowerCase().split(`:`).at(-1)??e.name.toLowerCase()}function Sn(e){if(e===void 0||!/^-?\d+(?:\.\d+)?(?:px)?$/.test(e.trim()))return null;let t=Number.parseFloat(e);return Number.isFinite(t)&&t>0?t:null}function Cn(e){let t=e.attributes.get(`viewBox`)?.trim().split(/[\s,]+/).map(Number);if(t?.length===4&&t.every(Number.isFinite)&&t[2]>0&&t[3]>0)return t;let n=Sn(e.attributes.get(`width`)),r=Sn(e.attributes.get(`height`));return n!==null&&r!==null?[0,0,n,r]:null}function wn(e,t){let n=e.attributes.get(`style`);if(!n)return null;for(let e of n.split(`;`)){let n=e.indexOf(`:`);if(n>=0&&e.slice(0,n).trim().toLowerCase()===t)return e.slice(n+1).trim()}return null}function Tn(e){let t=(e.attributes.get(`fill`)??wn(e,`fill`)??``).replaceAll(` `,``).toLowerCase();return t===`white`||t===`#fff`||t===`#ffffff`||t===`rgb(255,255,255)`}function En(e){if(e===void 0||!/^-?\d+(?:\.\d+)?$/.test(e.trim()))return null;let t=Number(e);return Number.isFinite(t)?t:null}function Dn(e,t){let n=e.attributes.get(`width`)?.trim(),r=e.attributes.get(`height`)?.trim();if(n===`100%`&&r===`100%`)return!0;let i=En(e.attributes.get(`x`)??`0`),a=En(e.attributes.get(`y`)??`0`),o=En(n),s=En(r);if([i,a,o,s].some(e=>e===null))return!1;let[c,l,u,d]=t,f=.01;return i<=c+f&&a<=l+f&&i+o>=c+u-f&&a+s>=l+d-f}function On(e,t,n){let r=xn(e),i=new Set((e.attributes.get(`class`)??``).toLowerCase().split(/\s+/));return r===`rect`&&i.has(`backdrop`)||r===`rect`&&t!==null&&Tn(e)&&Dn(e,t)?!0:n===`graphviz`&&r===`polygon`&&Tn(e)&&(e.attributes.get(`stroke`)??``).toLowerCase()===`none`}function kn(e,t,n){let r=xn(e)===`svg`?Cn(e)??t:t;e.children=e.children.filter(e=>e.type===`text`||!On(e,r,n)&&(kn(e,r,n),!0))}function An(e,t){let n=(e.attributes.get(`style`)??``).split(`;`).map(e=>e.trim()).filter(Boolean).filter(e=>!/^background(?:-color)?\s*:/i.test(e));n.push(`background-color:${t}`),e.attributes.set(`style`,`${n.join(`;`)};`)}function jn(e,t=[],n=[]){return{type:`element`,name:e,attributes:new Map(t),children:n}}function Mn(e,t,n,r){let i=[],a=null;t.title&&i.push(jn(`title`,[],[{type:`text`,value:t.title}])),t.description&&i.push(jn(`desc`,[],[{type:`text`,value:t.description}]));let o=Cn(e);if((n.padding||n.background||n.frame)&&o===null)throw new pn(`Renderer output has no usable SVG dimensions for presentation settings.`);if(o!==null){let[t,r,s,c]=o,l=n.padding,u=[t-l,r-l,s+l*2,c+l*2];e.attributes.set(`viewBox`,u.join(` `)),e.attributes.has(`width`)&&e.attributes.set(`width`,String(u[2])),e.attributes.has(`height`)&&e.attributes.set(`height`,String(u[3])),n.background&&i.push(jn(`rect`,[[`x`,String(u[0])],[`y`,String(u[1])],[`width`,String(u[2])],[`height`,String(u[3])],[`fill`,n.background]])),n.frame&&(a=jn(`rect`,[[`x`,String(u[0]+.5)],[`y`,String(u[1]+.5)],[`width`,String(Math.max(0,u[2]-1))],[`height`,String(Math.max(0,u[3]-1))],[`fill`,`none`],[`stroke`,`#000000`],[`stroke-width`,`1`],[`vector-effect`,`non-scaling-stroke`]]))}n.background&&(kn(e,o,r),An(e,n.background)),e.children.unshift(...i),a&&e.children.push(a)}function Nn(e,t,n,r){if(e.length>cn)throw new pn(`Rendered SVG is too large.`);let i=null,a=[],o=0,s=null,c=new sn.SaxesParser({xmlns:!0});c.on(`doctype`,e=>{e.includes(`[`)&&(s=Error(`Internal DOCTYPE subsets are not allowed`))}),c.on(`opentag`,e=>{let t=e.local.toLowerCase(),n=t===`foreignobject`||a.some(e=>xn(e)===`foreignobject`);if(o>0||ln.has(t)||n&&!un.has(t)){o++;return}let r=jn(e.name);for(let t of Object.values(e.attributes))_n(t.name,t.value)&&r.attributes.set(t.name,t.value);let c=a.at(-1);c?c.children.push(r):i===null?i=r:s=Error(`Multiple root elements`),a.push(r)});let l=e=>{if(o>0||e===``)return;let t=a.at(-1);if(t){if(xn(t)===`style`&&hn(e))throw new pn(`Renderer output contains unsafe CSS.`);t.children.push({type:`text`,value:e})}else e.trim()!==``&&(s=Error(`Text outside root element`))};c.on(`text`,l),c.on(`cdata`,l),c.on(`closetag`,()=>{o>0?o--:a.pop()}),c.on(`error`,e=>{s=e});try{c.write(e).close()}catch(e){if(e instanceof pn)throw e;s=e}if(s!==null||i===null||xn(i)!==`svg`)throw new pn(`Renderer returned invalid SVG.`);return Mn(i,t,n,r),bn(i)}var Pn=Object.freeze({plantuml:`https://plantuml.render.diagram.zip/v1/svg`,graphviz:`https://graphviz.render.diagram.zip/v1/svg`,d2:`https://d2.render.diagram.zip/v1/svg`,c4plantuml:`https://c4plantuml.render.diagram.zip/v1/svg`,blockdiag:`https://blockdiag.render.diagram.zip/v1/svg`,seqdiag:`https://seqdiag.render.diagram.zip/v1/svg`,actdiag:`https://actdiag.render.diagram.zip/v1/svg`,nwdiag:`https://nwdiag.render.diagram.zip/v1/svg`,packetdiag:`https://packetdiag.render.diagram.zip/v1/svg`,rackdiag:`https://rackdiag.render.diagram.zip/v1/svg`,bytefield:`https://bytefield.render.diagram.zip/v1/svg`,dbml:`https://dbml.render.diagram.zip/v1/svg`,diagramsnet:`https://diagramsnet.render.diagram.zip/v1/svg`,ditaa:`https://ditaa.render.diagram.zip/v1/svg`,erd:`https://erd.render.diagram.zip/v1/svg`,goat:`https://goat.render.diagram.zip/v1/svg`,nomnoml:`https://nomnoml.render.diagram.zip/v1/svg`,pikchr:`https://pikchr.render.diagram.zip/v1/svg`,structurizr:`https://structurizr.render.diagram.zip/v1/svg`,svgbob:`https://svgbob.render.diagram.zip/v1/svg`,symbolator:`https://symbolator.render.diagram.zip/v1/svg`,tikz:`https://tikz.render.diagram.zip/v1/svg`,umlet:`https://umlet.render.diagram.zip/v1/svg`,vega:`https://vega.render.diagram.zip/v1/svg`,vegalite:`https://vegalite.render.diagram.zip/v1/svg`,wavedrom:`https://wavedrom.render.diagram.zip/v1/svg`,wireviz:`https://wireviz.render.diagram.zip/v1/svg`}),Fn=Object.freeze([`mermaid`,`bpmn`,`excalidraw`,`bytefield`,`nomnoml`,`vega`,`vegalite`,`wavedrom`,`blockdiag`,`seqdiag`,`actdiag`,`nwdiag`,`packetdiag`,`rackdiag`,`graphviz`,`erd`,`pikchr`,`svgbob`,`wireviz`]);function In(e){return Fn.includes(e)}function Ln(e){return Pn[e]??null}function W(e,t,n){return Math.min(Math.max(e,t),n)}var Rn=class{constructor({stage:e,image:t,status:n,minimap:r,minimapImage:i,minimapViewport:a}){this.stage=e,this.image=t,this.status=n,this.minimap=r,this.minimapImage=i,this.minimapViewport=a,this.scale=1,this.x=0,this.y=0,this.objectUrl=null,this.abortController=null,this.requestNumber=0,this.pendingRender=null,this.renderLoop=null,this.latestRenderKey=null,this.latestSvgBlob=null,this.latestRendererIdentity=null,this.imageFallbackUrl=null,this.activeImageUrl=null,this.drag=null,this.image.addEventListener(`load`,()=>{this.image.src===this.activeImageUrl&&(this.imageFallbackUrl=null,this.setStatus(`Rendered`,`ready`),this.fit())}),this.image.addEventListener(`error`,()=>this.retryBlockedImage()),this.stage.addEventListener(`pointerdown`,e=>this.startPan(e)),this.stage.addEventListener(`pointermove`,e=>this.pan(e)),this.stage.addEventListener(`pointerup`,e=>this.endPan(e)),this.stage.addEventListener(`pointercancel`,e=>this.endPan(e)),this.stage.addEventListener(`wheel`,e=>this.wheelZoom(e),{passive:!1}),this.minimap.addEventListener(`pointerdown`,e=>this.moveFromMinimap(e)),new ResizeObserver(()=>this.updateTransform()).observe(this.stage)}render({type:e,source:t,options:n={},meta:r={},presentation:i={}}){if(!t.trim()){this.requestNumber++,this.pendingRender=null,this.abortController?.abort(),this.setStatus(`Write something to render.`,`idle`);return}let a=JSON.stringify({type:e,source:t,options:n,meta:r,presentation:i});return this.latestRenderKey===a&&this.latestSvgBlob?Promise.resolve():(this.pendingRender={type:e,source:t,options:n,meta:r,presentation:i,renderKey:a,requestNumber:++this.requestNumber},this.abortController?.abort(),this.renderLoop||=this.drainRenderQueue(),this.renderLoop)}async drainRenderQueue(){try{for(;this.pendingRender;){let e=this.pendingRender;this.pendingRender=null,await this.performRender(e)}}finally{this.renderLoop=null,this.pendingRender&&(this.renderLoop=this.drainRenderQueue())}}async performRender({type:e,source:t,options:n,meta:r,presentation:i,renderKey:a,requestNumber:o}){let s=new AbortController;this.abortController=s,this.setStatus(`Rendering…`,`loading`),this.stage.style.setProperty(`--render-background`,`var(--preview-bg)`);try{let c=nn(e),l;if(c)try{let a=await c.render({type:e,source:t,options:n},s.signal);l={body:Nn(a.body,r,i,e),identity:{unit:e,build:a.build||a.version,pipeline:Array.isArray(a.pipeline)?a.pipeline:[e]}},this.status.dataset.cache=`browser`,this.status.dataset.renderer=e}catch(a){if(a.name===`AbortError`||In(e))throw a;l=await this.renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},s.signal)}else l=await this.renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},s.signal);if(o!==this.requestNumber)return;let{blob:u,background:d}=this.normalizedSvgBlob(l.body);this.latestRenderKey=a,this.latestSvgBlob=u,this.latestRendererIdentity=l.identity,this.imageFallbackUrl=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(await u.text())}`,this.stage.style.setProperty(`--render-background`,d);let f=URL.createObjectURL(u),p=this.objectUrl;this.objectUrl=f,this.activeImageUrl=f,this.image.src=f,this.minimapImage.src=f,this.image.hidden=!1,this.setStatus(`Loading image…`,`loading`),p&&URL.revokeObjectURL(p)}catch(e){if(e.name===`AbortError`)return;this.setStatus(e.message||`Could not render this diagram.`,`error`)}finally{this.abortController===s&&(this.abortController=null)}}async renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},a){let o=Ln(e);if(o){try{let s=await this.renderRequest(o,{source:t,format:`svg`,options:n,metadata:r,presentation:i},a);if(s.ok||s.status<500)return await this.renderResponse(s,e);await s.body?.cancel()}catch(t){if(t.name===`AbortError`||In(e))throw t}if(In(e))throw Error(`The ${e} renderer unit is unavailable.`)}let s=await this.renderRequest(`/render/v1/svg`,{engine:e,source:t,format:`svg`,options:n,metadata:r,presentation:i},a);return this.renderResponse(s,e)}renderRequest(e,t,n){return fetch(e,{method:`POST`,headers:{Accept:`image/svg+xml`,"Content-Type":`application/json`},body:JSON.stringify(t),signal:n})}async renderResponse(e,t){if(!e.ok)throw Error(await this.errorMessage(e));this.status.dataset.cache=e.headers.get(`X-Diagram-Cache`)?.toLowerCase()??`miss`,this.status.dataset.renderer=e.headers.get(`X-Diagram-Unit`)?.toLowerCase()??e.headers.get(`X-Diagram-Renderer`)?.toLowerCase()??`gateway`;let n=e.headers.get(`X-Diagram-Unit`)?.toLowerCase()??e.headers.get(`X-Diagram-Engine`)?.toLowerCase()??t,r=e.headers.get(`X-Renderer-Build`)??e.headers.get(`X-Diagram-Engine-Version`)??`${n}-unknown`,i=(e.headers.get(`X-Diagram-Pipeline`)??n).split(`,`).map(e=>e.trim().toLowerCase()).filter(Boolean);return{body:await e.text(),identity:{unit:n,build:r,pipeline:i}}}retryBlockedImage(){if(this.image.src===this.activeImageUrl){if(this.imageFallbackUrl&&this.activeImageUrl.startsWith(`blob:`)){let e=this.imageFallbackUrl;this.imageFallbackUrl=null,this.objectUrl&&URL.revokeObjectURL(this.objectUrl),this.objectUrl=null,this.activeImageUrl=e,this.image.src=e,this.minimapImage.src=e;return}this.setStatus(`The rendered image was blocked or invalid.`,`error`)}}svgBlobFor(e){let{type:t,source:n,options:r={},meta:i={},presentation:a={}}=e,o=JSON.stringify({type:t,source:n,options:r,meta:i,presentation:a});return this.latestRenderKey===o?this.latestSvgBlob:null}rendererIdentityFor(e){let{type:t,source:n,options:r={},meta:i={},presentation:a={}}=e,o=JSON.stringify({type:t,source:n,options:r,meta:i,presentation:a});return this.latestRenderKey===o?this.latestRendererIdentity:null}async errorMessage(e){let t=await e.text();if(e.headers.get(`Content-Type`)?.includes(`application/json`))try{let e=JSON.parse(t);if(typeof e?.error?.message==`string`)return e.error.message}catch{}return new DOMParser().parseFromString(t,`text/html`).body.textContent.trim().replace(/\s+/g,` `)||`Render failed with HTTP ${e.status}.`}normalizedSvgBlob(e){let t=new DOMParser().parseFromString(e,`image/svg+xml`).documentElement,n=t.getAttribute(`viewBox`)?.trim().split(/[\s,]+/).map(Number);t.nodeName===`svg`&&n?.length===4&&n.every(Number.isFinite)&&(t.hasAttribute(`width`)||t.setAttribute(`width`,String(n[2])),t.hasAttribute(`height`)||t.setAttribute(`height`,String(n[3])),e=new XMLSerializer().serializeToString(t));let r=t.style.backgroundColor||t.style.background,i=r&&r!==`transparent`?r:`#ffffff`;return{blob:new Blob([e],{type:`image/svg+xml`}),background:i}}setStatus(e,t){this.status.textContent=e,this.status.dataset.state=t}zoom(e){if(this.image.hidden)return;let t=this.stage.getBoundingClientRect();this.zoomAt(e,t.width/2,t.height/2)}zoomAt(e,t,n){let r=W(this.scale*e,.1,8),i=(t-this.x)/this.scale,a=(n-this.y)/this.scale;this.x=t-i*r,this.y=n-a*r,this.scale=r,this.updateTransform()}wheelZoom(e){if(this.image.hidden||e.target.closest(`.preview-toolbar, .minimap`))return;e.preventDefault();let t=this.stage.getBoundingClientRect(),n=e.deltaY*(e.deltaMode===WheelEvent.DOM_DELTA_LINE?16:e.deltaMode===WheelEvent.DOM_DELTA_PAGE?t.height:1);this.zoomAt(Math.exp(-W(n,-240,240)*.002),e.clientX-t.left,e.clientY-t.top)}oneToOne(){if(this.image.hidden)return;let e=this.stage.getBoundingClientRect();this.scale=1,this.x=(e.width-this.image.naturalWidth)/2,this.y=(e.height-this.image.naturalHeight)/2,this.updateTransform()}fit(){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let e=this.stage.getBoundingClientRect(),t=Math.min(64,e.width*.12);this.scale=W(Math.min((e.width-t*2)/this.image.naturalWidth,(e.height-t*2)/this.image.naturalHeight),.1,2),this.x=(e.width-this.image.naturalWidth*this.scale)/2,this.y=(e.height-this.image.naturalHeight*this.scale)/2,this.updateTransform()}updateTransform(){this.image.style.transform=`translate(${this.x}px, ${this.y}px) scale(${this.scale})`,this.updateMinimap()}startPan(e){this.image.hidden||e.button!==0||e.target.closest(`.preview-toolbar, .minimap`)||(this.drag={pointerId:e.pointerId,x:e.clientX,y:e.clientY,originX:this.x,originY:this.y},this.stage.setPointerCapture(e.pointerId),this.stage.dataset.dragging=`true`)}pan(e){!this.drag||e.pointerId!==this.drag.pointerId||(this.x=this.drag.originX+e.clientX-this.drag.x,this.y=this.drag.originY+e.clientY-this.drag.y,this.updateTransform())}endPan(e){!this.drag||e.pointerId!==this.drag.pointerId||(this.drag=null,delete this.stage.dataset.dragging)}moveFromMinimap(e){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let t=this.minimap.getBoundingClientRect(),n=Math.min(t.width/this.image.naturalWidth,t.height/this.image.naturalHeight),r=(t.width-this.image.naturalWidth*n)/2,i=(t.height-this.image.naturalHeight*n)/2,a=(e.clientX-t.left-r)/n,o=(e.clientY-t.top-i)/n,s=this.stage.getBoundingClientRect();this.x=s.width/2-a*this.scale,this.y=s.height/2-o*this.scale,this.updateTransform()}updateMinimap(){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let e=this.stage.getBoundingClientRect(),t=this.image.naturalWidth*this.scale,n=this.image.naturalHeight*this.scale,r=this.x>=0&&this.y>=0&&this.x+t<=e.width&&this.y+n<=e.height;if(this.minimap.hidden=r,r)return;let i=this.minimap.getBoundingClientRect(),a=Math.min(i.width/this.image.naturalWidth,i.height/this.image.naturalHeight),o=(i.width-this.image.naturalWidth*a)/2,s=(i.height-this.image.naturalHeight*a)/2,c=W(-this.x/this.scale,0,this.image.naturalWidth),l=W(-this.y/this.scale,0,this.image.naturalHeight),u=W((e.width-this.x)/this.scale,0,this.image.naturalWidth),d=W((e.height-this.y)/this.scale,0,this.image.naturalHeight);this.minimapViewport.style.left=`${o+c*a}px`,this.minimapViewport.style.top=`${s+l*a}px`,this.minimapViewport.style.width=`${Math.max(4,(u-c)*a)}px`,this.minimapViewport.style.height=`${Math.max(4,(d-l)*a)}px`}},zn=`diagram.zip:draft:local:v2`,G=`diagram.zip:draft:alias:v1:`,Bn=`diagram.zip:write:v1:`,K=new Et,Vn,Hn,q=!1,Un=``,J={aliasId:null,contentId:null,renderId:null,revision:null,mode:`open`,savedMode:null,writeCapability:null,savedState:null,savedSnapshot:null,bundleKey:null,keyEnvelope:null,encryptedContent:null,encryptedMetadata:null,keyEnvelopeDirty:!1,dirty:!0},Wn=new Map;document.querySelector(`#app`).innerHTML=`16361637 ========16371638 ${h}.`);break;case m:switch(t){case`xml`:break;case``:e.fail(`the default namespace may not be set to ${n}.`);break;default:e.fail(`may not assign the xml namespace to another prefix.`)}}}function L(e,t){for(let n of Object.keys(t))$e(e,n,t[n])}var et=e=>p.test(e),tt=e=>l.test(e),R=0,nt=1,rt=2;e.EVENTS=[`xmldecl`,`text`,`processinginstruction`,`doctype`,`comment`,`opentagstart`,`attribute`,`opentag`,`closetag`,`cdata`,`error`,`end`,`ready`];var z={xmldecl:`xmldeclHandler`,text:`textHandler`,processinginstruction:`piHandler`,doctype:`doctypeHandler`,comment:`commentHandler`,opentagstart:`openTagStartHandler`,attribute:`attributeHandler`,opentag:`openTagHandler`,closetag:`closeTagHandler`,cdata:`cdataHandler`,error:`errorHandler`,end:`endHandler`,ready:`readyHandler`};e.SaxesParser=class{constructor(e){this.opt=e??{},this.fragmentOpt=!!this.opt.fragment;let t=this.xmlnsOpt=!!this.opt.xmlns;if(this.trackPosition=this.opt.position!==!1,this.fileName=this.opt.fileName,t){this.nameStartCheck=d,this.nameCheck=f,this.isName=et,this.processAttribs=this.processAttribsNS,this.pushAttrib=this.pushAttribNS,this.ns=Object.assign({__proto__:null},ee);let e=this.opt.additionalNamespaces;e!=null&&(L(this,e),Object.assign(this.ns,e))}else this.nameStartCheck=o,this.nameCheck=s,this.isName=tt,this.processAttribs=this.processAttribsPlain,this.pushAttrib=this.pushAttribPlain;this.stateTable=[this.sBegin,this.sBeginWhitespace,this.sDoctype,this.sDoctypeQuote,this.sDTD,this.sDTDQuoted,this.sDTDOpenWaka,this.sDTDOpenWakaBang,this.sDTDComment,this.sDTDCommentEnding,this.sDTDCommentEnded,this.sDTDPI,this.sDTDPIEnding,this.sText,this.sEntity,this.sOpenWaka,this.sOpenWakaBang,this.sComment,this.sCommentEnding,this.sCommentEnded,this.sCData,this.sCDataEnding,this.sCDataEnding2,this.sPIFirstChar,this.sPIRest,this.sPIBody,this.sPIEnding,this.sXMLDeclNameStart,this.sXMLDeclName,this.sXMLDeclEq,this.sXMLDeclValueStart,this.sXMLDeclValue,this.sXMLDeclSeparator,this.sXMLDeclEnding,this.sOpenTag,this.sOpenTagSlash,this.sAttrib,this.sAttribName,this.sAttribNameSawWhite,this.sAttribValue,this.sAttribValueQuoted,this.sAttribValueClosed,this.sAttribValueUnquoted,this.sCloseTag,this.sCloseTagSawWhite],this._init()}get closed(){return this._closed}_init(){var e;this.openWakaBang=``,this.text=``,this.name=``,this.piTarget=``,this.entity=``,this.q=null,this.tags=[],this.tag=null,this.topNS=null,this.chunk=``,this.chunkPosition=0,this.i=0,this.prevI=0,this.carriedFromPrevious=void 0,this.forbiddenState=R,this.attribList=[];let{fragmentOpt:t}=this;this.state=t?y:ne,this.reportedTextBeforeRoot=this.reportedTextAfterRoot=this.closedRoot=this.sawRoot=t,this.xmlDeclPossible=!t,this.xmlDeclExpects=[`version`],this.entityReturnState=void 0;let{defaultXMLVersion:n}=this.opt;if(n===void 0){if(this.opt.forceXMLVersion===!0)throw Error(`forceXMLVersion set but defaultXMLVersion is not set`);n=`1.0`}this.setXMLVersion(n),this.positionAtNewLine=0,this.doctype=!1,this._closed=!1,this.xmlDecl={version:void 0,encoding:void 0,standalone:void 0},this.line=1,this.column=0,this.ENTITIES=Object.create(te),(e=this.readyHandler)==null||e.call(this)}get position(){return this.chunkPosition+this.i}get columnIndex(){return this.position-this.positionAtNewLine}on(e,t){this[z[e]]=t}off(e){this[z[e]]=void 0}makeError(e){let t=this.fileName??``;return this.trackPosition&&(t.length>0&&(t+=`:`),t+=`${this.line}:${this.column}`),t.length>0&&(t+=`: `),Error(t+e)}fail(e){let t=this.makeError(e),n=this.errorHandler;if(n===void 0)throw t;return n(t),this}write(e){if(this.closed)return this.fail(`cannot write after close; assign an onready handler.`);let t=!1;e===null?(t=!0,e=``):typeof e==`object`&&(e=e.toString()),this.carriedFromPrevious!==void 0&&(e=`${this.carriedFromPrevious}${e}`,this.carriedFromPrevious=void 0);let n=e.length,r=e.charCodeAt(n-1);!t&&(r===Ve||r>=55296&&r<=56319)&&(this.carriedFromPrevious=e[n-1],n--,e=e.slice(0,n));let{stateTable:i}=this;for(this.chunk=e,this.i=0;this.i<n;)i[this.state].call(this);return this.chunkPosition+=n,t?this.end():this}close(){return this.write(null)}getCode10(){let{chunk:e,i:t}=this;if(this.prevI=t,this.i=t+1,t>=e.length)return g;let n=e.charCodeAt(t);if(this.column++,n<55296){if(n>=He||n===Be)return n;switch(n){case T:return this.line++,this.column=0,this.positionAtNewLine=this.position,T;case Ve:return e.charCodeAt(t+1)===T&&(this.i=t+2),this.line++,this.column=0,this.positionAtNewLine=this.position,_;default:return this.fail(`disallowed character.`),n}}if(n>56319)return n>=57344&&n<=65533||this.fail(`disallowed character.`),n;let r=65536+(n-55296)*1024+(e.charCodeAt(t+1)-56320);return this.i=t+2,r>1114111&&this.fail(`disallowed character.`),r}getCode11(){let{chunk:e,i:t}=this;if(this.prevI=t,this.i=t+1,t>=e.length)return g;let n=e.charCodeAt(t);if(this.column++,n<55296){if(n>31&&n<127||n>159&&n!==P||n===Be)return n;switch(n){case T:return this.line++,this.column=0,this.positionAtNewLine=this.position,T;case Ve:{let n=e.charCodeAt(t+1);(n===T||n===N)&&(this.i=t+2)}case N:case P:return this.line++,this.column=0,this.positionAtNewLine=this.position,_;default:return this.fail(`disallowed character.`),n}}if(n>56319)return n>=57344&&n<=65533||this.fail(`disallowed character.`),n;let r=65536+(n-55296)*1024+(e.charCodeAt(t+1)-56320);return this.i=t+2,r>1114111&&this.fail(`disallowed character.`),r}getCodeNorm(){let e=this.getCode();return e===_?T:e}unget(){this.i=this.prevI,this.column--}captureTo(e){let{i:t}=this,{chunk:n}=this;for(;;){let r=this.getCode(),i=r===_,a=i?T:r;if(a===g||e.includes(a))return this.text+=n.slice(t,this.prevI),a;i&&(this.text+=`${n.slice(t,this.prevI)}\n`,t=this.i)}}captureToChar(e){let{i:t}=this,{chunk:n}=this;for(;;){let r=this.getCode();switch(r){case _:this.text+=`${n.slice(t,this.prevI)}\n`,t=this.i,r=T;break;case g:return this.text+=n.slice(t),!1}if(r===e)return this.text+=n.slice(t,this.prevI),!0}}captureNameChars(){let{chunk:e,i:t}=this;for(;;){let n=this.getCode();if(n===g)return this.name+=e.slice(t),g;if(!s(n))return this.name+=e.slice(t,this.prevI),n===_?T:n}}skipSpaces(){for(;;){let e=this.getCodeNorm();if(e===g||!i(e))return e}}setXMLVersion(e){this.currentXMLVersion=e,e===`1.0`?(this.isChar=a,this.getCode=this.getCode10):(this.isChar=u,this.getCode=this.getCode11)}sBegin(){this.chunk.charCodeAt(0)===65279&&(this.i++,this.column++),this.state=re}sBeginWhitespace(){let e=this.i,t=this.skipSpaces();switch(this.prevI!==e&&(this.xmlDeclPossible=!1),t){case D:if(this.state=he,this.text.length!==0)throw Error(`no-empty text at start`);break;case g:break;default:this.unget(),this.state=y,this.xmlDeclPossible=!1}}sDoctype(){var e;let t=this.captureTo(Xe);switch(t){case k:(e=this.doctypeHandler)==null||e.call(this,this.text),this.text=``,this.state=y,this.doctype=!0;break;case g:break;default:this.text+=String.fromCodePoint(t),t===j?this.state=v:F(t)&&(this.state=ae,this.q=t)}}sDoctypeQuote(){let e=this.q;this.captureToChar(e)&&(this.text+=String.fromCodePoint(e),this.q=null,this.state=ie)}sDTD(){let e=this.captureTo(Ze);e!==g&&(this.text+=String.fromCodePoint(e),e===M?this.state=ie:e===D?this.state=se:F(e)&&(this.state=oe,this.q=e))}sDTDQuoted(){let e=this.q;this.captureToChar(e)&&(this.text+=String.fromCodePoint(e),this.state=v,this.q=null)}sDTDOpenWaka(){let e=this.getCodeNorm();switch(this.text+=String.fromCodePoint(e),e){case Ue:this.state=ce,this.openWakaBang=``;break;case A:this.state=fe;break;default:this.state=v}}sDTDOpenWakaBang(){let e=String.fromCodePoint(this.getCodeNorm()),t=this.openWakaBang+=e;this.text+=e,t!==`-`&&(this.state=t===`--`?le:v,this.openWakaBang=``)}sDTDComment(){this.captureToChar(qe)&&(this.text+=`-`,this.state=ue)}sDTDCommentEnding(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),this.state=e===qe?de:le}sDTDCommentEnded(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),e===k?this.state=v:(this.fail(`malformed comment.`),this.state=le)}sDTDPI(){this.captureToChar(A)&&(this.text+=`?`,this.state=pe)}sDTDPIEnding(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),e===k&&(this.state=v)}sText(){this.tags.length===0?this.handleTextOutsideRoot():this.handleTextInRoot()}sEntity(){let{i:e}=this,{chunk:t}=this;loop:for(;;)switch(this.getCode()){case _:this.entity+=`${t.slice(e,this.prevI)}\n`,e=this.i;break;case Je:{let{entityReturnState:n}=this,r=this.entity+t.slice(e,this.prevI);this.state=n;let i;r===``?(this.fail(`empty entity name.`),i=`&;`):(i=this.parseEntity(r),this.entity=``),(n!==y||this.textHandler!==void 0)&&(this.text+=i);break loop}case g:this.entity+=t.slice(e);break loop}}sOpenWaka(){let e=this.getCode();if(o(e))this.state=Ne,this.unget(),this.xmlDeclPossible=!1;else switch(e){case E:this.state=Re,this.xmlDeclPossible=!1;break;case Ue:this.state=ge,this.openWakaBang=``,this.xmlDeclPossible=!1;break;case A:this.state=Ce;break;default:this.fail(`disallowed character in tag name`),this.state=y,this.xmlDeclPossible=!1}}sOpenWakaBang(){switch(this.openWakaBang+=String.fromCodePoint(this.getCodeNorm()),this.openWakaBang){case`[CDATA[`:!this.sawRoot&&!this.reportedTextBeforeRoot&&(this.fail(`text data outside of root node.`),this.reportedTextBeforeRoot=!0),this.closedRoot&&!this.reportedTextAfterRoot&&(this.fail(`text data outside of root node.`),this.reportedTextAfterRoot=!0),this.state=be,this.openWakaBang=``;break;case`--`:this.state=_e,this.openWakaBang=``;break;case`DOCTYPE`:this.state=ie,(this.doctype||this.sawRoot)&&this.fail(`inappropriately located doctype declaration.`),this.openWakaBang=``;break;default:this.openWakaBang.length>=7&&this.fail(`incorrect syntax.`)}}sComment(){this.captureToChar(qe)&&(this.state=ve)}sCommentEnding(){var e;let t=this.getCodeNorm();t===qe?(this.state=ye,(e=this.commentHandler)==null||e.call(this,this.text),this.text=``):(this.text+=`-${String.fromCodePoint(t)}`,this.state=_e)}sCommentEnded(){let e=this.getCodeNorm();e===k?this.state=y:(this.fail(`malformed comment.`),this.text+=`--${String.fromCodePoint(e)}`,this.state=_e)}sCData(){this.captureToChar(M)&&(this.state=xe)}sCDataEnding(){let e=this.getCodeNorm();e===M?this.state=Se:(this.text+=`]${String.fromCodePoint(e)}`,this.state=be)}sCDataEnding2(){var e;let t=this.getCodeNorm();switch(t){case k:(e=this.cdataHandler)==null||e.call(this,this.text),this.text=``,this.state=y;break;case M:this.text+=`]`;break;default:this.text+=`]]${String.fromCodePoint(t)}`,this.state=be}}sPIFirstChar(){let e=this.getCodeNorm();this.nameStartCheck(e)?(this.piTarget+=String.fromCodePoint(e),this.state=we):e===A||i(e)?(this.fail(`processing instruction without a target.`),this.state=e===A?Ee:Te):(this.fail(`disallowed character in processing instruction name.`),this.piTarget+=String.fromCodePoint(e),this.state=we)}sPIRest(){let{chunk:e,i:t}=this;for(;;){let n=this.getCodeNorm();if(n===g){this.piTarget+=e.slice(t);return}if(!this.nameCheck(n)){this.piTarget+=e.slice(t,this.prevI);let r=n===A;r||i(n)?this.piTarget===`xml`?(this.xmlDeclPossible||this.fail(`an XML declaration must be at the start of the document.`),this.state=r?b:De):this.state=r?Ee:Te:(this.fail(`disallowed character in processing instruction name.`),this.piTarget+=String.fromCodePoint(n));break}}}sPIBody(){if(this.text.length===0){let e=this.getCodeNorm();e===A?this.state=Ee:i(e)||(this.text=String.fromCodePoint(e))}else this.captureToChar(A)&&(this.state=Ee)}sPIEnding(){var e;let t=this.getCodeNorm();if(t===k){let{piTarget:t}=this;t.toLowerCase()===`xml`&&this.fail(`the XML declaration must appear at the start of the document.`),(e=this.piHandler)==null||e.call(this,{target:t,body:this.text}),this.piTarget=this.text=``,this.state=y}else t===A?this.text+=`?`:(this.text+=`?${String.fromCodePoint(t)}`,this.state=Te);this.xmlDeclPossible=!1}sXMLDeclNameStart(){let e=this.skipSpaces();if(e===A){this.state=b;return}e!==g&&(this.state=Oe,this.name=String.fromCodePoint(e))}sXMLDeclName(){let e=this.captureTo(Qe);if(e===A){this.state=b,this.name+=this.text,this.text=``,this.fail(`XML declaration is incomplete.`);return}if(i(e)||e===O){if(this.name+=this.text,this.text=``,!this.xmlDeclExpects.includes(this.name))switch(this.name.length){case 0:this.fail(`did not expect any more name/value pairs.`);break;case 1:this.fail(`expected the name ${this.xmlDeclExpects[0]}.`);break;default:this.fail(`expected one of ${this.xmlDeclExpects.join(`, `)}`)}this.state=e===O?Ae:ke}}sXMLDeclEq(){let e=this.getCodeNorm();if(e===A){this.state=b,this.fail(`XML declaration is incomplete.`);return}i(e)||(e!==O&&this.fail(`value required.`),this.state=Ae)}sXMLDeclValueStart(){let e=this.getCodeNorm();if(e===A){this.state=b,this.fail(`XML declaration is incomplete.`);return}i(e)||(F(e)?this.q=e:(this.fail(`value must be quoted.`),this.q=He),this.state=je)}sXMLDeclValue(){let e=this.captureTo([this.q,A]);if(e===A){this.state=b,this.text=``,this.fail(`XML declaration is incomplete.`);return}if(e===g)return;let t=this.text;switch(this.text=``,this.name){case`version`:{this.xmlDeclExpects=[`encoding`,`standalone`];let e=t;this.xmlDecl.version=e,/^1\.[0-9]+$/.test(e)?this.opt.forceXMLVersion||this.setXMLVersion(e):this.fail(`version number must match /^1\\.[0-9]+$/.`);break}case`encoding`:/^[A-Za-z][A-Za-z0-9._-]*$/.test(t)||this.fail(`encoding value must match /^[A-Za-z0-9][A-Za-z0-9._-]*$/.`),this.xmlDeclExpects=[`standalone`],this.xmlDecl.encoding=t;break;case`standalone`:t!==`yes`&&t!==`no`&&this.fail(`standalone value must match "yes" or "no".`),this.xmlDeclExpects=[],this.xmlDecl.standalone=t}this.name=``,this.state=Me}sXMLDeclSeparator(){let e=this.getCodeNorm();if(e===A){this.state=b;return}i(e)||(this.fail(`whitespace required.`),this.unget()),this.state=De}sXMLDeclEnding(){var e;this.getCodeNorm()===k?(this.piTarget===`xml`?this.name!==`version`&&this.xmlDeclExpects.includes(`version`)&&this.fail(`XML declaration must contain a version.`):this.fail(`processing instructions are not allowed before root.`),(e=this.xmldeclHandler)==null||e.call(this,this.xmlDecl),this.name=``,this.piTarget=this.text=``,this.state=y):this.fail(`The character ? is disallowed anywhere in XML declarations.`),this.xmlDeclPossible=!1}sOpenTag(){var e;let t=this.captureNameChars();if(t===g)return;let n=this.tag={name:this.name,attributes:Object.create(null)};switch(this.name=``,this.xmlnsOpt&&(this.topNS=n.ns=Object.create(null)),(e=this.openTagStartHandler)==null||e.call(this,n),this.sawRoot=!0,!this.fragmentOpt&&this.closedRoot&&this.fail(`documents may contain only one root.`),t){case k:this.openTag();break;case E:this.state=Pe;break;default:i(t)||this.fail(`disallowed character in tag name.`),this.state=x}}sOpenTagSlash(){this.getCode()===k?this.openSelfClosingTag():(this.fail(`forward-slash in opening tag not followed by >.`),this.state=x)}sAttrib(){let e=this.skipSpaces();e!==g&&(o(e)?(this.unget(),this.state=S):e===k?this.openTag():e===E?this.state=Pe:this.fail(`disallowed character in attribute name.`))}sAttribName(){let e=this.captureNameChars();e===O?this.state=w:i(e)?this.state=C:e===k?(this.fail(`attribute without value.`),this.pushAttrib(this.name,this.name),this.name=this.text=``,this.openTag()):e!==g&&this.fail(`disallowed character in attribute name.`)}sAttribNameSawWhite(){let e=this.skipSpaces();switch(e){case g:return;case O:this.state=w;break;default:this.fail(`attribute without value.`),this.text=``,this.name=``,e===k?this.openTag():o(e)?(this.unget(),this.state=S):(this.fail(`disallowed character in attribute name.`),this.state=x)}}sAttribValue(){let e=this.getCodeNorm();F(e)?(this.q=e,this.state=Fe):i(e)||(this.fail(`unquoted attribute value.`),this.state=Le,this.unget())}sAttribValueQuoted(){let{q:e,chunk:t}=this,{i:n}=this;for(;;)switch(this.getCode()){case e:this.pushAttrib(this.name,this.text+t.slice(n,this.prevI)),this.name=this.text=``,this.q=null,this.state=Ie;return;case Ge:this.text+=t.slice(n,this.prevI),this.state=me,this.entityReturnState=Fe;return;case T:case _:case Be:this.text+=`${t.slice(n,this.prevI)} `,n=this.i;break;case D:this.text+=t.slice(n,this.prevI),this.fail(`disallowed character.`);return;case g:this.text+=t.slice(n);return}}sAttribValueClosed(){let e=this.getCodeNorm();i(e)?this.state=x:e===k?this.openTag():e===E?this.state=Pe:o(e)?(this.fail(`no whitespace between attributes.`),this.unget(),this.state=S):this.fail(`disallowed character in attribute name.`)}sAttribValueUnquoted(){let e=this.captureTo(I);switch(e){case Ge:this.state=me,this.entityReturnState=Le;break;case D:this.fail(`disallowed character.`);break;case g:break;default:this.text.includes(`]]>`)&&this.fail(`the string "]]>" is disallowed in char data.`),this.pushAttrib(this.name,this.text),this.name=this.text=``,e===k?this.openTag():this.state=x}}sCloseTag(){let e=this.captureNameChars();e===k?this.closeTag():i(e)?this.state=ze:e!==g&&this.fail(`disallowed character in closing tag.`)}sCloseTagSawWhite(){switch(this.skipSpaces()){case k:this.closeTag();break;case g:break;default:this.fail(`disallowed character in closing tag.`)}}handleTextInRoot(){let{i:e,forbiddenState:t}=this,{chunk:n,textHandler:r}=this;scanLoop:for(;;)switch(this.getCode()){case D:if(this.state=he,r!==void 0){let{text:t}=this,i=n.slice(e,this.prevI);t.length===0?i.length!==0&&r(i):(r(t+i),this.text=``)}t=R;break scanLoop;case Ge:this.state=me,this.entityReturnState=y,r!==void 0&&(this.text+=n.slice(e,this.prevI)),t=R;break scanLoop;case M:switch(t){case R:t=nt;break;case nt:t=rt;break;case rt:break;default:throw Error(`impossible state`)}break;case k:t===rt&&this.fail(`the string "]]>" is disallowed in char data.`),t=R;break;case _:r!==void 0&&(this.text+=`${n.slice(e,this.prevI)}\n`),e=this.i,t=R;break;case g:r!==void 0&&(this.text+=n.slice(e));break scanLoop;default:t=R}this.forbiddenState=t}handleTextOutsideRoot(){let{i:e}=this,{chunk:t,textHandler:n}=this,r=!1;outRootLoop:for(;;){let a=this.getCode();switch(a){case D:if(this.state=he,n!==void 0){let{text:r}=this,i=t.slice(e,this.prevI);r.length===0?i.length!==0&&n(i):(n(r+i),this.text=``)}break outRootLoop;case Ge:this.state=me,this.entityReturnState=y,n!==void 0&&(this.text+=t.slice(e,this.prevI)),r=!0;break outRootLoop;case _:n!==void 0&&(this.text+=`${t.slice(e,this.prevI)}\n`),e=this.i;break;case g:n!==void 0&&(this.text+=t.slice(e));break outRootLoop;default:i(a)||(r=!0)}}r&&(!this.sawRoot&&!this.reportedTextBeforeRoot&&(this.fail(`text data outside of root node.`),this.reportedTextBeforeRoot=!0),this.closedRoot&&!this.reportedTextAfterRoot&&(this.fail(`text data outside of root node.`),this.reportedTextAfterRoot=!0))}pushAttribNS(e,t){var n;let{prefix:r,local:i}=this.qname(e),a={name:e,prefix:r,local:i,value:t};if(this.attribList.push(a),(n=this.attributeHandler)==null||n.call(this,a),r===`xmlns`){let e=t.trim();this.currentXMLVersion===`1.0`&&e===``&&this.fail(`invalid attempt to undefine prefix in XML 1.0`),this.topNS[i]=e,$e(this,i,e)}else if(e===`xmlns`){let e=t.trim();this.topNS[``]=e,$e(this,``,e)}}pushAttribPlain(e,t){var n;let r={name:e,value:t};this.attribList.push(r),(n=this.attributeHandler)==null||n.call(this,r)}end(){var e,t;this.sawRoot||this.fail(`document must contain a root element.`);let{tags:n}=this;for(;n.length>0;){let e=n.pop();this.fail(`unclosed tag: ${e.name}`)}this.state!==ne&&this.state!==y&&this.fail(`unexpected end.`);let{text:r}=this;return r.length!==0&&((e=this.textHandler)==null||e.call(this,r),this.text=``),this._closed=!0,(t=this.endHandler)==null||t.call(this),this._init(),this}resolve(e){var t;let n=this.topNS[e];if(n!==void 0)return n;let{tags:r}=this;for(let t=r.length-1;t>=0;t--)if(n=r[t].ns[e],n!==void 0)return n;return n=this.ns[e],n===void 0?(t=this.opt).resolvePrefix?.call(t,e):n}qname(e){let t=e.indexOf(`:`);if(t===-1)return{prefix:``,local:e};let n=e.slice(t+1),r=e.slice(0,t);return(r===``||n===``||n.includes(`:`))&&this.fail(`malformed name: ${e}.`),{prefix:r,local:n}}processAttribsNS(){let{attribList:e}=this,t=this.tag;{let{prefix:e,local:n}=this.qname(t.name);t.prefix=e,t.local=n;let r=t.uri=this.resolve(e)??``;e!==``&&(e===`xmlns`&&this.fail(`tags may not have "xmlns" as prefix.`),r===``&&(this.fail(`unbound namespace prefix: ${JSON.stringify(e)}.`),t.uri=e))}if(e.length===0)return;let{attributes:n}=t,r=new Set;for(let t of e){let{name:e,prefix:i,local:a}=t,o,s;i===``?(o=e===`xmlns`?h:``,s=e):(o=this.resolve(i),o===void 0&&(this.fail(`unbound namespace prefix: ${JSON.stringify(i)}.`),o=i),s=`{${o}}${a}`),r.has(s)&&this.fail(`duplicate attribute: ${s}.`),r.add(s),t.uri=o,n[e]=t}this.attribList=[]}processAttribsPlain(){let{attribList:e}=this,t=this.tag.attributes;for(let{name:n,value:r}of e)t[n]!==void 0&&this.fail(`duplicate attribute: ${n}.`),t[n]=r;this.attribList=[]}openTag(){var e;this.processAttribs();let{tags:t}=this,n=this.tag;n.isSelfClosing=!1,(e=this.openTagHandler)==null||e.call(this,n),t.push(n),this.state=y,this.name=``}openSelfClosingTag(){var e,t;this.processAttribs();let{tags:n}=this,r=this.tag;r.isSelfClosing=!0,(e=this.openTagHandler)==null||e.call(this,r),(t=this.closeTagHandler)==null||t.call(this,r),(this.tag=n[n.length-1]??null)===null&&(this.closedRoot=!0),this.state=y,this.name=``}closeTag(){let{tags:e,name:t}=this;if(this.state=y,this.name=``,t===``){this.fail(`weird empty close tag.`),this.text+=`</>`;return}let n=this.closeTagHandler,r=e.length;for(;r-->0;){let r=this.tag=e.pop();if(this.topNS=r.ns,n?.(r),r.name===t)break;this.fail(`unexpected close tag.`)}r===0?this.closedRoot=!0:r<0&&(this.fail(`unmatched closing tag: ${t}.`),this.text+=`</${t}>`)}parseEntity(e){if(e[0]!==`#`){let t=this.ENTITIES[e];return t===void 0?(this.fail(this.isName(e)?`undefined entity.`:`disallowed character in entity name.`),`&${e};`):t}let t=NaN;return e[1]===`x`&&/^#x[0-9a-f]+$/i.test(e)?t=parseInt(e.slice(2),16):/^#[0-9]+$/.test(e)&&(t=parseInt(e.slice(1),10)),this.isChar(t)?String.fromCodePoint(t):(this.fail(`malformed character entity.`),`&${e};`)}}}))(),cn=4194304,ln=new Set([`script`,`iframe`,`object`,`embed`,`audio`,`video`]),un=new Set([`foreignobject`,`div`,`span`,`p`,`br`,`b`,`strong`,`i`,`em`,`small`,`sub`,`sup`,`code`,`ul`,`ol`,`li`,`a`]),dn=/^data:image\/(?:png|jpeg|gif|webp);base64,[a-z0-9+/=\s]+$/i,fn=/^data:(?:font\/(?:woff2?|opentype|truetype)|application\/(?:x-)?font-woff2?);base64,[a-z0-9+/=\s]+$/i,pn=class extends Error{constructor(e){super(e),this.name=`ClientSvgError`}};function mn(e){let t=e.trim();return t.startsWith(`#`)||dn.test(t)||fn.test(t)}function hn(e){if(/@import|expression\s*\(|javascript\s*:|data\s*:\s*text\/html|behavior\s*:|-moz-binding/i.test(e))return!0;let t=/url\s*\(\s*(['"]?)(.*?)\1\s*\)/gi;for(let n of e.matchAll(t))if(!mn(n[2]??``))return!0;return/url\s*\(/i.test(e.replace(t,``))}function gn(e){let t=e.trim();return t.startsWith(`#`)||dn.test(t)}function _n(e,t){let n=e.toLowerCase();return n.startsWith(`on`)?!1:n===`href`||n===`xlink:href`||n===`src`?gn(t):[`style`,`filter`,`fill`,`stroke`,`clip-path`,`mask`].includes(n)?!hn(t):n!==`xml:base`&&!/javascript\s*:/i.test(t)}function vn(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`)}function yn(e){return vn(e).replaceAll(`"`,`&quot;`)}function bn(e){if(e.type===`text`)return vn(e.value);let t=[...e.attributes].map(([e,t])=>` ${e}="${yn(t)}"`).join(``);return`<${e.name}${t}>${e.children.map(bn).join(``)}</${e.name}>`}function xn(e){return e.name.toLowerCase().split(`:`).at(-1)??e.name.toLowerCase()}function Sn(e){if(e===void 0||!/^-?\d+(?:\.\d+)?(?:px)?$/.test(e.trim()))return null;let t=Number.parseFloat(e);return Number.isFinite(t)&&t>0?t:null}function Cn(e){let t=e.attributes.get(`viewBox`)?.trim().split(/[\s,]+/).map(Number);if(t?.length===4&&t.every(Number.isFinite)&&t[2]>0&&t[3]>0)return t;let n=Sn(e.attributes.get(`width`)),r=Sn(e.attributes.get(`height`));return n!==null&&r!==null?[0,0,n,r]:null}function wn(e,t){let n=e.attributes.get(`style`);if(!n)return null;for(let e of n.split(`;`)){let n=e.indexOf(`:`);if(n>=0&&e.slice(0,n).trim().toLowerCase()===t)return e.slice(n+1).trim()}return null}function Tn(e){let t=(e.attributes.get(`fill`)??wn(e,`fill`)??``).replaceAll(` `,``).toLowerCase();return t===`white`||t===`#fff`||t===`#ffffff`||t===`rgb(255,255,255)`}function En(e){if(e===void 0||!/^-?\d+(?:\.\d+)?$/.test(e.trim()))return null;let t=Number(e);return Number.isFinite(t)?t:null}function Dn(e,t){let n=e.attributes.get(`width`)?.trim(),r=e.attributes.get(`height`)?.trim();if(n===`100%`&&r===`100%`)return!0;let i=En(e.attributes.get(`x`)??`0`),a=En(e.attributes.get(`y`)??`0`),o=En(n),s=En(r);if([i,a,o,s].some(e=>e===null))return!1;let[c,l,u,d]=t,f=.01;return i<=c+f&&a<=l+f&&i+o>=c+u-f&&a+s>=l+d-f}function On(e,t,n){let r=xn(e),i=new Set((e.attributes.get(`class`)??``).toLowerCase().split(/\s+/));return r===`rect`&&i.has(`backdrop`)||r===`rect`&&t!==null&&Tn(e)&&Dn(e,t)?!0:n===`graphviz`&&r===`polygon`&&Tn(e)&&(e.attributes.get(`stroke`)??``).toLowerCase()===`none`}function kn(e,t,n){let r=xn(e)===`svg`?Cn(e)??t:t;e.children=e.children.filter(e=>e.type===`text`||!On(e,r,n)&&(kn(e,r,n),!0))}function An(e,t){let n=(e.attributes.get(`style`)??``).split(`;`).map(e=>e.trim()).filter(Boolean).filter(e=>!/^background(?:-color)?\s*:/i.test(e));n.push(`background-color:${t}`),e.attributes.set(`style`,`${n.join(`;`)};`)}function jn(e,t=[],n=[]){return{type:`element`,name:e,attributes:new Map(t),children:n}}function Mn(e,t,n,r){let i=[],a=null;t.title&&i.push(jn(`title`,[],[{type:`text`,value:t.title}])),t.description&&i.push(jn(`desc`,[],[{type:`text`,value:t.description}]));let o=Cn(e);if((n.padding||n.background||n.frame)&&o===null)throw new pn(`Renderer output has no usable SVG dimensions for presentation settings.`);if(o!==null){let[t,r,s,c]=o,l=n.padding,u=[t-l,r-l,s+l*2,c+l*2];e.attributes.set(`viewBox`,u.join(` `)),e.attributes.has(`width`)&&e.attributes.set(`width`,String(u[2])),e.attributes.has(`height`)&&e.attributes.set(`height`,String(u[3])),n.background&&i.push(jn(`rect`,[[`x`,String(u[0])],[`y`,String(u[1])],[`width`,String(u[2])],[`height`,String(u[3])],[`fill`,n.background]])),n.frame&&(a=jn(`rect`,[[`x`,String(u[0]+.5)],[`y`,String(u[1]+.5)],[`width`,String(Math.max(0,u[2]-1))],[`height`,String(Math.max(0,u[3]-1))],[`fill`,`none`],[`stroke`,`#000000`],[`stroke-width`,`1`],[`vector-effect`,`non-scaling-stroke`]]))}n.background&&(kn(e,o,r),An(e,n.background)),e.children.unshift(...i),a&&e.children.push(a)}function Nn(e,t,n,r){if(e.length>cn)throw new pn(`Rendered SVG is too large.`);let i=null,a=[],o=0,s=null,c=new sn.SaxesParser({xmlns:!0});c.on(`doctype`,e=>{e.includes(`[`)&&(s=Error(`Internal DOCTYPE subsets are not allowed`))}),c.on(`opentag`,e=>{let t=e.local.toLowerCase(),n=t===`foreignobject`||a.some(e=>xn(e)===`foreignobject`);if(o>0||ln.has(t)||n&&!un.has(t)){o++;return}let r=jn(e.name);for(let t of Object.values(e.attributes))_n(t.name,t.value)&&r.attributes.set(t.name,t.value);let c=a.at(-1);c?c.children.push(r):i===null?i=r:s=Error(`Multiple root elements`),a.push(r)});let l=e=>{if(o>0||e===``)return;let t=a.at(-1);if(t){if(xn(t)===`style`&&hn(e))throw new pn(`Renderer output contains unsafe CSS.`);t.children.push({type:`text`,value:e})}else e.trim()!==``&&(s=Error(`Text outside root element`))};c.on(`text`,l),c.on(`cdata`,l),c.on(`closetag`,()=>{o>0?o--:a.pop()}),c.on(`error`,e=>{s=e});try{c.write(e).close()}catch(e){if(e instanceof pn)throw e;s=e}if(s!==null||i===null||xn(i)!==`svg`)throw new pn(`Renderer returned invalid SVG.`);return Mn(i,t,n,r),bn(i)}var Pn=Object.freeze({plantuml:`https://plantuml.render.diagram.zip/v1/svg`,graphviz:`https://graphviz.render.diagram.zip/v1/svg`,d2:`https://d2.render.diagram.zip/v1/svg`,c4plantuml:`https://c4plantuml.render.diagram.zip/v1/svg`,blockdiag:`https://blockdiag.render.diagram.zip/v1/svg`,seqdiag:`https://seqdiag.render.diagram.zip/v1/svg`,actdiag:`https://actdiag.render.diagram.zip/v1/svg`,nwdiag:`https://nwdiag.render.diagram.zip/v1/svg`,packetdiag:`https://packetdiag.render.diagram.zip/v1/svg`,rackdiag:`https://rackdiag.render.diagram.zip/v1/svg`,bytefield:`https://bytefield.render.diagram.zip/v1/svg`,dbml:`https://dbml.render.diagram.zip/v1/svg`,diagramsnet:`https://diagramsnet.render.diagram.zip/v1/svg`,ditaa:`https://ditaa.render.diagram.zip/v1/svg`,erd:`https://erd.render.diagram.zip/v1/svg`,goat:`https://goat.render.diagram.zip/v1/svg`,nomnoml:`https://nomnoml.render.diagram.zip/v1/svg`,pikchr:`https://pikchr.render.diagram.zip/v1/svg`,structurizr:`https://structurizr.render.diagram.zip/v1/svg`,svgbob:`https://svgbob.render.diagram.zip/v1/svg`,symbolator:`https://symbolator.render.diagram.zip/v1/svg`,tikz:`https://tikz.render.diagram.zip/v1/svg`,umlet:`https://umlet.render.diagram.zip/v1/svg`,vega:`https://vega.render.diagram.zip/v1/svg`,vegalite:`https://vegalite.render.diagram.zip/v1/svg`,wavedrom:`https://wavedrom.render.diagram.zip/v1/svg`,wireviz:`https://wireviz.render.diagram.zip/v1/svg`}),Fn=Object.freeze([`mermaid`,`bpmn`,`excalidraw`,`plantuml`,`c4plantuml`,`bytefield`,`nomnoml`,`vega`,`vegalite`,`wavedrom`,`blockdiag`,`seqdiag`,`actdiag`,`nwdiag`,`packetdiag`,`rackdiag`,`graphviz`,`erd`,`dbml`,`goat`,`pikchr`,`svgbob`,`wireviz`]);function In(e){return Fn.includes(e)}function Ln(e){return Pn[e]??null}function W(e,t,n){return Math.min(Math.max(e,t),n)}var Rn=class{constructor({stage:e,image:t,status:n,minimap:r,minimapImage:i,minimapViewport:a}){this.stage=e,this.image=t,this.status=n,this.minimap=r,this.minimapImage=i,this.minimapViewport=a,this.scale=1,this.x=0,this.y=0,this.objectUrl=null,this.abortController=null,this.requestNumber=0,this.pendingRender=null,this.renderLoop=null,this.latestRenderKey=null,this.latestSvgBlob=null,this.latestRendererIdentity=null,this.imageFallbackUrl=null,this.activeImageUrl=null,this.drag=null,this.image.addEventListener(`load`,()=>{this.image.src===this.activeImageUrl&&(this.imageFallbackUrl=null,this.setStatus(`Rendered`,`ready`),this.fit())}),this.image.addEventListener(`error`,()=>this.retryBlockedImage()),this.stage.addEventListener(`pointerdown`,e=>this.startPan(e)),this.stage.addEventListener(`pointermove`,e=>this.pan(e)),this.stage.addEventListener(`pointerup`,e=>this.endPan(e)),this.stage.addEventListener(`pointercancel`,e=>this.endPan(e)),this.stage.addEventListener(`wheel`,e=>this.wheelZoom(e),{passive:!1}),this.minimap.addEventListener(`pointerdown`,e=>this.moveFromMinimap(e)),new ResizeObserver(()=>this.updateTransform()).observe(this.stage)}render({type:e,source:t,options:n={},meta:r={},presentation:i={}}){if(!t.trim()){this.requestNumber++,this.pendingRender=null,this.abortController?.abort(),this.setStatus(`Write something to render.`,`idle`);return}let a=JSON.stringify({type:e,source:t,options:n,meta:r,presentation:i});return this.latestRenderKey===a&&this.latestSvgBlob?Promise.resolve():(this.pendingRender={type:e,source:t,options:n,meta:r,presentation:i,renderKey:a,requestNumber:++this.requestNumber},this.abortController?.abort(),this.renderLoop||=this.drainRenderQueue(),this.renderLoop)}async drainRenderQueue(){try{for(;this.pendingRender;){let e=this.pendingRender;this.pendingRender=null,await this.performRender(e)}}finally{this.renderLoop=null,this.pendingRender&&(this.renderLoop=this.drainRenderQueue())}}async performRender({type:e,source:t,options:n,meta:r,presentation:i,renderKey:a,requestNumber:o}){let s=new AbortController;this.abortController=s,this.setStatus(`Rendering…`,`loading`),this.stage.style.setProperty(`--render-background`,`var(--preview-bg)`);try{let c=nn(e),l;if(c)try{let a=await c.render({type:e,source:t,options:n},s.signal);l={body:Nn(a.body,r,i,e),identity:{unit:e,build:a.build||a.version,pipeline:Array.isArray(a.pipeline)?a.pipeline:[e]}},this.status.dataset.cache=`browser`,this.status.dataset.renderer=e}catch(a){if(a.name===`AbortError`||In(e))throw a;l=await this.renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},s.signal)}else l=await this.renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},s.signal);if(o!==this.requestNumber)return;let{blob:u,background:d}=this.normalizedSvgBlob(l.body);this.latestRenderKey=a,this.latestSvgBlob=u,this.latestRendererIdentity=l.identity,this.imageFallbackUrl=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(await u.text())}`,this.stage.style.setProperty(`--render-background`,d);let f=URL.createObjectURL(u),p=this.objectUrl;this.objectUrl=f,this.activeImageUrl=f,this.image.src=f,this.minimapImage.src=f,this.image.hidden=!1,this.setStatus(`Loading image…`,`loading`),p&&URL.revokeObjectURL(p)}catch(e){if(e.name===`AbortError`)return;this.setStatus(e.message||`Could not render this diagram.`,`error`)}finally{this.abortController===s&&(this.abortController=null)}}async renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},a){let o=Ln(e);if(o){try{let s=await this.renderRequest(o,{source:t,format:`svg`,options:n,metadata:r,presentation:i},a);if(s.ok||s.status<500)return await this.renderResponse(s,e);await s.body?.cancel()}catch(t){if(t.name===`AbortError`||In(e))throw t}if(In(e))throw Error(`The ${e} renderer unit is unavailable.`)}let s=await this.renderRequest(`/render/v1/svg`,{engine:e,source:t,format:`svg`,options:n,metadata:r,presentation:i},a);return this.renderResponse(s,e)}renderRequest(e,t,n){return fetch(e,{method:`POST`,headers:{Accept:`image/svg+xml`,"Content-Type":`application/json`},body:JSON.stringify(t),signal:n})}async renderResponse(e,t){if(!e.ok)throw Error(await this.errorMessage(e));this.status.dataset.cache=e.headers.get(`X-Diagram-Cache`)?.toLowerCase()??`miss`,this.status.dataset.renderer=e.headers.get(`X-Diagram-Unit`)?.toLowerCase()??e.headers.get(`X-Diagram-Renderer`)?.toLowerCase()??`gateway`;let n=e.headers.get(`X-Diagram-Unit`)?.toLowerCase()??e.headers.get(`X-Diagram-Engine`)?.toLowerCase()??t,r=e.headers.get(`X-Renderer-Build`)??e.headers.get(`X-Diagram-Engine-Version`)??`${n}-unknown`,i=(e.headers.get(`X-Diagram-Pipeline`)??n).split(`,`).map(e=>e.trim().toLowerCase()).filter(Boolean);return{body:await e.text(),identity:{unit:n,build:r,pipeline:i}}}retryBlockedImage(){if(this.image.src===this.activeImageUrl){if(this.imageFallbackUrl&&this.activeImageUrl.startsWith(`blob:`)){let e=this.imageFallbackUrl;this.imageFallbackUrl=null,this.objectUrl&&URL.revokeObjectURL(this.objectUrl),this.objectUrl=null,this.activeImageUrl=e,this.image.src=e,this.minimapImage.src=e;return}this.setStatus(`The rendered image was blocked or invalid.`,`error`)}}svgBlobFor(e){let{type:t,source:n,options:r={},meta:i={},presentation:a={}}=e,o=JSON.stringify({type:t,source:n,options:r,meta:i,presentation:a});return this.latestRenderKey===o?this.latestSvgBlob:null}rendererIdentityFor(e){let{type:t,source:n,options:r={},meta:i={},presentation:a={}}=e,o=JSON.stringify({type:t,source:n,options:r,meta:i,presentation:a});return this.latestRenderKey===o?this.latestRendererIdentity:null}async errorMessage(e){let t=await e.text();if(e.headers.get(`Content-Type`)?.includes(`application/json`))try{let e=JSON.parse(t);if(typeof e?.error?.message==`string`)return e.error.message}catch{}return new DOMParser().parseFromString(t,`text/html`).body.textContent.trim().replace(/\s+/g,` `)||`Render failed with HTTP ${e.status}.`}normalizedSvgBlob(e){let t=new DOMParser().parseFromString(e,`image/svg+xml`).documentElement,n=t.getAttribute(`viewBox`)?.trim().split(/[\s,]+/).map(Number);t.nodeName===`svg`&&n?.length===4&&n.every(Number.isFinite)&&(t.hasAttribute(`width`)||t.setAttribute(`width`,String(n[2])),t.hasAttribute(`height`)||t.setAttribute(`height`,String(n[3])),e=new XMLSerializer().serializeToString(t));let r=t.style.backgroundColor||t.style.background,i=r&&r!==`transparent`?r:`#ffffff`;return{blob:new Blob([e],{type:`image/svg+xml`}),background:i}}setStatus(e,t){this.status.textContent=e,this.status.dataset.state=t}zoom(e){if(this.image.hidden)return;let t=this.stage.getBoundingClientRect();this.zoomAt(e,t.width/2,t.height/2)}zoomAt(e,t,n){let r=W(this.scale*e,.1,8),i=(t-this.x)/this.scale,a=(n-this.y)/this.scale;this.x=t-i*r,this.y=n-a*r,this.scale=r,this.updateTransform()}wheelZoom(e){if(this.image.hidden||e.target.closest(`.preview-toolbar, .minimap`))return;e.preventDefault();let t=this.stage.getBoundingClientRect(),n=e.deltaY*(e.deltaMode===WheelEvent.DOM_DELTA_LINE?16:e.deltaMode===WheelEvent.DOM_DELTA_PAGE?t.height:1);this.zoomAt(Math.exp(-W(n,-240,240)*.002),e.clientX-t.left,e.clientY-t.top)}oneToOne(){if(this.image.hidden)return;let e=this.stage.getBoundingClientRect();this.scale=1,this.x=(e.width-this.image.naturalWidth)/2,this.y=(e.height-this.image.naturalHeight)/2,this.updateTransform()}fit(){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let e=this.stage.getBoundingClientRect(),t=Math.min(64,e.width*.12);this.scale=W(Math.min((e.width-t*2)/this.image.naturalWidth,(e.height-t*2)/this.image.naturalHeight),.1,2),this.x=(e.width-this.image.naturalWidth*this.scale)/2,this.y=(e.height-this.image.naturalHeight*this.scale)/2,this.updateTransform()}updateTransform(){this.image.style.transform=`translate(${this.x}px, ${this.y}px) scale(${this.scale})`,this.updateMinimap()}startPan(e){this.image.hidden||e.button!==0||e.target.closest(`.preview-toolbar, .minimap`)||(this.drag={pointerId:e.pointerId,x:e.clientX,y:e.clientY,originX:this.x,originY:this.y},this.stage.setPointerCapture(e.pointerId),this.stage.dataset.dragging=`true`)}pan(e){!this.drag||e.pointerId!==this.drag.pointerId||(this.x=this.drag.originX+e.clientX-this.drag.x,this.y=this.drag.originY+e.clientY-this.drag.y,this.updateTransform())}endPan(e){!this.drag||e.pointerId!==this.drag.pointerId||(this.drag=null,delete this.stage.dataset.dragging)}moveFromMinimap(e){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let t=this.minimap.getBoundingClientRect(),n=Math.min(t.width/this.image.naturalWidth,t.height/this.image.naturalHeight),r=(t.width-this.image.naturalWidth*n)/2,i=(t.height-this.image.naturalHeight*n)/2,a=(e.clientX-t.left-r)/n,o=(e.clientY-t.top-i)/n,s=this.stage.getBoundingClientRect();this.x=s.width/2-a*this.scale,this.y=s.height/2-o*this.scale,this.updateTransform()}updateMinimap(){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let e=this.stage.getBoundingClientRect(),t=this.image.naturalWidth*this.scale,n=this.image.naturalHeight*this.scale,r=this.x>=0&&this.y>=0&&this.x+t<=e.width&&this.y+n<=e.height;if(this.minimap.hidden=r,r)return;let i=this.minimap.getBoundingClientRect(),a=Math.min(i.width/this.image.naturalWidth,i.height/this.image.naturalHeight),o=(i.width-this.image.naturalWidth*a)/2,s=(i.height-this.image.naturalHeight*a)/2,c=W(-this.x/this.scale,0,this.image.naturalWidth),l=W(-this.y/this.scale,0,this.image.naturalHeight),u=W((e.width-this.x)/this.scale,0,this.image.naturalWidth),d=W((e.height-this.y)/this.scale,0,this.image.naturalHeight);this.minimapViewport.style.left=`${o+c*a}px`,this.minimapViewport.style.top=`${s+l*a}px`,this.minimapViewport.style.width=`${Math.max(4,(u-c)*a)}px`,this.minimapViewport.style.height=`${Math.max(4,(d-l)*a)}px`}},zn=`diagram.zip:draft:local:v2`,G=`diagram.zip:draft:alias:v1:`,Bn=`diagram.zip:write:v1:`,K=new Et,Vn,Hn,q=!1,Un=``,J={aliasId:null,contentId:null,renderId:null,revision:null,mode:`open`,savedMode:null,writeCapability:null,savedState:null,savedSnapshot:null,bundleKey:null,keyEnvelope:null,encryptedContent:null,encryptedMetadata:null,keyEnvelopeDirty:!1,dirty:!0},Wn=new Map;document.querySelector(`#app`).innerHTML=`16381639 >>>>>>>> 0737bab1 (migrate PlantUML family to edge Wasm):server/src/main/resources/web/diagramzip/assets/index-Cv7q_F0Y.js1640+========1641+${h}.`);break;case m:switch(t){case`xml`:break;case``:e.fail(`the default namespace may not be set to ${n}.`);break;default:e.fail(`may not assign the xml namespace to another prefix.`)}}}function L(e,t){for(let n of Object.keys(t))$e(e,n,t[n])}var et=e=>p.test(e),tt=e=>l.test(e),R=0,nt=1,rt=2;e.EVENTS=[`xmldecl`,`text`,`processinginstruction`,`doctype`,`comment`,`opentagstart`,`attribute`,`opentag`,`closetag`,`cdata`,`error`,`end`,`ready`];var z={xmldecl:`xmldeclHandler`,text:`textHandler`,processinginstruction:`piHandler`,doctype:`doctypeHandler`,comment:`commentHandler`,opentagstart:`openTagStartHandler`,attribute:`attributeHandler`,opentag:`openTagHandler`,closetag:`closeTagHandler`,cdata:`cdataHandler`,error:`errorHandler`,end:`endHandler`,ready:`readyHandler`};e.SaxesParser=class{constructor(e){this.opt=e??{},this.fragmentOpt=!!this.opt.fragment;let t=this.xmlnsOpt=!!this.opt.xmlns;if(this.trackPosition=this.opt.position!==!1,this.fileName=this.opt.fileName,t){this.nameStartCheck=d,this.nameCheck=f,this.isName=et,this.processAttribs=this.processAttribsNS,this.pushAttrib=this.pushAttribNS,this.ns=Object.assign({__proto__:null},ee);let e=this.opt.additionalNamespaces;e!=null&&(L(this,e),Object.assign(this.ns,e))}else this.nameStartCheck=o,this.nameCheck=s,this.isName=tt,this.processAttribs=this.processAttribsPlain,this.pushAttrib=this.pushAttribPlain;this.stateTable=[this.sBegin,this.sBeginWhitespace,this.sDoctype,this.sDoctypeQuote,this.sDTD,this.sDTDQuoted,this.sDTDOpenWaka,this.sDTDOpenWakaBang,this.sDTDComment,this.sDTDCommentEnding,this.sDTDCommentEnded,this.sDTDPI,this.sDTDPIEnding,this.sText,this.sEntity,this.sOpenWaka,this.sOpenWakaBang,this.sComment,this.sCommentEnding,this.sCommentEnded,this.sCData,this.sCDataEnding,this.sCDataEnding2,this.sPIFirstChar,this.sPIRest,this.sPIBody,this.sPIEnding,this.sXMLDeclNameStart,this.sXMLDeclName,this.sXMLDeclEq,this.sXMLDeclValueStart,this.sXMLDeclValue,this.sXMLDeclSeparator,this.sXMLDeclEnding,this.sOpenTag,this.sOpenTagSlash,this.sAttrib,this.sAttribName,this.sAttribNameSawWhite,this.sAttribValue,this.sAttribValueQuoted,this.sAttribValueClosed,this.sAttribValueUnquoted,this.sCloseTag,this.sCloseTagSawWhite],this._init()}get closed(){return this._closed}_init(){var e;this.openWakaBang=``,this.text=``,this.name=``,this.piTarget=``,this.entity=``,this.q=null,this.tags=[],this.tag=null,this.topNS=null,this.chunk=``,this.chunkPosition=0,this.i=0,this.prevI=0,this.carriedFromPrevious=void 0,this.forbiddenState=R,this.attribList=[];let{fragmentOpt:t}=this;this.state=t?y:ne,this.reportedTextBeforeRoot=this.reportedTextAfterRoot=this.closedRoot=this.sawRoot=t,this.xmlDeclPossible=!t,this.xmlDeclExpects=[`version`],this.entityReturnState=void 0;let{defaultXMLVersion:n}=this.opt;if(n===void 0){if(this.opt.forceXMLVersion===!0)throw Error(`forceXMLVersion set but defaultXMLVersion is not set`);n=`1.0`}this.setXMLVersion(n),this.positionAtNewLine=0,this.doctype=!1,this._closed=!1,this.xmlDecl={version:void 0,encoding:void 0,standalone:void 0},this.line=1,this.column=0,this.ENTITIES=Object.create(te),(e=this.readyHandler)==null||e.call(this)}get position(){return this.chunkPosition+this.i}get columnIndex(){return this.position-this.positionAtNewLine}on(e,t){this[z[e]]=t}off(e){this[z[e]]=void 0}makeError(e){let t=this.fileName??``;return this.trackPosition&&(t.length>0&&(t+=`:`),t+=`${this.line}:${this.column}`),t.length>0&&(t+=`: `),Error(t+e)}fail(e){let t=this.makeError(e),n=this.errorHandler;if(n===void 0)throw t;return n(t),this}write(e){if(this.closed)return this.fail(`cannot write after close; assign an onready handler.`);let t=!1;e===null?(t=!0,e=``):typeof e==`object`&&(e=e.toString()),this.carriedFromPrevious!==void 0&&(e=`${this.carriedFromPrevious}${e}`,this.carriedFromPrevious=void 0);let n=e.length,r=e.charCodeAt(n-1);!t&&(r===Ve||r>=55296&&r<=56319)&&(this.carriedFromPrevious=e[n-1],n--,e=e.slice(0,n));let{stateTable:i}=this;for(this.chunk=e,this.i=0;this.i<n;)i[this.state].call(this);return this.chunkPosition+=n,t?this.end():this}close(){return this.write(null)}getCode10(){let{chunk:e,i:t}=this;if(this.prevI=t,this.i=t+1,t>=e.length)return g;let n=e.charCodeAt(t);if(this.column++,n<55296){if(n>=He||n===Be)return n;switch(n){case T:return this.line++,this.column=0,this.positionAtNewLine=this.position,T;case Ve:return e.charCodeAt(t+1)===T&&(this.i=t+2),this.line++,this.column=0,this.positionAtNewLine=this.position,_;default:return this.fail(`disallowed character.`),n}}if(n>56319)return n>=57344&&n<=65533||this.fail(`disallowed character.`),n;let r=65536+(n-55296)*1024+(e.charCodeAt(t+1)-56320);return this.i=t+2,r>1114111&&this.fail(`disallowed character.`),r}getCode11(){let{chunk:e,i:t}=this;if(this.prevI=t,this.i=t+1,t>=e.length)return g;let n=e.charCodeAt(t);if(this.column++,n<55296){if(n>31&&n<127||n>159&&n!==P||n===Be)return n;switch(n){case T:return this.line++,this.column=0,this.positionAtNewLine=this.position,T;case Ve:{let n=e.charCodeAt(t+1);(n===T||n===N)&&(this.i=t+2)}case N:case P:return this.line++,this.column=0,this.positionAtNewLine=this.position,_;default:return this.fail(`disallowed character.`),n}}if(n>56319)return n>=57344&&n<=65533||this.fail(`disallowed character.`),n;let r=65536+(n-55296)*1024+(e.charCodeAt(t+1)-56320);return this.i=t+2,r>1114111&&this.fail(`disallowed character.`),r}getCodeNorm(){let e=this.getCode();return e===_?T:e}unget(){this.i=this.prevI,this.column--}captureTo(e){let{i:t}=this,{chunk:n}=this;for(;;){let r=this.getCode(),i=r===_,a=i?T:r;if(a===g||e.includes(a))return this.text+=n.slice(t,this.prevI),a;i&&(this.text+=`${n.slice(t,this.prevI)}\n`,t=this.i)}}captureToChar(e){let{i:t}=this,{chunk:n}=this;for(;;){let r=this.getCode();switch(r){case _:this.text+=`${n.slice(t,this.prevI)}\n`,t=this.i,r=T;break;case g:return this.text+=n.slice(t),!1}if(r===e)return this.text+=n.slice(t,this.prevI),!0}}captureNameChars(){let{chunk:e,i:t}=this;for(;;){let n=this.getCode();if(n===g)return this.name+=e.slice(t),g;if(!s(n))return this.name+=e.slice(t,this.prevI),n===_?T:n}}skipSpaces(){for(;;){let e=this.getCodeNorm();if(e===g||!i(e))return e}}setXMLVersion(e){this.currentXMLVersion=e,e===`1.0`?(this.isChar=a,this.getCode=this.getCode10):(this.isChar=u,this.getCode=this.getCode11)}sBegin(){this.chunk.charCodeAt(0)===65279&&(this.i++,this.column++),this.state=re}sBeginWhitespace(){let e=this.i,t=this.skipSpaces();switch(this.prevI!==e&&(this.xmlDeclPossible=!1),t){case D:if(this.state=he,this.text.length!==0)throw Error(`no-empty text at start`);break;case g:break;default:this.unget(),this.state=y,this.xmlDeclPossible=!1}}sDoctype(){var e;let t=this.captureTo(Xe);switch(t){case k:(e=this.doctypeHandler)==null||e.call(this,this.text),this.text=``,this.state=y,this.doctype=!0;break;case g:break;default:this.text+=String.fromCodePoint(t),t===j?this.state=v:F(t)&&(this.state=ae,this.q=t)}}sDoctypeQuote(){let e=this.q;this.captureToChar(e)&&(this.text+=String.fromCodePoint(e),this.q=null,this.state=ie)}sDTD(){let e=this.captureTo(Ze);e!==g&&(this.text+=String.fromCodePoint(e),e===M?this.state=ie:e===D?this.state=se:F(e)&&(this.state=oe,this.q=e))}sDTDQuoted(){let e=this.q;this.captureToChar(e)&&(this.text+=String.fromCodePoint(e),this.state=v,this.q=null)}sDTDOpenWaka(){let e=this.getCodeNorm();switch(this.text+=String.fromCodePoint(e),e){case Ue:this.state=ce,this.openWakaBang=``;break;case A:this.state=fe;break;default:this.state=v}}sDTDOpenWakaBang(){let e=String.fromCodePoint(this.getCodeNorm()),t=this.openWakaBang+=e;this.text+=e,t!==`-`&&(this.state=t===`--`?le:v,this.openWakaBang=``)}sDTDComment(){this.captureToChar(qe)&&(this.text+=`-`,this.state=ue)}sDTDCommentEnding(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),this.state=e===qe?de:le}sDTDCommentEnded(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),e===k?this.state=v:(this.fail(`malformed comment.`),this.state=le)}sDTDPI(){this.captureToChar(A)&&(this.text+=`?`,this.state=pe)}sDTDPIEnding(){let e=this.getCodeNorm();this.text+=String.fromCodePoint(e),e===k&&(this.state=v)}sText(){this.tags.length===0?this.handleTextOutsideRoot():this.handleTextInRoot()}sEntity(){let{i:e}=this,{chunk:t}=this;loop:for(;;)switch(this.getCode()){case _:this.entity+=`${t.slice(e,this.prevI)}\n`,e=this.i;break;case Je:{let{entityReturnState:n}=this,r=this.entity+t.slice(e,this.prevI);this.state=n;let i;r===``?(this.fail(`empty entity name.`),i=`&;`):(i=this.parseEntity(r),this.entity=``),(n!==y||this.textHandler!==void 0)&&(this.text+=i);break loop}case g:this.entity+=t.slice(e);break loop}}sOpenWaka(){let e=this.getCode();if(o(e))this.state=Ne,this.unget(),this.xmlDeclPossible=!1;else switch(e){case E:this.state=Re,this.xmlDeclPossible=!1;break;case Ue:this.state=ge,this.openWakaBang=``,this.xmlDeclPossible=!1;break;case A:this.state=Ce;break;default:this.fail(`disallowed character in tag name`),this.state=y,this.xmlDeclPossible=!1}}sOpenWakaBang(){switch(this.openWakaBang+=String.fromCodePoint(this.getCodeNorm()),this.openWakaBang){case`[CDATA[`:!this.sawRoot&&!this.reportedTextBeforeRoot&&(this.fail(`text data outside of root node.`),this.reportedTextBeforeRoot=!0),this.closedRoot&&!this.reportedTextAfterRoot&&(this.fail(`text data outside of root node.`),this.reportedTextAfterRoot=!0),this.state=be,this.openWakaBang=``;break;case`--`:this.state=_e,this.openWakaBang=``;break;case`DOCTYPE`:this.state=ie,(this.doctype||this.sawRoot)&&this.fail(`inappropriately located doctype declaration.`),this.openWakaBang=``;break;default:this.openWakaBang.length>=7&&this.fail(`incorrect syntax.`)}}sComment(){this.captureToChar(qe)&&(this.state=ve)}sCommentEnding(){var e;let t=this.getCodeNorm();t===qe?(this.state=ye,(e=this.commentHandler)==null||e.call(this,this.text),this.text=``):(this.text+=`-${String.fromCodePoint(t)}`,this.state=_e)}sCommentEnded(){let e=this.getCodeNorm();e===k?this.state=y:(this.fail(`malformed comment.`),this.text+=`--${String.fromCodePoint(e)}`,this.state=_e)}sCData(){this.captureToChar(M)&&(this.state=xe)}sCDataEnding(){let e=this.getCodeNorm();e===M?this.state=Se:(this.text+=`]${String.fromCodePoint(e)}`,this.state=be)}sCDataEnding2(){var e;let t=this.getCodeNorm();switch(t){case k:(e=this.cdataHandler)==null||e.call(this,this.text),this.text=``,this.state=y;break;case M:this.text+=`]`;break;default:this.text+=`]]${String.fromCodePoint(t)}`,this.state=be}}sPIFirstChar(){let e=this.getCodeNorm();this.nameStartCheck(e)?(this.piTarget+=String.fromCodePoint(e),this.state=we):e===A||i(e)?(this.fail(`processing instruction without a target.`),this.state=e===A?Ee:Te):(this.fail(`disallowed character in processing instruction name.`),this.piTarget+=String.fromCodePoint(e),this.state=we)}sPIRest(){let{chunk:e,i:t}=this;for(;;){let n=this.getCodeNorm();if(n===g){this.piTarget+=e.slice(t);return}if(!this.nameCheck(n)){this.piTarget+=e.slice(t,this.prevI);let r=n===A;r||i(n)?this.piTarget===`xml`?(this.xmlDeclPossible||this.fail(`an XML declaration must be at the start of the document.`),this.state=r?b:De):this.state=r?Ee:Te:(this.fail(`disallowed character in processing instruction name.`),this.piTarget+=String.fromCodePoint(n));break}}}sPIBody(){if(this.text.length===0){let e=this.getCodeNorm();e===A?this.state=Ee:i(e)||(this.text=String.fromCodePoint(e))}else this.captureToChar(A)&&(this.state=Ee)}sPIEnding(){var e;let t=this.getCodeNorm();if(t===k){let{piTarget:t}=this;t.toLowerCase()===`xml`&&this.fail(`the XML declaration must appear at the start of the document.`),(e=this.piHandler)==null||e.call(this,{target:t,body:this.text}),this.piTarget=this.text=``,this.state=y}else t===A?this.text+=`?`:(this.text+=`?${String.fromCodePoint(t)}`,this.state=Te);this.xmlDeclPossible=!1}sXMLDeclNameStart(){let e=this.skipSpaces();if(e===A){this.state=b;return}e!==g&&(this.state=Oe,this.name=String.fromCodePoint(e))}sXMLDeclName(){let e=this.captureTo(Qe);if(e===A){this.state=b,this.name+=this.text,this.text=``,this.fail(`XML declaration is incomplete.`);return}if(i(e)||e===O){if(this.name+=this.text,this.text=``,!this.xmlDeclExpects.includes(this.name))switch(this.name.length){case 0:this.fail(`did not expect any more name/value pairs.`);break;case 1:this.fail(`expected the name ${this.xmlDeclExpects[0]}.`);break;default:this.fail(`expected one of ${this.xmlDeclExpects.join(`, `)}`)}this.state=e===O?Ae:ke}}sXMLDeclEq(){let e=this.getCodeNorm();if(e===A){this.state=b,this.fail(`XML declaration is incomplete.`);return}i(e)||(e!==O&&this.fail(`value required.`),this.state=Ae)}sXMLDeclValueStart(){let e=this.getCodeNorm();if(e===A){this.state=b,this.fail(`XML declaration is incomplete.`);return}i(e)||(F(e)?this.q=e:(this.fail(`value must be quoted.`),this.q=He),this.state=je)}sXMLDeclValue(){let e=this.captureTo([this.q,A]);if(e===A){this.state=b,this.text=``,this.fail(`XML declaration is incomplete.`);return}if(e===g)return;let t=this.text;switch(this.text=``,this.name){case`version`:{this.xmlDeclExpects=[`encoding`,`standalone`];let e=t;this.xmlDecl.version=e,/^1\.[0-9]+$/.test(e)?this.opt.forceXMLVersion||this.setXMLVersion(e):this.fail(`version number must match /^1\\.[0-9]+$/.`);break}case`encoding`:/^[A-Za-z][A-Za-z0-9._-]*$/.test(t)||this.fail(`encoding value must match /^[A-Za-z0-9][A-Za-z0-9._-]*$/.`),this.xmlDeclExpects=[`standalone`],this.xmlDecl.encoding=t;break;case`standalone`:t!==`yes`&&t!==`no`&&this.fail(`standalone value must match "yes" or "no".`),this.xmlDeclExpects=[],this.xmlDecl.standalone=t}this.name=``,this.state=Me}sXMLDeclSeparator(){let e=this.getCodeNorm();if(e===A){this.state=b;return}i(e)||(this.fail(`whitespace required.`),this.unget()),this.state=De}sXMLDeclEnding(){var e;this.getCodeNorm()===k?(this.piTarget===`xml`?this.name!==`version`&&this.xmlDeclExpects.includes(`version`)&&this.fail(`XML declaration must contain a version.`):this.fail(`processing instructions are not allowed before root.`),(e=this.xmldeclHandler)==null||e.call(this,this.xmlDecl),this.name=``,this.piTarget=this.text=``,this.state=y):this.fail(`The character ? is disallowed anywhere in XML declarations.`),this.xmlDeclPossible=!1}sOpenTag(){var e;let t=this.captureNameChars();if(t===g)return;let n=this.tag={name:this.name,attributes:Object.create(null)};switch(this.name=``,this.xmlnsOpt&&(this.topNS=n.ns=Object.create(null)),(e=this.openTagStartHandler)==null||e.call(this,n),this.sawRoot=!0,!this.fragmentOpt&&this.closedRoot&&this.fail(`documents may contain only one root.`),t){case k:this.openTag();break;case E:this.state=Pe;break;default:i(t)||this.fail(`disallowed character in tag name.`),this.state=x}}sOpenTagSlash(){this.getCode()===k?this.openSelfClosingTag():(this.fail(`forward-slash in opening tag not followed by >.`),this.state=x)}sAttrib(){let e=this.skipSpaces();e!==g&&(o(e)?(this.unget(),this.state=S):e===k?this.openTag():e===E?this.state=Pe:this.fail(`disallowed character in attribute name.`))}sAttribName(){let e=this.captureNameChars();e===O?this.state=w:i(e)?this.state=C:e===k?(this.fail(`attribute without value.`),this.pushAttrib(this.name,this.name),this.name=this.text=``,this.openTag()):e!==g&&this.fail(`disallowed character in attribute name.`)}sAttribNameSawWhite(){let e=this.skipSpaces();switch(e){case g:return;case O:this.state=w;break;default:this.fail(`attribute without value.`),this.text=``,this.name=``,e===k?this.openTag():o(e)?(this.unget(),this.state=S):(this.fail(`disallowed character in attribute name.`),this.state=x)}}sAttribValue(){let e=this.getCodeNorm();F(e)?(this.q=e,this.state=Fe):i(e)||(this.fail(`unquoted attribute value.`),this.state=Le,this.unget())}sAttribValueQuoted(){let{q:e,chunk:t}=this,{i:n}=this;for(;;)switch(this.getCode()){case e:this.pushAttrib(this.name,this.text+t.slice(n,this.prevI)),this.name=this.text=``,this.q=null,this.state=Ie;return;case Ge:this.text+=t.slice(n,this.prevI),this.state=me,this.entityReturnState=Fe;return;case T:case _:case Be:this.text+=`${t.slice(n,this.prevI)} `,n=this.i;break;case D:this.text+=t.slice(n,this.prevI),this.fail(`disallowed character.`);return;case g:this.text+=t.slice(n);return}}sAttribValueClosed(){let e=this.getCodeNorm();i(e)?this.state=x:e===k?this.openTag():e===E?this.state=Pe:o(e)?(this.fail(`no whitespace between attributes.`),this.unget(),this.state=S):this.fail(`disallowed character in attribute name.`)}sAttribValueUnquoted(){let e=this.captureTo(I);switch(e){case Ge:this.state=me,this.entityReturnState=Le;break;case D:this.fail(`disallowed character.`);break;case g:break;default:this.text.includes(`]]>`)&&this.fail(`the string "]]>" is disallowed in char data.`),this.pushAttrib(this.name,this.text),this.name=this.text=``,e===k?this.openTag():this.state=x}}sCloseTag(){let e=this.captureNameChars();e===k?this.closeTag():i(e)?this.state=ze:e!==g&&this.fail(`disallowed character in closing tag.`)}sCloseTagSawWhite(){switch(this.skipSpaces()){case k:this.closeTag();break;case g:break;default:this.fail(`disallowed character in closing tag.`)}}handleTextInRoot(){let{i:e,forbiddenState:t}=this,{chunk:n,textHandler:r}=this;scanLoop:for(;;)switch(this.getCode()){case D:if(this.state=he,r!==void 0){let{text:t}=this,i=n.slice(e,this.prevI);t.length===0?i.length!==0&&r(i):(r(t+i),this.text=``)}t=R;break scanLoop;case Ge:this.state=me,this.entityReturnState=y,r!==void 0&&(this.text+=n.slice(e,this.prevI)),t=R;break scanLoop;case M:switch(t){case R:t=nt;break;case nt:t=rt;break;case rt:break;default:throw Error(`impossible state`)}break;case k:t===rt&&this.fail(`the string "]]>" is disallowed in char data.`),t=R;break;case _:r!==void 0&&(this.text+=`${n.slice(e,this.prevI)}\n`),e=this.i,t=R;break;case g:r!==void 0&&(this.text+=n.slice(e));break scanLoop;default:t=R}this.forbiddenState=t}handleTextOutsideRoot(){let{i:e}=this,{chunk:t,textHandler:n}=this,r=!1;outRootLoop:for(;;){let a=this.getCode();switch(a){case D:if(this.state=he,n!==void 0){let{text:r}=this,i=t.slice(e,this.prevI);r.length===0?i.length!==0&&n(i):(n(r+i),this.text=``)}break outRootLoop;case Ge:this.state=me,this.entityReturnState=y,n!==void 0&&(this.text+=t.slice(e,this.prevI)),r=!0;break outRootLoop;case _:n!==void 0&&(this.text+=`${t.slice(e,this.prevI)}\n`),e=this.i;break;case g:n!==void 0&&(this.text+=t.slice(e));break outRootLoop;default:i(a)||(r=!0)}}r&&(!this.sawRoot&&!this.reportedTextBeforeRoot&&(this.fail(`text data outside of root node.`),this.reportedTextBeforeRoot=!0),this.closedRoot&&!this.reportedTextAfterRoot&&(this.fail(`text data outside of root node.`),this.reportedTextAfterRoot=!0))}pushAttribNS(e,t){var n;let{prefix:r,local:i}=this.qname(e),a={name:e,prefix:r,local:i,value:t};if(this.attribList.push(a),(n=this.attributeHandler)==null||n.call(this,a),r===`xmlns`){let e=t.trim();this.currentXMLVersion===`1.0`&&e===``&&this.fail(`invalid attempt to undefine prefix in XML 1.0`),this.topNS[i]=e,$e(this,i,e)}else if(e===`xmlns`){let e=t.trim();this.topNS[``]=e,$e(this,``,e)}}pushAttribPlain(e,t){var n;let r={name:e,value:t};this.attribList.push(r),(n=this.attributeHandler)==null||n.call(this,r)}end(){var e,t;this.sawRoot||this.fail(`document must contain a root element.`);let{tags:n}=this;for(;n.length>0;){let e=n.pop();this.fail(`unclosed tag: ${e.name}`)}this.state!==ne&&this.state!==y&&this.fail(`unexpected end.`);let{text:r}=this;return r.length!==0&&((e=this.textHandler)==null||e.call(this,r),this.text=``),this._closed=!0,(t=this.endHandler)==null||t.call(this),this._init(),this}resolve(e){var t;let n=this.topNS[e];if(n!==void 0)return n;let{tags:r}=this;for(let t=r.length-1;t>=0;t--)if(n=r[t].ns[e],n!==void 0)return n;return n=this.ns[e],n===void 0?(t=this.opt).resolvePrefix?.call(t,e):n}qname(e){let t=e.indexOf(`:`);if(t===-1)return{prefix:``,local:e};let n=e.slice(t+1),r=e.slice(0,t);return(r===``||n===``||n.includes(`:`))&&this.fail(`malformed name: ${e}.`),{prefix:r,local:n}}processAttribsNS(){let{attribList:e}=this,t=this.tag;{let{prefix:e,local:n}=this.qname(t.name);t.prefix=e,t.local=n;let r=t.uri=this.resolve(e)??``;e!==``&&(e===`xmlns`&&this.fail(`tags may not have "xmlns" as prefix.`),r===``&&(this.fail(`unbound namespace prefix: ${JSON.stringify(e)}.`),t.uri=e))}if(e.length===0)return;let{attributes:n}=t,r=new Set;for(let t of e){let{name:e,prefix:i,local:a}=t,o,s;i===``?(o=e===`xmlns`?h:``,s=e):(o=this.resolve(i),o===void 0&&(this.fail(`unbound namespace prefix: ${JSON.stringify(i)}.`),o=i),s=`{${o}}${a}`),r.has(s)&&this.fail(`duplicate attribute: ${s}.`),r.add(s),t.uri=o,n[e]=t}this.attribList=[]}processAttribsPlain(){let{attribList:e}=this,t=this.tag.attributes;for(let{name:n,value:r}of e)t[n]!==void 0&&this.fail(`duplicate attribute: ${n}.`),t[n]=r;this.attribList=[]}openTag(){var e;this.processAttribs();let{tags:t}=this,n=this.tag;n.isSelfClosing=!1,(e=this.openTagHandler)==null||e.call(this,n),t.push(n),this.state=y,this.name=``}openSelfClosingTag(){var e,t;this.processAttribs();let{tags:n}=this,r=this.tag;r.isSelfClosing=!0,(e=this.openTagHandler)==null||e.call(this,r),(t=this.closeTagHandler)==null||t.call(this,r),(this.tag=n[n.length-1]??null)===null&&(this.closedRoot=!0),this.state=y,this.name=``}closeTag(){let{tags:e,name:t}=this;if(this.state=y,this.name=``,t===``){this.fail(`weird empty close tag.`),this.text+=`</>`;return}let n=this.closeTagHandler,r=e.length;for(;r-->0;){let r=this.tag=e.pop();if(this.topNS=r.ns,n?.(r),r.name===t)break;this.fail(`unexpected close tag.`)}r===0?this.closedRoot=!0:r<0&&(this.fail(`unmatched closing tag: ${t}.`),this.text+=`</${t}>`)}parseEntity(e){if(e[0]!==`#`){let t=this.ENTITIES[e];return t===void 0?(this.fail(this.isName(e)?`undefined entity.`:`disallowed character in entity name.`),`&${e};`):t}let t=NaN;return e[1]===`x`&&/^#x[0-9a-f]+$/i.test(e)?t=parseInt(e.slice(2),16):/^#[0-9]+$/.test(e)&&(t=parseInt(e.slice(1),10)),this.isChar(t)?String.fromCodePoint(t):(this.fail(`malformed character entity.`),`&${e};`)}}}))(),cn=4194304,ln=new Set([`script`,`iframe`,`object`,`embed`,`audio`,`video`]),un=new Set([`foreignobject`,`div`,`span`,`p`,`br`,`b`,`strong`,`i`,`em`,`small`,`sub`,`sup`,`code`,`ul`,`ol`,`li`,`a`]),dn=/^data:image\/(?:png|jpeg|gif|webp);base64,[a-z0-9+/=\s]+$/i,fn=/^data:(?:font\/(?:woff2?|opentype|truetype)|application\/(?:x-)?font-woff2?);base64,[a-z0-9+/=\s]+$/i,pn=class extends Error{constructor(e){super(e),this.name=`ClientSvgError`}};function mn(e){let t=e.trim();return t.startsWith(`#`)||dn.test(t)||fn.test(t)}function hn(e){if(/@import|expression\s*\(|javascript\s*:|data\s*:\s*text\/html|behavior\s*:|-moz-binding/i.test(e))return!0;let t=/url\s*\(\s*(['"]?)(.*?)\1\s*\)/gi;for(let n of e.matchAll(t))if(!mn(n[2]??``))return!0;return/url\s*\(/i.test(e.replace(t,``))}function gn(e){let t=e.trim();return t.startsWith(`#`)||dn.test(t)}function _n(e,t){let n=e.toLowerCase();return n.startsWith(`on`)?!1:n===`href`||n===`xlink:href`||n===`src`?gn(t):[`style`,`filter`,`fill`,`stroke`,`clip-path`,`mask`].includes(n)?!hn(t):n!==`xml:base`&&!/javascript\s*:/i.test(t)}function vn(e){return e.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`)}function yn(e){return vn(e).replaceAll(`"`,`&quot;`)}function bn(e){if(e.type===`text`)return vn(e.value);let t=[...e.attributes].map(([e,t])=>` ${e}="${yn(t)}"`).join(``);return`<${e.name}${t}>${e.children.map(bn).join(``)}</${e.name}>`}function xn(e){return e.name.toLowerCase().split(`:`).at(-1)??e.name.toLowerCase()}function Sn(e){if(e===void 0||!/^-?\d+(?:\.\d+)?(?:px)?$/.test(e.trim()))return null;let t=Number.parseFloat(e);return Number.isFinite(t)&&t>0?t:null}function Cn(e){let t=e.attributes.get(`viewBox`)?.trim().split(/[\s,]+/).map(Number);if(t?.length===4&&t.every(Number.isFinite)&&t[2]>0&&t[3]>0)return t;let n=Sn(e.attributes.get(`width`)),r=Sn(e.attributes.get(`height`));return n!==null&&r!==null?[0,0,n,r]:null}function wn(e,t){let n=e.attributes.get(`style`);if(!n)return null;for(let e of n.split(`;`)){let n=e.indexOf(`:`);if(n>=0&&e.slice(0,n).trim().toLowerCase()===t)return e.slice(n+1).trim()}return null}function Tn(e){let t=(e.attributes.get(`fill`)??wn(e,`fill`)??``).replaceAll(` `,``).toLowerCase();return t===`white`||t===`#fff`||t===`#ffffff`||t===`rgb(255,255,255)`}function En(e){if(e===void 0||!/^-?\d+(?:\.\d+)?$/.test(e.trim()))return null;let t=Number(e);return Number.isFinite(t)?t:null}function Dn(e,t){let n=e.attributes.get(`width`)?.trim(),r=e.attributes.get(`height`)?.trim();if(n===`100%`&&r===`100%`)return!0;let i=En(e.attributes.get(`x`)??`0`),a=En(e.attributes.get(`y`)??`0`),o=En(n),s=En(r);if([i,a,o,s].some(e=>e===null))return!1;let[c,l,u,d]=t,f=.01;return i<=c+f&&a<=l+f&&i+o>=c+u-f&&a+s>=l+d-f}function On(e,t,n){let r=xn(e),i=new Set((e.attributes.get(`class`)??``).toLowerCase().split(/\s+/));return r===`rect`&&i.has(`backdrop`)||r===`rect`&&t!==null&&Tn(e)&&Dn(e,t)?!0:n===`graphviz`&&r===`polygon`&&Tn(e)&&(e.attributes.get(`stroke`)??``).toLowerCase()===`none`}function kn(e,t,n){let r=xn(e)===`svg`?Cn(e)??t:t;e.children=e.children.filter(e=>e.type===`text`||!On(e,r,n)&&(kn(e,r,n),!0))}function An(e,t){let n=(e.attributes.get(`style`)??``).split(`;`).map(e=>e.trim()).filter(Boolean).filter(e=>!/^background(?:-color)?\s*:/i.test(e));n.push(`background-color:${t}`),e.attributes.set(`style`,`${n.join(`;`)};`)}function jn(e,t=[],n=[]){return{type:`element`,name:e,attributes:new Map(t),children:n}}function Mn(e,t,n,r){let i=[],a=null;t.title&&i.push(jn(`title`,[],[{type:`text`,value:t.title}])),t.description&&i.push(jn(`desc`,[],[{type:`text`,value:t.description}]));let o=Cn(e);if((n.padding||n.background||n.frame)&&o===null)throw new pn(`Renderer output has no usable SVG dimensions for presentation settings.`);if(o!==null){let[t,r,s,c]=o,l=n.padding,u=[t-l,r-l,s+l*2,c+l*2];e.attributes.set(`viewBox`,u.join(` `)),e.attributes.has(`width`)&&e.attributes.set(`width`,String(u[2])),e.attributes.has(`height`)&&e.attributes.set(`height`,String(u[3])),n.background&&i.push(jn(`rect`,[[`x`,String(u[0])],[`y`,String(u[1])],[`width`,String(u[2])],[`height`,String(u[3])],[`fill`,n.background]])),n.frame&&(a=jn(`rect`,[[`x`,String(u[0]+.5)],[`y`,String(u[1]+.5)],[`width`,String(Math.max(0,u[2]-1))],[`height`,String(Math.max(0,u[3]-1))],[`fill`,`none`],[`stroke`,`#000000`],[`stroke-width`,`1`],[`vector-effect`,`non-scaling-stroke`]]))}n.background&&(kn(e,o,r),An(e,n.background)),e.children.unshift(...i),a&&e.children.push(a)}function Nn(e,t,n,r){if(e.length>cn)throw new pn(`Rendered SVG is too large.`);let i=null,a=[],o=0,s=null,c=new sn.SaxesParser({xmlns:!0});c.on(`doctype`,e=>{e.includes(`[`)&&(s=Error(`Internal DOCTYPE subsets are not allowed`))}),c.on(`opentag`,e=>{let t=e.local.toLowerCase(),n=t===`foreignobject`||a.some(e=>xn(e)===`foreignobject`);if(o>0||ln.has(t)||n&&!un.has(t)){o++;return}let r=jn(e.name);for(let t of Object.values(e.attributes))_n(t.name,t.value)&&r.attributes.set(t.name,t.value);let c=a.at(-1);c?c.children.push(r):i===null?i=r:s=Error(`Multiple root elements`),a.push(r)});let l=e=>{if(o>0||e===``)return;let t=a.at(-1);if(t){if(xn(t)===`style`&&hn(e))throw new pn(`Renderer output contains unsafe CSS.`);t.children.push({type:`text`,value:e})}else e.trim()!==``&&(s=Error(`Text outside root element`))};c.on(`text`,l),c.on(`cdata`,l),c.on(`closetag`,()=>{o>0?o--:a.pop()}),c.on(`error`,e=>{s=e});try{c.write(e).close()}catch(e){if(e instanceof pn)throw e;s=e}if(s!==null||i===null||xn(i)!==`svg`)throw new pn(`Renderer returned invalid SVG.`);return Mn(i,t,n,r),bn(i)}var Pn=Object.freeze({plantuml:`https://plantuml.render.diagram.zip/v1/svg`,graphviz:`https://graphviz.render.diagram.zip/v1/svg`,d2:`https://d2.render.diagram.zip/v1/svg`,c4plantuml:`https://c4plantuml.render.diagram.zip/v1/svg`,blockdiag:`https://blockdiag.render.diagram.zip/v1/svg`,seqdiag:`https://seqdiag.render.diagram.zip/v1/svg`,actdiag:`https://actdiag.render.diagram.zip/v1/svg`,nwdiag:`https://nwdiag.render.diagram.zip/v1/svg`,packetdiag:`https://packetdiag.render.diagram.zip/v1/svg`,rackdiag:`https://rackdiag.render.diagram.zip/v1/svg`,bytefield:`https://bytefield.render.diagram.zip/v1/svg`,dbml:`https://dbml.render.diagram.zip/v1/svg`,ditaa:`https://ditaa.render.diagram.zip/v1/svg`,erd:`https://erd.render.diagram.zip/v1/svg`,goat:`https://goat.render.diagram.zip/v1/svg`,nomnoml:`https://nomnoml.render.diagram.zip/v1/svg`,pikchr:`https://pikchr.render.diagram.zip/v1/svg`,structurizr:`https://structurizr.render.diagram.zip/v1/svg`,svgbob:`https://svgbob.render.diagram.zip/v1/svg`,symbolator:`https://symbolator.render.diagram.zip/v1/svg`,tikz:`https://tikz.render.diagram.zip/v1/svg`,umlet:`https://umlet.render.diagram.zip/v1/svg`,vega:`https://vega.render.diagram.zip/v1/svg`,vegalite:`https://vegalite.render.diagram.zip/v1/svg`,wavedrom:`https://wavedrom.render.diagram.zip/v1/svg`,wireviz:`https://wireviz.render.diagram.zip/v1/svg`}),Fn=Object.freeze(`mermaid.bpmn.excalidraw.diagramsnet.plantuml.c4plantuml.bytefield.nomnoml.vega.vegalite.wavedrom.blockdiag.seqdiag.actdiag.nwdiag.packetdiag.rackdiag.graphviz.erd.dbml.goat.pikchr.svgbob.ditaa.wireviz.structurizr.symbolator`.split(`.`));function In(e){return Fn.includes(e)}function Ln(e){return Pn[e]??null}function W(e,t,n){return Math.min(Math.max(e,t),n)}var Rn=class{constructor({stage:e,image:t,status:n,minimap:r,minimapImage:i,minimapViewport:a}){this.stage=e,this.image=t,this.status=n,this.minimap=r,this.minimapImage=i,this.minimapViewport=a,this.scale=1,this.x=0,this.y=0,this.objectUrl=null,this.abortController=null,this.requestNumber=0,this.pendingRender=null,this.renderLoop=null,this.latestRenderKey=null,this.latestSvgBlob=null,this.latestRendererIdentity=null,this.imageFallbackUrl=null,this.activeImageUrl=null,this.drag=null,this.image.addEventListener(`load`,()=>{this.image.src===this.activeImageUrl&&(this.imageFallbackUrl=null,this.setStatus(`Rendered`,`ready`),this.fit())}),this.image.addEventListener(`error`,()=>this.retryBlockedImage()),this.stage.addEventListener(`pointerdown`,e=>this.startPan(e)),this.stage.addEventListener(`pointermove`,e=>this.pan(e)),this.stage.addEventListener(`pointerup`,e=>this.endPan(e)),this.stage.addEventListener(`pointercancel`,e=>this.endPan(e)),this.stage.addEventListener(`wheel`,e=>this.wheelZoom(e),{passive:!1}),this.minimap.addEventListener(`pointerdown`,e=>this.moveFromMinimap(e)),new ResizeObserver(()=>this.updateTransform()).observe(this.stage)}render({type:e,source:t,options:n={},meta:r={},presentation:i={}}){if(!t.trim()){this.requestNumber++,this.pendingRender=null,this.abortController?.abort(),this.setStatus(`Write something to render.`,`idle`);return}let a=JSON.stringify({type:e,source:t,options:n,meta:r,presentation:i});return this.latestRenderKey===a&&this.latestSvgBlob?Promise.resolve():(this.pendingRender={type:e,source:t,options:n,meta:r,presentation:i,renderKey:a,requestNumber:++this.requestNumber},this.abortController?.abort(),this.renderLoop||=this.drainRenderQueue(),this.renderLoop)}async drainRenderQueue(){try{for(;this.pendingRender;){let e=this.pendingRender;this.pendingRender=null,await this.performRender(e)}}finally{this.renderLoop=null,this.pendingRender&&(this.renderLoop=this.drainRenderQueue())}}async performRender({type:e,source:t,options:n,meta:r,presentation:i,renderKey:a,requestNumber:o}){let s=new AbortController;this.abortController=s,this.setStatus(`Rendering…`,`loading`),this.stage.style.setProperty(`--render-background`,`var(--preview-bg)`);try{let c=nn(e),l;if(c)try{let a=await c.render({type:e,source:t,options:n},s.signal);l={body:Nn(a.body,r,i,e),identity:{unit:e,build:a.build||a.version,pipeline:Array.isArray(a.pipeline)?a.pipeline:[e]}},this.status.dataset.cache=`browser`,this.status.dataset.renderer=e}catch(a){if(a.name===`AbortError`||In(e))throw a;l=await this.renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},s.signal)}else l=await this.renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},s.signal);if(o!==this.requestNumber)return;let{blob:u,background:d}=this.normalizedSvgBlob(l.body);this.latestRenderKey=a,this.latestSvgBlob=u,this.latestRendererIdentity=l.identity,this.imageFallbackUrl=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(await u.text())}`,this.stage.style.setProperty(`--render-background`,d);let f=URL.createObjectURL(u),p=this.objectUrl;this.objectUrl=f,this.activeImageUrl=f,this.image.src=f,this.minimapImage.src=f,this.image.hidden=!1,this.setStatus(`Loading image…`,`loading`),p&&URL.revokeObjectURL(p)}catch(e){if(e.name===`AbortError`)return;this.setStatus(e.message||`Could not render this diagram.`,`error`)}finally{this.abortController===s&&(this.abortController=null)}}async renderThroughGateway({type:e,source:t,options:n,meta:r,presentation:i},a){let o=Ln(e);if(o){try{let s=await this.renderRequest(o,{source:t,format:`svg`,options:n,metadata:r,presentation:i},a);if(s.ok||s.status<500)return await this.renderResponse(s,e);await s.body?.cancel()}catch(t){if(t.name===`AbortError`||In(e))throw t}if(In(e))throw Error(`The ${e} renderer unit is unavailable.`)}let s=await this.renderRequest(`/render/v1/svg`,{engine:e,source:t,format:`svg`,options:n,metadata:r,presentation:i},a);return this.renderResponse(s,e)}renderRequest(e,t,n){return fetch(e,{method:`POST`,headers:{Accept:`image/svg+xml`,"Content-Type":`application/json`},body:JSON.stringify(t),signal:n})}async renderResponse(e,t){if(!e.ok)throw Error(await this.errorMessage(e));this.status.dataset.cache=e.headers.get(`X-Diagram-Cache`)?.toLowerCase()??`miss`,this.status.dataset.renderer=e.headers.get(`X-Diagram-Unit`)?.toLowerCase()??e.headers.get(`X-Diagram-Renderer`)?.toLowerCase()??`gateway`;let n=e.headers.get(`X-Diagram-Unit`)?.toLowerCase()??e.headers.get(`X-Diagram-Engine`)?.toLowerCase()??t,r=e.headers.get(`X-Renderer-Build`)??e.headers.get(`X-Diagram-Engine-Version`)??`${n}-unknown`,i=(e.headers.get(`X-Diagram-Pipeline`)??n).split(`,`).map(e=>e.trim().toLowerCase()).filter(Boolean);return{body:await e.text(),identity:{unit:n,build:r,pipeline:i}}}retryBlockedImage(){if(this.image.src===this.activeImageUrl){if(this.imageFallbackUrl&&this.activeImageUrl.startsWith(`blob:`)){let e=this.imageFallbackUrl;this.imageFallbackUrl=null,this.objectUrl&&URL.revokeObjectURL(this.objectUrl),this.objectUrl=null,this.activeImageUrl=e,this.image.src=e,this.minimapImage.src=e;return}this.setStatus(`The rendered image was blocked or invalid.`,`error`)}}svgBlobFor(e){let{type:t,source:n,options:r={},meta:i={},presentation:a={}}=e,o=JSON.stringify({type:t,source:n,options:r,meta:i,presentation:a});return this.latestRenderKey===o?this.latestSvgBlob:null}rendererIdentityFor(e){let{type:t,source:n,options:r={},meta:i={},presentation:a={}}=e,o=JSON.stringify({type:t,source:n,options:r,meta:i,presentation:a});return this.latestRenderKey===o?this.latestRendererIdentity:null}async errorMessage(e){let t=await e.text();if(e.headers.get(`Content-Type`)?.includes(`application/json`))try{let e=JSON.parse(t);if(typeof e?.error?.message==`string`)return e.error.message}catch{}return new DOMParser().parseFromString(t,`text/html`).body.textContent.trim().replace(/\s+/g,` `)||`Render failed with HTTP ${e.status}.`}normalizedSvgBlob(e){let t=new DOMParser().parseFromString(e,`image/svg+xml`).documentElement,n=t.getAttribute(`viewBox`)?.trim().split(/[\s,]+/).map(Number);t.nodeName===`svg`&&n?.length===4&&n.every(Number.isFinite)&&(t.hasAttribute(`width`)||t.setAttribute(`width`,String(n[2])),t.hasAttribute(`height`)||t.setAttribute(`height`,String(n[3])),e=new XMLSerializer().serializeToString(t));let r=t.style.backgroundColor||t.style.background,i=r&&r!==`transparent`?r:`#ffffff`;return{blob:new Blob([e],{type:`image/svg+xml`}),background:i}}setStatus(e,t){this.status.textContent=e,this.status.dataset.state=t}zoom(e){if(this.image.hidden)return;let t=this.stage.getBoundingClientRect();this.zoomAt(e,t.width/2,t.height/2)}zoomAt(e,t,n){let r=W(this.scale*e,.1,8),i=(t-this.x)/this.scale,a=(n-this.y)/this.scale;this.x=t-i*r,this.y=n-a*r,this.scale=r,this.updateTransform()}wheelZoom(e){if(this.image.hidden||e.target.closest(`.preview-toolbar, .minimap`))return;e.preventDefault();let t=this.stage.getBoundingClientRect(),n=e.deltaY*(e.deltaMode===WheelEvent.DOM_DELTA_LINE?16:e.deltaMode===WheelEvent.DOM_DELTA_PAGE?t.height:1);this.zoomAt(Math.exp(-W(n,-240,240)*.002),e.clientX-t.left,e.clientY-t.top)}oneToOne(){if(this.image.hidden)return;let e=this.stage.getBoundingClientRect();this.scale=1,this.x=(e.width-this.image.naturalWidth)/2,this.y=(e.height-this.image.naturalHeight)/2,this.updateTransform()}fit(){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let e=this.stage.getBoundingClientRect(),t=Math.min(64,e.width*.12);this.scale=W(Math.min((e.width-t*2)/this.image.naturalWidth,(e.height-t*2)/this.image.naturalHeight),.1,2),this.x=(e.width-this.image.naturalWidth*this.scale)/2,this.y=(e.height-this.image.naturalHeight*this.scale)/2,this.updateTransform()}updateTransform(){this.image.style.transform=`translate(${this.x}px, ${this.y}px) scale(${this.scale})`,this.updateMinimap()}startPan(e){this.image.hidden||e.button!==0||e.target.closest(`.preview-toolbar, .minimap`)||(this.drag={pointerId:e.pointerId,x:e.clientX,y:e.clientY,originX:this.x,originY:this.y},this.stage.setPointerCapture(e.pointerId),this.stage.dataset.dragging=`true`)}pan(e){!this.drag||e.pointerId!==this.drag.pointerId||(this.x=this.drag.originX+e.clientX-this.drag.x,this.y=this.drag.originY+e.clientY-this.drag.y,this.updateTransform())}endPan(e){!this.drag||e.pointerId!==this.drag.pointerId||(this.drag=null,delete this.stage.dataset.dragging)}moveFromMinimap(e){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let t=this.minimap.getBoundingClientRect(),n=Math.min(t.width/this.image.naturalWidth,t.height/this.image.naturalHeight),r=(t.width-this.image.naturalWidth*n)/2,i=(t.height-this.image.naturalHeight*n)/2,a=(e.clientX-t.left-r)/n,o=(e.clientY-t.top-i)/n,s=this.stage.getBoundingClientRect();this.x=s.width/2-a*this.scale,this.y=s.height/2-o*this.scale,this.updateTransform()}updateMinimap(){if(!this.image.naturalWidth||!this.image.naturalHeight)return;let e=this.stage.getBoundingClientRect(),t=this.image.naturalWidth*this.scale,n=this.image.naturalHeight*this.scale,r=this.x>=0&&this.y>=0&&this.x+t<=e.width&&this.y+n<=e.height;if(this.minimap.hidden=r,r)return;let i=this.minimap.getBoundingClientRect(),a=Math.min(i.width/this.image.naturalWidth,i.height/this.image.naturalHeight),o=(i.width-this.image.naturalWidth*a)/2,s=(i.height-this.image.naturalHeight*a)/2,c=W(-this.x/this.scale,0,this.image.naturalWidth),l=W(-this.y/this.scale,0,this.image.naturalHeight),u=W((e.width-this.x)/this.scale,0,this.image.naturalWidth),d=W((e.height-this.y)/this.scale,0,this.image.naturalHeight);this.minimapViewport.style.left=`${o+c*a}px`,this.minimapViewport.style.top=`${s+l*a}px`,this.minimapViewport.style.width=`${Math.max(4,(u-c)*a)}px`,this.minimapViewport.style.height=`${Math.max(4,(d-l)*a)}px`}},zn=`diagram.zip:draft:local:v2`,G=`diagram.zip:draft:alias:v1:`,Bn=`diagram.zip:write:v1:`,K=new Et,Vn,Hn,q=!1,Un=``,J={aliasId:null,contentId:null,renderId:null,revision:null,mode:`open`,savedMode:null,writeCapability:null,savedState:null,savedSnapshot:null,bundleKey:null,keyEnvelope:null,encryptedContent:null,encryptedMetadata:null,keyEnvelopeDirty:!1,dirty:!0},Wn=new Map;document.querySelector(`#app`).innerHTML=`1642+>>>>>>>> 169205c2 (migrate four renderers off Fly):server/src/main/resources/web/diagramzip/assets/index-Bl_7m9cX.js16391643 <main class="app-shell">16401644 <header class="app-header">1641- <a class="brand" href="/" aria-label="New diagram"><img class="brand-mark" src="/diagram.zip/icon.svg" alt=""><span>diagram.zip</span></a>1645+ <a class="brand" href="/" aria-label="New diagram"><img class="brand-mark" src="/diagram.zip/icon.svg?v=2" alt=""><span>diagram.zip</span></a>16421646 <div class="header-meta">16431647 <span class="render-status" data-state="idle" role="status">Ready</span>16441648 <label class="type-picker">