Internal Architecture and Algorithms
This page describes the implementation as it exists in the repository. Code
links point to the canonical implementation on the main branch; the diagrams
are generated from the .xal sources linked beneath them.
Rendering pipeline
Diagram source: internal-rendering-pipeline.xal
The pipeline diagram is nested as xaligo -> internal/external -> Main/Other.
Each Main group contains the command -> controller -> usecase -> repository
processing path, while Other contains entity, configuration, logging, tools,
and generated artifacts. Nested package boundaries use generic-group,
rectangles represent functions, and ports represent conceptual data passed
between functions. The notation describes responsibilities and data flow; it is
not a claim that every conceptual port is a distinct Go type or function
parameter.
The native CLI assembles responsibility-specific controllers, use cases, and repositories in
NewRootCmd.
The WASM entry point is a separate composition root, but calls the same render-use-case
boundary (cmd/wasm/main.go).
This keeps input adapters independent from parsing, layout, and rendering.
- Parse.
ParseV1EngineParseDocumentstreams XML tokens with Go’sencoding/xml, builds anentity.Nodetree, records source positions, expands connection shorthand, and validates roots and endpoint references. - Normalize and resolve layout.
validateLayoutDocumentV1EngineLayoutValidationchecks numeric domains before arithmetic.BuildV1EngineLayoutBuildthen converts the syntax tree into an absoluteentity.Boxtree and verifies finite coordinates, positive sizes, content boxes, and parent containment. - Build the canonical scene.
buildScenecontrols context checks, repository-backed catalog/icon ports, and stage order. Synchronous scene calculation runs inv1/engineand emits the Excalidraw-compatible scene shared by downstream formats. - Project, dispatch, and encode.
Renderowns format dispatch. Excalidraw returns the scene directly; XYFlow and Isoflow translate the complete logical scene. SVG, PPTX, PDF, and Excel build an orderedDocumentPlanafter full-scene routing, with one identified frame projection per physical page by default, then call their repositories.
The key invariant is therefore .xal -> validated node tree -> resolved box tree -> canonical scene -> document plan or logical-document encoder.
validate and render execute the same layout checks, and formats do not have
independent parsers or layout engines.
Structural diff algorithm
DiffUsecase.Diff
parses the before and after inputs once each, invokes the synchronous tree
comparison, then sends both annotated documents through the normal V1 layout,
scene, plan, and SVG repository stages. The two render jobs are independent;
future parallel scheduling belongs in this parent use case rather than the V1
engine.
The comparison starts at
DiffDocumentsV1EngineDiffDocument.
It does not compare XML lines or generated element IDs because current layout
IDs contain sibling indexes. Instead it compares the parsed entity.Node
trees using these stages:
diff_fingerprint.gocanonicalizes tags, user attributes, and direct text. Attribute order, formatting whitespace,_xaligo*parser metadata, and implicit versus explicit V1 are ignored.diff_match.gofirst matches uniquename,ref, andididentities. Remaining siblings use exact ordered-subtree fingerprints and a deterministic dynamic-programming sequence alignment, avoiding a cascade when one early sibling is inserted.diff_classify.goclassifies unmatched branches as added/removed and matched value, order, or parent changes as modified. Added and removed subtrees collapse to their highest changed root.
The old side is annotated as removed and the new side as added. Area overlays
are created by
appendDiffBoxHighlightsV1EngineSceneDiffHighlight,
while connector highlighting clones the already resolved path in
connectorDiffHighlightOpV1EnginePlanDiffHighlight.
These translucent overlays are excluded from routing obstacles, so displaying a
diff cannot change the underlying connector route. The SVG repository only
serializes ordinary draw operations and contains no diff-specific branching.
DiffController.Run
reads both sources, waits for both SVG byte sequences, and replaces the paired
-removed.svg and -added.svg outputs through temporary files. A render error
therefore occurs before either final output is written.
V1 compatibility and the V2 boundary
The current
ParseV1EngineParseDocument
accepts the canonical <xaligo version="1"><frames>...</frames></xaligo>
envelope and historical <frame> / <frames> roots. Explicit root
version="1" defines the recommended frozen V1 profile; omission defaults to
V1 with a warning. A direct child frame may independently use version as its
visible page-content revision. Native V2 will
use <scene version="2">; the distinct root is intentional so the V1 parser
rejects V2 before interpreting any nested tag as a permissive V1 custom group.
The planned parent-use-case dispatcher reads the first XML start element once and selects exactly one frontend. V2 owns both its native frontend and a V1 compatibility frontend. Each parses the original source once and lowers directly to the same typed, version-neutral model used by V2 layout, routing, and encoders. V1 does not import or call V2.
This avoids four expensive or ambiguous designs: renaming roots and reparsing, trying V1 and then V2 after a parser error, serializing through the current scene and reading it back, or running the complete V1 renderer before V2. V1 golden tests freeze defaults and error/fallback behavior so the compatibility frontend can be checked at normalized-model and resolved-geometry boundaries, not only by comparing final pixels.
Render Mode is orthogonal to the language version. V1 currently accepts
standard, network, and aws, but
ValidateRenderOptionsV1EngineOptionRender
does not select different layout or rendering semantics for them. They all use
the same resolved 2D pipeline until a versioned implementation explicitly adds
those semantics.
Package and dependency boundaries
| Package | Role | Representative code |
|---|---|---|
cmd, internal/controller | Process entry points, CLI flags, and file I/O | internal/command.go |
internal/usecase | Context checks, repository adaptation, stage ordering, render/diff orchestration, format dispatch, and future parallel scheduling | render.go, diff.go, diagnostics.go |
internal/usecase/v1/engine | Synchronous V1 parser, validation, layout, scene, routing, pagination, theme, and draw-plan calculations | v1/engine |
internal/entity | Data exchanged across layers, including resolved content boxes, PresentationScene, DocumentPlan, Plan, and TextLayout; no orchestration | internal/entity |
internal/repository | Catalog/filesystem access and output encoders | internal/repository |
external | TypeScript API and the PptxGenJS/WASM adapter | external |
Use-case declarations are not collected in a package-wide facade. Each
responsibility file begins with its interface, private concrete type, and
constructor: RenderUsecase,
DiagnosticsUsecase,
SceneIOUsecase,
CatalogUsecase, and
ExportUsecase, and
DiffUsecase. Parser,
layout, element construction, pagination, plan construction, scene
construction, and theming follow the same component form in
internal/usecase.
NewRootCmd constructs these independently and injects only the interfaces
needed by each controller; one use case does not call another use case.
Native files and embedded/WASM assets differ through dependencies and
RenderOptions.Assets, not through duplicated rendering logic.
The V1 engine has no repository import, context interpretation, goroutine,
channel, worker pool, or concurrency limit. Repository methods are adapted to
synchronous function ports in
scene.go,
and the parent use case checks cancellation between calculation stages. A
future scheduler can therefore partition independent documents, frames, or
pages and run those jobs concurrently outside the engine. Connector routing
inside one plan remains ordered because each accepted route affects the score
of later routes.
V1 engine package-scope identifiers encode both version and ownership file as
<base>V1Engine<FileBaseCamelCase>. For example,
ParseV1EngineParseDocument
and
BuildPlanV1EnginePlanBuild
make cross-file ownership explicit. Parent use-case methods keep concise domain
names and form the compatibility boundary. The mandatory rule is defined in
.github/instructions/coding.instructions.md and enforced by a source-structure
regression test.
RenderController.RunFormat reads each input once. Container formats call the
generic RenderUsecase.Render path; SVG calls RenderArtifacts so a
multi-frame document can persist several files
(internal/controller/render.go).
RenderPPTX and BuildPPTXPlan remain compatibility APIs, but the CLI no
longer assembles a second PPTX-only pipeline. The legacy Excalidraw outside-frame
legend still requires a post-render scene read/write; moving that legend into
the canonical result is an explicit remaining migration.
Layout algorithms
Layout is a recursive top-down constraint resolution. Each node receives an allocation from its parent, resolves a border box and content box, removes margin and padding, then divides the remaining area among its children.
- Vertical stacks first reserve explicit child heights, gaps, and margins. Only
children without a fixed height divide the remainder by their positive
rowweights (layoutStackV1EngineLayoutFlow). - Horizontal flex containers reserve explicit widths and then divide the
remainder by positive
colweights (layoutFlexHV1EngineLayoutFlow). - A
<row>uses a 12-column grid: child width is the available width multiplied byspan / 12; zero, non-finite, out-of-range, and overcommitted spans are rejected before division (layoutRowV1EngineLayoutFlow). - Item-only groups use a shared grid solver for rows, columns, icon size,
alignment, and offsets
(
resolveItemGridV1EngineLayoutItemGrid).Buildpreflights the minimum label cell and source offsets; scene construction reruns the same solver with catalog-derived label height.
The resolved Box
stores both its border box and content box, source position, and overflow policy.
It also records a known intrinsic size and whether an explicit visible-overflow
policy was actually exercised; zero intrinsic size means measurement is still
deferred (notably for catalog-backed item grids).
validateResolvedGeometryV1EngineLayoutValidation
rejects non-finite geometry, non-positive drawable sizes, content boxes outside
their borders, and children outside the parent content box. The default policy
is overflow="error"; overflow="visible" is the explicit opt-out. Scene
construction and encoders no longer discover resolved-box or source-offset
failures for the first time.
Catalog-derived item-label measurement is still completed during scene
construction. This remains an ownership gap for a container that mixes
<item> slots with rectangles: the two algorithms cannot yet reserve space
against one another. The design precondition is to store the selected item-grid
cells in resolved layout before mixed-content item groups are declared fully
supported.
Orthogonal connector routing
Diagram source: internal-routing-algorithm.xal
The router is deterministic and operates in layout-pixel space. Its entry point
is routeConnectionsV1EngineRouteBuild.
Requests are processed in stable order, and every accepted path is included
when scoring later paths.
For each connection, routeOneV1EngineRoutePath
filters out the source and destination from the obstacle set, creates fixed
endpoint stubs, and generates orthogonal candidates through direct lanes and
available gutters. User-supplied bends constrain this candidate construction.
Candidates are simplified and snapped to the configured grid.
scorePathV1EngineRouteCandidate
selects the lowest-cost candidate. The cost combines, in descending practical
importance:
1000 * obstacle hits
40 * normalized overlap with placed paths
24 * normalized near-parallel proximity
20 * crossings
8 * bends
5 * (length / 1000)
The large obstacle penalty makes avoidance dominant; the remaining terms prefer
short, simple routes while spreading connectors across lanes. After selection,
overlap separation may move an internal trunk without changing endpoint stubs.
A traffic connection matching a route follows an offset copy of that route,
which visually separates structural connectivity from directional flow.
Scene, draw plan, and encoders
The canonical scene currently retains Excalidraw-compatible JSON for editable
output and backward compatibility. Its Go-facing name is
PresentationScene;
PptxScene remains only as a deprecated source alias. The same scene is the
interchange input used by the XYFlow and Isoflow adapters
(XYFlow.Render,
Isoflow.Render).
Removing the underlying Excalidraw field vocabulary is a separate compatibility
migration; the neutral name does not pretend that migration is already complete.
For a connection whose endpoints belong to different frames, V1 deliberately
keeps two page-local editable stubs in PresentationScene, never one line
across the inter-frame canvas. The source stub runs from its endpoint to the
source frame’s page-terminal inset line and is labeled
to <destination frame ID>; the destination stub runs from the destination
frame’s page-terminal inset line to its endpoint and is labeled
from <source frame ID>. The angle brackets are
literal punctuation, so an overview to detail link displays to <detail>
and from <overview>.
The outer logical page edge is not a rendered frame outline in SVG, PPTX, PDF, or Excel. It remains available for sizing, projection, side selection, and the tangent-anchor basis; the drawable page terminal may be on a parallel inward inset line.
Shared scene construction resolves endpoint binding and frame-terminal geometry
independently. src/dst-anchor and src/dst-side own the endpoint;
cross-frame-only src/dst-frame-anchor and src/dst-frame-side own the
logical page terminal. Frame-terminal precedence is frame anchor, frame side,
legacy endpoint anchor, endpoint side, then automatic selection. The first two
are fixed; the rest supply a preferred side when no frame terminal is explicit.
Every frame edge has fixed tangent anchors at 10/30/50/70/90 percent of the
outer extent. Shared scene construction keeps a safe preference or minimizes
distance from the actual endpoint visual envelope over only safe candidates;
equal minima prefer the remote-facing candidate and then top, right,
bottom, left. Layout validation checks that a safe candidate exists but
does not infer the automatic side from Box positions. The endpoint and
frame-terminal segments are perpendicular to their respective selected sides
even when those sides differ.
Without an explicit frame anchor, the unconstrained terminal parallel
coordinate comes from the endpoint binding. A coordinate inside the
24-layout-px corner gutter is clamped and bridged by a two-bend orthogonal
dogleg. Borders shorter than 96 layout pixels use an adaptive quarter-length
gutter. The final normal coordinate is shifted inward from the outer logical
edge by the resolved metadata row-gap, or by 4 layout pixels when metadata is
absent. The same value applies to all four sides, and zero keeps the outer edge.
An explicit side must fit that side’s normal frame dimension and avoid the
metadata reservation. Without an explicit frame terminal, the same constraints
filter candidate sides; an unsafe preference is remapped and only an empty set
is a connection-positioned validation error. The resolved inset is not
clamped.
An unconstrained coincident inset terminal shifts by up to 24 layout pixels
along the parallel axis within the available range to retain a visible stub.
An explicit frame anchor retains its exact tangent coordinate and uses an
orthogonal local stub for visible separation.
Frame metadata adds a final safety pass after normal side precedence. Without explicit frame-terminal geometry, unsafe sides are removed before the visual nearest-side choice and left/right terminals are clamped beyond the full-width reservation strip. An explicit frame side/anchor that conflicts with the reservation is a validation error. A safe explicit left/right side remains valid even if an unused top/bottom inset line would be unsafe. The path and label remain outside the strip. Page-link labels keep a 4-layout-pixel inward gap and at least a 4-layout-pixel tangent gap from the final inset terminal. The closest tangent position that avoids endpoint geometry is selected.
The stubs share a stable logical connector ID plus the original source/destination element and frame IDs. XYFlow and Isoflow use those fields to reconstruct one logical edge; Excalidraw, SVG, PPTX, PDF, and Excel keep the page-local representation. Routing metadata such as bends, scale, grid, endpoint anchors, and frame-terminal anchors is stored on both stubs so a capable adapter does not have to infer it from generated geometry. Manual bends do not steer the two page-local paths.
Rendered V1 nodes also carry xaligoSemanticElementKind and
xaligoSemanticParentElementId, projected from the resolved Box tree. Pure
layout containers and invisible boxes inherit the nearest rendered semantic
ancestor rather than becoming graph nodes. XYFlow consumes these IDs directly;
rectangle containment remains only a compatibility fallback for older scenes.
Output schemas remain capability projections, not alternate semantic models. XYFlow can retain arbitrary edge data, whereas the upstream-compatible Isoflow connector shape cannot retain V1 kind, arrowhead, fixed-point, or original scale/grid metadata. A future V2 compatibility frontend must therefore lower V1 input directly to the shared typed model; it must not recover V1 semantics by round-tripping through any output adapter.
BuildPlanV1EnginePlanBuild
turns visible scene elements into ordered DrawOp values. It resolves paper
orientation by comparing portrait and landscape fit, scales the content into
the available margins, prepares connector bindings, masks border intersections,
and adds legends. The same plan feeds the SVG encoder
(SVG.Render)
and the PPTX exporter, so their geometry and connector semantics remain aligned.
The plan is renderer-shared rather than fully format-neutral today: it uses
physical inch/point units and presentation-style surface metadata. A neutral
schema that can serve every future encoder without those assumptions remains a
roadmap migration.
BuildDocumentPlanV1EnginePlanDocument
projects that already-resolved scene into ordered DocumentPage values. Each
identified child frame becomes one page unless CombineFrames requests the
compatibility canvas. Projection is intentionally downstream of scene
construction so cross-frame endpoint lookup and its two page-link stubs are
resolved once, not independently on cropped pages.
Default frame pages carry a strict-crop policy. SVG uses the logical frame rectangle as the exact canvas and clip boundary, and PDF/Excel inherit that page SVG. Combined compatibility output keeps marker-safe bounds expansion.
The physical encoders map the same page order as follows:
- SVG returns
RenderArtifactvalues. One frame uses the exact requested path; multiple frames append a sanitized frame ID to the output stem and reject collisions. - PPTX writes one diagram slide per page. Its document plan normalizes all slides to the largest page dimensions and centers smaller pages because a PowerPoint deck has one common slide size.
- PDF parses each page SVG as vector drawing content and writes one PDF page with the corresponding physical dimensions.
- Excel places each page SVG image on one worksheet; it does not translate diagram shapes into spreadsheet cells.
The preview adapter explicitly requests CombineFrames so the browser still
shows all frames on one SVG canvas. Excalidraw, XYFlow, and Isoflow never enter
the physical-page split and remain one logical document.
Frame metadata follows the same downstream ownership model. The parser
normalizes a direct frame <metadata> block and distinguishes a child-frame
content revision from the root DSL version. Shared layout resolves the
top/bottom band against the logical frame border box, font-sized tag height,
auto/fixed widths, greedy wrapping, explicit row breaks, and per-row alignment
on Box.FrameMetadata. The resolved row-gap is both the inter-row spacing
and the inset at the selected vertical edge and both horizontal page edges;
row wrapping and alignment use frame width - 2 * row-gap. The full-width
reservation strip still starts at the outer logical frame edge, reaches the
final content-box boundary, and is at least
row-gap + complete band height + 8 pixels deep. Scene construction emits
stable page-owned key/value shapes and text once and excludes normal items/text,
local and UML connector paths and labels, and page links from that strip. The
result is projected with its owning DocumentPage; the SVG, PPTX, PDF, Excel,
and Excalidraw repositories do not recompute the band or reservation. XYFlow
and Isoflow discard the decoration
through their normal semantic projection instead of exposing synthetic nodes.
Every text operation now carries a renderer-neutral
TextLayout
with semantic role, wrap, fit, typed visible|clip overflow, line height, and
padding. The legacy clip boolean remains in plan JSON as a compatibility
field. Group headers,
item labels, port labels, and connector labels are identified by scene metadata;
generated IDs are used only as an old-plan compatibility fallback. SVG performs
deterministic wrapping and shrink-to-fit and emits a clip path
(writeText);
the external PPTX repository consumes the same fields
(drawText).
After PptxGenJS serializes the package,
finalizePptxPackage
sets DrawingML horizontal and vertical overflow to clip or overflow on the
matching text object. This preserves fit="none" instead of approximating a
clip request by shrinking the text.
Excalidraw-compatible text elements retain the same policy as
customData.xaligoTextLayout; the editor’s bound-text engine remains an
approximation, not a second source of geometry.
Scene coordinates and font sizes start as layout pixels. BuildPlan computes
one effective pixel-to-output transform, including paper fitting. Geometry is
stored in plan inches and font size in points, while TextLayout.Padding uses
plan inches. Stroke widths use points as well, and SVG converts both point
values back to output pixels with the same effective PPI. This makes
--px-per-inch 144 and paper fitting scale text, strokes, and shapes together
instead of applying the former fixed px * 0.75 font conversion.
PPTX is deliberately split at a serialization boundary: Go produces the plan,
then PowerpointRepository
invokes the configured WASI/PptxGenJS exporter. Inside that exporter, the WASI
command delegates to
runPptxExporter,
which calls the external use case before the PptxGenJS repository. The internal
repository never calls the external repository directly. There is no second
OOXML layout implementation in Go.
Updating these diagrams
Edit the .xal source, then regenerate the checked-in SVG used by mdBook:
go run ./cmd render docs/src/architecture/internal-rendering-pipeline.xal \
--format svg -o docs/src/images/internal-rendering-pipeline.svg
go run ./cmd render docs/src/architecture/internal-routing-algorithm.xal \
--format svg -o docs/src/images/internal-routing-algorithm.svg
Keeping .xal as the source makes the documentation exercise the same parser,
layout engine, router, and SVG encoder described above.