diff --git a/.cspell.yaml b/.cspell.yaml
index 862d58d6..355837a0 100644
--- a/.cspell.yaml
+++ b/.cspell.yaml
@@ -93,6 +93,15 @@ words:
- renderable
- Segoe
- colour
+ - dasharray
+ - Metaobjects
+ - noviews
+ - Premul
+ - rasterizes
+ - Rgba
+ - Rects
+ - RRGGBB
+ - RRGGBBAA
# Exclude common build artifacts, dependencies, and vendored third-party code
ignorePaths:
diff --git a/.reviewmark.yaml b/.reviewmark.yaml
index 9864ee7b..7af55293 100644
--- a/.reviewmark.yaml
+++ b/.reviewmark.yaml
@@ -477,6 +477,18 @@ reviews:
- "docs/verification/sysml2-tools-tool/lint.md"
- "src/**/Lint/LintCommand.cs"
+ - id: SysML2Tools-Tool-Render
+ title: Review of SysML2 Tools Render subsystem implementation
+ context:
+ - docs/design/sysml2-tools-tool.md
+ - docs/reqstream/sysml2-tools-tool.yaml
+ paths:
+ - "docs/reqstream/sysml2-tools-tool/render.yaml"
+ - "docs/design/sysml2-tools-tool/render.md"
+ - "docs/verification/sysml2-tools-tool/render.md"
+ - "src/DemaConsulting.SysML2Tools.Tool/Render/RenderCommand.cs"
+ - "test/DemaConsulting.SysML2Tools.Tool.Tests/Render/RenderSubsystemTests.cs"
+
# OTS Items
- id: OTS-BuildMark
title: Review of BuildMark OTS verification evidence
diff --git a/docs/design/introduction.md b/docs/design/introduction.md
index 1fa7fa27..77187c87 100644
--- a/docs/design/introduction.md
+++ b/docs/design/introduction.md
@@ -46,13 +46,15 @@ system, subsystem, and unit levels:
- **ReferenceResolver** (Unit) — resolves supertype references; detects circular imports
- **SupertypeWalker** (Unit) — walks specialization chains; detects cyclic specialization
- **Layout** (Subsystem) — LayoutTree intermediate representation: nine node types covering all SysML diagram elements
+ - **Internal** (Subsystem) — internal layout implementation
+ - **GeneralViewLayoutStrategy** (Unit) — two-column grid layout for general view diagrams
- **Rendering** (Subsystem) — rendering pipeline interfaces: IRenderer, ILayoutStrategy, Theme, RenderOptions, DiagramRenderer
- **DemaConsulting.SysML2Tools.Svg** (System) — SVG renderer: renders `LayoutTree` to
SVG output with zero external dependencies
- - TODO: subsystems and units to be defined in Phase 4+
+ - **SvgRenderer** (Unit) — translates a `LayoutTree` to a self-contained SVG 1.1 document
- **DemaConsulting.SysML2Tools.Png** (System) — PNG renderer: renders `LayoutTree` to
PNG output using SkiaSharp
- - TODO: subsystems and units to be defined in Phase 4+
+ - **PngRenderer** (Unit) — rasterizes a `LayoutTree` to a PNG image using SkiaSharp
- **DemaConsulting.SysML2Tools.Tool** (System) — dotnet tool: thin CLI wrapper and
orchestration
- **Program** (Unit) — entry point and execution orchestrator
@@ -60,6 +62,8 @@ system, subsystem, and unit levels:
- **Context** (Unit) — argument parser and I/O owner
- **Lint** (Subsystem) — lint command implementation
- **LintCommand** (Unit) — resolves glob patterns, invokes WorkspaceLoader, reports diagnostics
+ - **Render** (Subsystem) — render command implementation
+ - **RenderCommand** (Unit) — loads workspace, selects renderer, writes diagram output files
- **SelfTest** (Subsystem) — self-validation test runner
- **Validation** (Unit) — self-validation test runner
- **Utilities** (Subsystem) — shared utilities
@@ -89,14 +93,15 @@ reviewers an explicit navigation aid from design to code:
- **src/** — source projects
- **DemaConsulting.SysML2Tools/** — core library
- **Grammar/** — ANTLR4 grammar files (hand-maintained; see Grammar/README.md)
- - **Layout/** — LayoutTree intermediate representation (Phase 3+)
+ - **Layout/** — LayoutTree intermediate representation
+ - **Internal/** — internal layout implementation (GeneralViewLayoutStrategy)
- **Parser/** — SysML v2 parsing subsystem
- **Antlr/** — ANTLR4-generated C# (committed; not hand-written)
- **Internal/** — internal implementation (SysmlDiagnosticListener, StdlibLoader)
- **Rendering/** — rendering interfaces and theme (Phase 3+)
- **Stdlib/** — embedded SysML v2 standard library files (EPL-2.0; see Stdlib/README.md)
- - **DemaConsulting.SysML2Tools.Svg/** — SVG renderer (Phase 0: stub)
- - **DemaConsulting.SysML2Tools.Png/** — PNG renderer (Phase 0: stub)
+ - **DemaConsulting.SysML2Tools.Svg/** — SVG renderer
+ - **DemaConsulting.SysML2Tools.Png/** — PNG renderer
- **DemaConsulting.SysML2Tools.Tool/** — dotnet tool CLI wrapper
- **Cli/** — command-line interface subsystem
- **Lint/** — lint command subsystem
@@ -105,11 +110,12 @@ reviewers an explicit navigation aid from design to code:
- **docs/design/** — design documentation
- **sysml2-tools-core/** — core library unit/subsystem design
- **parser/** — Parser subsystem design (Internal subsystem)
- - **sysml2-tools-svg/** — TODO: SVG renderer unit/subsystem design (Phase 4+)
- - **sysml2-tools-png/** — TODO: PNG renderer unit/subsystem design (Phase 4+)
+ - **sysml2-tools-svg.md** — SVG renderer design
+ - **sysml2-tools-png.md** — PNG renderer design
- **sysml2-tools-tool/** — DemaConsulting.SysML2Tools.Tool unit/subsystem design
- **cli/** — Cli subsystem design
- **lint/** — Lint subsystem design
+ - **render/** — Render subsystem design (render.md)
- **self-test/** — SelfTest subsystem design
- **utilities/** — Utilities subsystem design
diff --git a/docs/design/sysml2-tools-core/layout.md b/docs/design/sysml2-tools-core/layout.md
index b8a13d73..6d587fe0 100644
--- a/docs/design/sysml2-tools-core/layout.md
+++ b/docs/design/sysml2-tools-core/layout.md
@@ -175,11 +175,54 @@ decisions recorded here reflect constraints imposed during the Phase 3 vocabular
### Design Constraints
- All Layout types target net8.0, net9.0, and net10.0 with `enable`.
-- No methods or behaviors are defined in the Layout subsystem; it is a data model only.
+- No methods or behaviors are defined in the Layout subsystem data types; it is a data model only.
- `TextAlign` is declared in `LayoutLabel.cs` and reused by `LayoutGrid.cs`; both files
are in the same `DemaConsulting.SysML2Tools.Layout` namespace, so no cross-namespace
import is required.
+### Internal Subsystem
+
+#### GeneralViewLayoutStrategy
+
+##### Purpose
+
+`GeneralViewLayoutStrategy` implements `ILayoutStrategy` to produce a two-column grid
+layout for general view diagrams. It collects all user-defined `part def` elements from
+the workspace, groups them by parent package, and arranges the group boxes into two
+left-to-right columns. Specialization relationships between `part def` elements with
+declared supertypes are represented as `LayoutLine` nodes with open arrowheads.
+
+##### Data Model
+
+`GeneralViewLayoutStrategy` has no instance state. All inputs are supplied through
+`BuildLayout` parameters. Layout constants (margins, gaps, minimum box sizes) are
+declared as `private const double` fields.
+
+##### Key Methods
+
+**`BuildLayout(ViewContext context, RenderOptions options)`**
+
+Entry point. Calls `CollectUserPartDefs` to gather non-stdlib `part def` declarations.
+Returns a minimal 200×100 `LayoutTree` when no user part defs are found. Otherwise
+calls `GroupByPackage` and `BuildGridLayout` to produce the full layout tree.
+
+**`CollectUserPartDefs(SysmlWorkspace workspace)`**
+
+Iterates `workspace.Declarations`, keeping only `SysmlDefinitionNode` entries with
+`DefinitionKeyword == "part def"` that pass `StdlibFilter.IsStdlibElement`.
+
+**`BuildGridLayout(groups, theme)`**
+
+Places group `LayoutBox` nodes alternately in left and right columns. Computes column
+widths from the maximum group width in each column, then assigns absolute `(X, Y)`
+coordinates. Calls `AddSpecializationLines` to append `LayoutLine` nodes.
+
+##### Dependencies
+
+- `ILayoutStrategy` (in `DemaConsulting.SysML2Tools.Rendering`) — interface
+- `StdlibFilter` (in `DemaConsulting.SysML2Tools.Rendering.Internal`) — stdlib exclusion
+- `SysmlDefinitionNode` (in `DemaConsulting.SysML2Tools.Semantic.Internal`) — part def nodes
+
### Requirements Traceability
| Requirement ID | Satisfied by |
diff --git a/docs/design/sysml2-tools-core/rendering.md b/docs/design/sysml2-tools-core/rendering.md
index 2551cd86..18f2d1f7 100644
--- a/docs/design/sysml2-tools-core/rendering.md
+++ b/docs/design/sysml2-tools-core/rendering.md
@@ -97,9 +97,26 @@ flowchart TD
- *Type*: Sealed class.
- *Role*: Orchestrator.
- *Contract*: `IReadOnlyList RenderWorkspace(SysmlWorkspace workspace, IRenderer renderer, RenderOptions options)`.
- Iterates over all views in the workspace, calls `ILayoutStrategy.BuildLayout` for each,
- then calls `IRenderer.Render` and collects the results. Implementation is deferred to
- Phase 4.
+ Iterates over all views in the workspace, routes each view to an `ILayoutStrategy` via
+ `DiagramTypeRouter`, calls `ILayoutStrategy.BuildLayout`, then calls `IRenderer.Render`
+ and collects the results. Standard-library view declarations are filtered by `StdlibFilter`.
+
+**DiagramTypeRouter**: Internal routing helper.
+
+- *Type*: Internal static class.
+- *Role*: Router.
+- *Contract*: `static ILayoutStrategy GetStrategy(object viewNode, SysmlWorkspace workspace, out string? unsupportedMessage)`.
+ Routes all view types to `GeneralViewLayoutStrategy` in Phase 4. Sets `unsupportedMessage`
+ to a non-null diagnostic when no strategy is available for the view type; the caller skips
+ rendering that view.
+
+**StdlibFilter**: Standard-library element filter.
+
+- *Type*: Internal static class.
+- *Role*: Filter.
+- *Contract*: `static bool IsStdlibElement(string qualifiedName)`. Returns `true` when the
+ qualified name matches a standard-library prefix. Used by `DiagramRenderer` to exclude
+ stdlib view declarations from rendering.
### Design
@@ -126,9 +143,6 @@ flowchart TD
### Design Constraints
-- `DiagramRenderer`, `IRenderer`, `ILayoutStrategy`, and `ViewContext` are Phase 3
- interface definitions. All `Render` and `RenderWorkspace` method bodies throw
- `NotImplementedException` until Phase 4 implementation.
- `ViewContext.Workspace` references `SysmlWorkspace` from
`DemaConsulting.SysML2Tools.Semantic`; the `using` directive `using DemaConsulting.SysML2Tools.Semantic;`
is required in `ILayoutStrategy.cs` and `DiagramRenderer.cs`.
@@ -141,11 +155,11 @@ flowchart TD
| Requirement ID | Satisfied by |
| --- | --- |
| SysML2Tools-Core-Rendering-IRenderer | `IRenderer` interface |
-| SysML2Tools-Core-Rendering-IRendererStateless | `IRenderer` doc constraint; stub throws `NotImplementedException` |
+| SysML2Tools-Core-Rendering-IRendererStateless | `SvgRenderer` and `PngRenderer` are pure stateless implementations |
| SysML2Tools-Core-Rendering-Theme | `Theme` and `FontDescriptor` records |
| SysML2Tools-Core-Rendering-ThemeDepthWrap | `Theme.DepthFillColors` with modulo indexing documented in `Theme` |
| SysML2Tools-Core-Rendering-RenderOptions | `RenderOptions` record with default values |
| SysML2Tools-Core-Rendering-ILayoutStrategy | `ILayoutStrategy` interface and `ViewContext` record |
-| SysML2Tools-Core-Rendering-DiagramRenderer | `DiagramRenderer` class stub |
+| SysML2Tools-Core-Rendering-DiagramRenderer | `DiagramRenderer.RenderWorkspace`; `DiagramTypeRouter`; `StdlibFilter` |
| SysML2Tools-Core-Rendering-RenderOutput | `RenderOutput` record |
| SysML2Tools-Core-Rendering-BuiltinThemes | `Themes.Light`, `Themes.Dark`, `Themes.Print` |
diff --git a/docs/design/sysml2-tools-png.md b/docs/design/sysml2-tools-png.md
index da725432..00ef952c 100644
--- a/docs/design/sysml2-tools-png.md
+++ b/docs/design/sysml2-tools-png.md
@@ -1,30 +1,217 @@
# DemaConsulting.SysML2Tools.Png
-## Architecture
+## Overview
-The `DemaConsulting.SysML2Tools.Png` package provides the PNG renderer, implementing the
-`IRenderer` interface from the core library using SkiaSharp (MIT-licensed). This system is
-a Phase 0 stub; detailed design will be populated in Phase 4+.
+`DemaConsulting.SysML2Tools.Png` is the PNG renderer system for SysML2 Tools. It provides
+a single public class, `PngRenderer`, that uses SkiaSharp to rasterize a `LayoutTree`
+into a PNG image written to an output stream.
-## External Interfaces
+## PngRenderer
-*To be defined in Phase 4+.*
+### Purpose
-## Dependencies
+`PngRenderer` implements the `IRenderer` interface to produce PNG diagram output from a
+`LayoutTree` intermediate representation. Each call to `Render` allocates an `SKBitmap`,
+draws all layout nodes onto an `SKCanvas`, encodes the result as PNG via `SKImage.Encode`,
+and writes the bytes to the output stream. All SkiaSharp resources are disposed before
+the method returns. The renderer is pure and stateless.
-- **DemaConsulting.SysML2Tools** — provides `IRenderer` interface and semantic model.
-- **SkiaSharp** — MIT-licensed 2D graphics library used for PNG rasterization.
+### Data Model
-## Risk Control Measures
+`PngRenderer` has no instance state. All inputs are supplied through `Render` parameters.
-N/A — not a safety-classified software item.
+- `LayoutTree` — read-only input; canvas dimensions and node list
+- `RenderOptions` — read-only input; `Theme` for colors/fonts, `Scale` for bitmap size
+- `Stream output` — write-only output; receives PNG bytes; caller owns lifetime
-## Data Flow
+### Embedded Font
-*To be defined in Phase 4+.*
+`PngRenderer` embeds the four static Noto Sans TTF files as assembly resources to guarantee
+pixel-identical output across all platforms regardless of which fonts are installed on the
+host system:
-## Design Constraints
+| Resource | Typeface |
+| --- | --- |
+| `Fonts/NotoSans-Regular.ttf` | Regular weight, normal style |
+| `Fonts/NotoSans-Bold.ttf` | Bold weight, normal style |
+| `Fonts/NotoSans-Italic.ttf` | Regular weight, italic style |
+| `Fonts/NotoSans-BoldItalic.ttf` | Bold weight, italic style |
-- Platform: multi-targets net8.0, net9.0, and net10.0 framework compatibility specifications
- on Windows, Linux, and macOS.
-- SkiaSharp dependency is introduced in Phase 1.
+The four static `Lazy` fields (`RegularTypeface`, `BoldTypeface`,
+`ItalicTypeface`, `BoldItalicTypeface`) load each font once on first use via the
+`LoadTypeface(string fileName)` helper. `LoadTypeface` locates the resource by matching
+the file name suffix (case-insensitive) against the assembly manifest resource names and
+falls back to `SKTypeface.Default` if the resource is not found, so the renderer
+remains functional even when the font files are absent.
+
+The SIL Open Font License 1.1 attribution file for Noto Sans is included at
+`Fonts/OFL.txt` and is also embedded as an assembly resource.
+
+### Font Weight and Style Per Node Type
+
+Each node type uses a fixed weight and style:
+
+| Node Type | Weight | Style |
+| --- | --- | --- |
+| `LayoutBox` label | Bold | Normal |
+| `LayoutBoxCompartment` title | Bold | Italic |
+| `LayoutBoxCompartment` rows | Regular | Normal |
+| `LayoutLine` midpoint label | Regular | Normal |
+| `LayoutLabel` | Per `FontWeight` field | Per `FontStyle` field |
+| `LayoutPort` label | Regular | Normal |
+| `LayoutBadge` label | Regular | Normal |
+| `LayoutBand` label | Regular | Normal |
+| `LayoutLifeline` label | Bold | Normal |
+| `LayoutGrid` header cells | Bold | Normal |
+| `LayoutGrid` body cells | Regular | Normal |
+
+### LayoutLabel Font Styling Fields
+
+`LayoutLabel` carries three explicit font styling fields added in Phase 4:
+
+- `Weight` (`FontWeight`) — `Regular` or `Bold`; selects the typeface variant.
+- `Style` (`FontStyle`) — `Normal` or `Italic`; selects the typeface variant.
+- `FontSize` (double) — Font size in logical pixels, independent of the theme body size.
+
+### Key Methods
+
+**`Render(LayoutTree layout, RenderOptions options, Stream output)`**
+
+Entry point. Validates arguments. Computes bitmap width and height as
+`(int)Math.Ceiling(layout.Width * options.Scale)`, clamped to a minimum of 1×1 pixels
+to prevent SkiaSharp allocation errors on empty trees. Creates `SKBitmap(w, h, Rgba8888, Premul)`,
+fills background with `SKColors.White`, calls `RenderNode` for each top-level node,
+then encodes via `SKImage.FromBitmap(bitmap).Encode(Png, 100)` and saves to `output`.
+
+**`RenderNode(SKCanvas canvas, LayoutNode node, RenderOptions options)`**
+
+Dispatches by concrete node type to the appropriate typed render method. All nine
+`LayoutNode` subtypes are handled: `LayoutBox` → `RenderBox`, `LayoutLine` → `RenderLine`,
+`LayoutLabel` → `RenderLabel`, `LayoutPort` → `RenderPort`, `LayoutBadge` → `RenderBadge`,
+`LayoutBand` → `RenderBand`, `LayoutLifeline` → `RenderLifeline`,
+`LayoutActivation` → `RenderActivation`, `LayoutGrid` → `RenderGrid`. Unknown subtypes
+are silently skipped for forward compatibility.
+
+**`LoadTypeface(string fileName)`**
+
+Locates the embedded assembly resource whose name ends with `fileName`
+(case-insensitive). Returns an `SKTypeface` decoded from the resource stream, or
+`SKTypeface.Default` if no matching resource is found.
+
+**`CreateTextPaint(SKColor color, float fontSize, bool bold, bool italic)`**
+
+Selects the appropriate `Lazy` variant based on the `(bold, italic)` tuple,
+then returns a new `SKPaint` configured with the color, font size, anti-aliasing, and
+typeface. The caller is responsible for disposing the returned paint.
+
+**`FitFontSize(SKPaint paint, string text, float availableWidth, float maxFontSize)`**
+
+Measures the text width at `maxFontSize`. Returns `maxFontSize` unchanged when the text
+fits within `availableWidth` or when `availableWidth` is zero or negative. Otherwise
+scales the font size proportionally: `maxFontSize * (availableWidth / measuredWidth)`.
+This ensures long labels do not overflow their bounding box.
+
+**`RenderBox(SKCanvas canvas, LayoutBox box, RenderOptions options)`**
+
+Draws a filled rectangle (plain or rounded via `DrawRoundRect` when
+`BoxShape.RoundedRectangle` and `LineCornerRadius > 0`) using the fill color from
+`theme.DepthFillColors[depth % count]`. Draws a matching stroke rectangle. Draws centered
+bold title text when `box.Label` is non-null, calling `FitFontSize` to prevent overflow.
+Calls `RenderBoxCompartments` for any compartments, then recursively calls `RenderNode`
+for all `box.Children`.
+
+**`RenderBoxCompartments(SKCanvas canvas, LayoutBox box, RenderOptions options, SKColor strokeColor)`**
+
+Draws a full-width horizontal divider line at the start of each compartment, followed by
+an optional bold-italic title row and zero or more left-aligned regular-weight body-font
+text rows. Tracks a running Y offset starting below the label area.
+
+**`RenderLine(SKCanvas canvas, LayoutLine line, RenderOptions options)`**
+
+Builds a single `SKPath` from all waypoints. Applies `SKPathEffect.CreateCorner` when
+`LineCornerRadius > 0`. When dashing is also active, composes effects with
+`SKPathEffect.CreateCompose(dash, corner)` so the dash pattern follows the rounded path.
+After drawing the path, calls `DrawArrowhead` for non-None source and target arrowhead
+styles, then `RenderLineMidpointLabel` when `MidpointLabel` is non-null.
+
+**`DrawArrowhead(SKCanvas, tipX, tipY, dx, dy, ArrowheadStyle, scale, color, strokeWidth)`**
+
+Draws the arrowhead at the given tip using a normalized direction vector. Supports all
+seven `ArrowheadStyle` values: `None` (no-op), `Open` (hollow triangle), `Filled` (solid
+triangle), `Diamond` (hollow four-point polygon), `FilledDiamond` (solid four-point
+polygon), `Circle` (open circle), `Bar` (perpendicular stroke).
+
+**`RenderLabel(SKCanvas canvas, LayoutLabel label, RenderOptions options)`**
+
+Draws text using `CreateTextPaint` with the weight and style from `label.Weight` and
+`label.Style`, font size from `label.FontSize`, and alignment from `label.Align`. Calls
+`FitFontSize` when `label.MaxWidth > 0`.
+
+**`RenderPort(SKCanvas canvas, LayoutPort port, RenderOptions options)`**
+
+Draws an 8×8-pixel filled square (filled with the stroke color) centered at
+`(CentreX, CentreY)`. Optional label is offset away from the attached `PortSide`.
+
+**`RenderBadge(SKCanvas canvas, LayoutBadge badge, RenderOptions options)`**
+
+Draws the badge shape centered at `(CentreX, CentreY)` within a bounding circle of
+radius `Size/2`. Shapes: `FilledCircle` (solid circle), `Bullseye` (filled circle with
+white inner circle), `Diamond` (rotated open square), `HorizontalBar` (horizontal stroke),
+`VerticalBar` (vertical stroke). Optional label drawn to the right.
+
+**`RenderBand(SKCanvas canvas, LayoutBand band, RenderOptions options)`**
+
+Draws a filled and stroked rectangle using `DepthFillColors[0]`. For `Horizontal`
+orientation the label is rendered with 90° CCW rotation along the left edge using
+`canvas.Save/Translate/RotateDegrees/Restore`. For `Vertical` orientation the label is
+horizontal at the top. Children are rendered recursively.
+
+**`RenderLifeline(SKCanvas canvas, LayoutLifeline lifeline, RenderOptions options)`**
+
+Draws a header box centered at `CentreX` (top at `TopY`, size `HeaderWidth × HeaderHeight`)
+filled with `DepthFillColors[0]`. Draws a bold centered label in the box. Draws a dashed
+vertical stem (`SKPathEffect.CreateDash`) from the bottom of the header to `BottomY`.
+
+**`RenderActivation(SKCanvas canvas, LayoutActivation activation, RenderOptions options)`**
+
+Draws a white-filled rectangle of width `LabelPadding * 2` centered at `CentreX`,
+spanning `TopY` to `BottomY`, with a stroke border.
+
+**`RenderGrid(SKCanvas canvas, LayoutGrid grid, RenderOptions options)`**
+
+Iterates rows and cells, accumulating X/Y positions. Each cell gets a filled background
+(`DepthFillColors[1]` for header rows, `DepthFillColors[0]` for body rows), a stroke
+border, and a text element vertically centered within the row height and horizontally
+aligned per `LayoutGridCell.Align`. Header cells use bold weight; body cells use regular.
+
+### Error Handling
+
+`Render` throws `ArgumentNullException` when `layout`, `options`, or `output` is null.
+`SKColor.Parse` throws `ArgumentException` for malformed hex strings; this is a programming
+error in the theme definition, not a user input error.
+
+### Dependencies
+
+- `DemaConsulting.SysML2Tools` — provides `IRenderer`, `LayoutTree`, all nine `LayoutNode`
+ subtypes, `RenderOptions`, `Theme`
+- `SkiaSharp` (OTS) — provides `SKBitmap`, `SKCanvas`, `SKImage`, `SKPaint`, `SKColor`,
+ `SKPath`, `SKPathEffect`, `SKTypeface`, `SKData`
+- Noto Sans (OTS, SIL OFL 1.1) — four static TTF files embedded as assembly resources
+
+### Callers
+
+- `DiagramRenderer.RenderWorkspace` (in `DemaConsulting.SysML2Tools`) — passes the
+ renderer to `IRenderer.Render` as part of the orchestrated rendering pipeline
+- `RenderCommand.RunAsync` (in `DemaConsulting.SysML2Tools.Tool`) — creates a
+ `PngRenderer` instance when `--format png` is selected
+
+## Requirements Traceability
+
+| Requirement ID | Satisfied by |
+| --- | --- |
+| SysML2Tools-Core-Rendering-IRenderer | `IRenderer` implementation in `PngRenderer` |
+| SysML2Tools-Core-Rendering-IRendererStateless | `PngRenderer` is a stateless, pure class |
+| SysML2Tools-Png-MediaType | `PngRenderer.MediaType` property |
+| SysML2Tools-Png-DefaultExtension | `PngRenderer.DefaultExtension` property |
+| SysML2Tools-Png-Render-Signature | `PngRenderer.Render` encodes via SkiaSharp PNG |
+| SysML2Tools-Png-Render-Box | `RenderBox` draws filled and stroked rectangle |
diff --git a/docs/design/sysml2-tools-svg.md b/docs/design/sysml2-tools-svg.md
index ccfa941b..d8232074 100644
--- a/docs/design/sysml2-tools-svg.md
+++ b/docs/design/sysml2-tools-svg.md
@@ -1,29 +1,198 @@
# DemaConsulting.SysML2Tools.Svg
-## Architecture
+## Overview
-The `DemaConsulting.SysML2Tools.Svg` package provides the SVG renderer, implementing the
-`IRenderer` interface from the core library with zero external dependencies. This system is
-a Phase 0 stub; detailed design will be populated in Phase 4+.
+`DemaConsulting.SysML2Tools.Svg` is the SVG renderer system for SysML2 Tools. It provides
+a single public class, `SvgRenderer`, that translates a `LayoutTree` into a self-contained
+SVG 1.1 document written to an output stream. The renderer has zero external runtime
+dependencies beyond the .NET base class library.
-## External Interfaces
+## SvgRenderer
-*To be defined in Phase 4+.*
+### Purpose
-## Dependencies
+`SvgRenderer` implements the `IRenderer` interface to produce SVG 1.1 diagram output
+from a `LayoutTree` intermediate representation. Each call to `Render` builds a complete
+SVG document in a `StringBuilder` and writes it to the supplied stream in UTF-8 encoding.
+The renderer is pure and stateless; no fields are mutated between calls.
-- **DemaConsulting.SysML2Tools** — provides `IRenderer` interface and semantic model.
+### Data Model
-## Risk Control Measures
+`SvgRenderer` has no instance state. All inputs are supplied through `Render` parameters.
-N/A — not a safety-classified software item.
+- `LayoutTree` — read-only input; canvas dimensions and node list
+- `RenderOptions` — read-only input; `Theme` for visual parameters, `Scale` for sizing
+- `Stream output` — write-only output; receives UTF-8 SVG bytes; caller owns lifetime
-## Data Flow
+### Font Family
-*To be defined in Phase 4+.*
+All text elements use `font-family="Noto Sans, sans-serif"`. The `Noto Sans` family is
+specified first so that browsers and renderers with Noto Sans installed use it; `sans-serif`
+is the CSS generic fallback. This replaced the earlier `Segoe UI, sans-serif` default to
+provide consistent rendering across all platforms.
-## Design Constraints
+### Font Weight and Style Per Node Type
-- Platform: multi-targets net8.0, net9.0, and net10.0 framework compatibility specifications
- on Windows, Linux, and macOS.
-- Zero external NuGet dependencies beyond the core library.
+Each node type uses a fixed font weight and style as SVG attributes:
+
+| Node Type | `font-weight` | `font-style` |
+| --- | --- | --- |
+| `LayoutBox` label | `bold` | (default) |
+| `LayoutBoxCompartment` title | `bold` | `italic` |
+| `LayoutBoxCompartment` rows | (default) | (default) |
+| `LayoutLine` midpoint label | (default) | (default) |
+| `LayoutLabel` | Per `FontWeight` field | Per `FontStyle` field |
+| `LayoutPort` label | (default) | (default) |
+| `LayoutBadge` label | (default) | (default) |
+| `LayoutBand` label | (default) | (default) |
+| `LayoutLifeline` label | `bold` | (default) |
+| `LayoutGrid` header cells | `bold` | (default) |
+| `LayoutGrid` body cells | (default) | (default) |
+
+### LayoutLabel Font Styling Fields
+
+`LayoutLabel` carries three explicit font styling fields added in Phase 4:
+
+- `Weight` (`FontWeight`) — `Regular` maps to `font-weight="normal"`; `Bold` maps to `font-weight="bold"`.
+- `Style` (`FontStyle`) — `Normal` maps to `font-style="normal"`; `Italic` maps to `font-style="italic"`.
+- `FontSize` (double) — Font size in logical pixels, used as `font-size` instead of the theme body size.
+
+### Text Length Shrink-to-Fit
+
+`LayoutBox` labels include `textLength` and `lengthAdjust="spacingAndGlyphs"` attributes
+set to `(box.Width - 2 * theme.LabelPadding) * scale`. This instructs SVG renderers to
+compress or stretch glyph spacing so the text fills (or shrinks into) the available title
+area without overflow.
+
+`LayoutLabel` nodes with `MaxWidth > 0` similarly include
+`textLength="{MaxWidth * scale}" lengthAdjust="spacingAndGlyphs"`.
+
+### Key Methods
+
+**`Render(LayoutTree layout, RenderOptions options, Stream output)`**
+
+Entry point. Validates arguments, computes canvas size clamped to a minimum of 1×1,
+writes the SVG root element with `xmlns`, `width`, `height`, and `viewBox` attributes,
+then calls `WriteArrowheadDefs` followed by recursive `RenderNode` calls for every
+top-level node. Encodes the completed `StringBuilder` as UTF-8 and writes all bytes
+to `output` in a single `Write` call.
+
+**`WriteArrowheadDefs(StringBuilder sb, Theme theme)`**
+
+Writes the SVG `` block containing six named marker elements: `arrowhead-open`
+(hollow triangle), `arrowhead-filled` (solid triangle), `arrowhead-diamond` (hollow
+four-point polygon), `arrowhead-filled-diamond` (solid four-point polygon),
+`arrowhead-circle` (open circle), and `arrowhead-bar` (perpendicular line). All markers
+use `theme.StrokeColor` and `theme.StrokeWidth`.
+
+**`RenderNode(StringBuilder sb, LayoutNode node, Theme theme, double scale)`**
+
+Dispatches by concrete node type to the appropriate typed render method. All nine
+`LayoutNode` subtypes are handled: `LayoutBox` → `RenderBox`, `LayoutLine` → `RenderLine`,
+`LayoutLabel` → `RenderLabel`, `LayoutPort` → `RenderPort`, `LayoutBadge` → `RenderBadge`,
+`LayoutBand` → `RenderBand`, `LayoutLifeline` → `RenderLifeline`,
+`LayoutActivation` → `RenderActivation`, `LayoutGrid` → `RenderGrid`. Unknown subtypes
+are silently skipped for forward compatibility.
+
+**`RenderBox(StringBuilder sb, LayoutBox box, Theme theme, double scale)`**
+
+Writes a `` element using fill color from `theme.DepthFillColors[box.Depth % count]`.
+Adds `rx`/`ry` attributes when `BoxShape.RoundedRectangle` and `LineCornerRadius > 0`.
+Writes a bold `` element with `textLength` in the title area when `box.Label` is
+non-null. Calls `RenderBoxCompartments` for any compartments, then recursively calls
+`RenderNode` for all `box.Children`.
+
+**`RenderBoxCompartments(StringBuilder sb, LayoutBox box, Theme theme, double scale)`**
+
+Writes a `` divider across the full box width at the top of each compartment,
+followed by an optional `font-weight="bold" font-style="italic"` `` title row and
+zero or more left-aligned regular-weight body-font `` rows.
+
+**`RenderLine(StringBuilder sb, LayoutLine line, Theme theme, double scale)`**
+
+Calls `BuildLinePath` to produce the path `d` attribute, then writes a `` element
+with `fill="none"`. Adds `marker-start` or `marker-end` attributes for all six non-None
+`ArrowheadStyle` values. Adds `stroke-dasharray` for `Dashed` and `Dotted` line styles.
+Writes an optional midpoint `` element when `MidpointLabel` is non-null.
+
+**`BuildLinePath(IReadOnlyList waypoints, double cornerRadius, double scale)`**
+
+Builds the SVG path `d` string. When `cornerRadius` is zero, emits plain `M`/`L`
+commands. When positive, each interior waypoint is replaced with a shortened `L` command
+to the arc start point, followed by an `A` (elliptical arc) command whose sweep direction
+(0 or 1) is determined from the cross product of the incoming and outgoing unit direction
+vectors. The radius is clamped to half the shorter adjacent segment to prevent overshoot.
+
+**`RenderLabel(StringBuilder sb, LayoutLabel label, Theme theme, double scale)`**
+
+Writes a `` element with `text-anchor` from `label.Align`, `font-size` from
+`label.FontSize`, `font-weight` and `font-style` from `label.Weight` and `label.Style`.
+When `label.MaxWidth > 0`, adds `textLength` and `lengthAdjust="spacingAndGlyphs"`.
+
+**`RenderPort(StringBuilder sb, LayoutPort port, Theme theme, double scale)`**
+
+Writes a filled 8×8 `` centered at `(CentreX, CentreY)`. Optional label is written
+as a `` element offset away from the attached `PortSide`.
+
+**`RenderBadge(StringBuilder sb, LayoutBadge badge, Theme theme, double scale)`**
+
+Writes shape-specific SVG elements: `` for `FilledCircle` and `Bullseye` (the
+latter with an additional white inner circle), `` for `Diamond`, and ``
+for `HorizontalBar` and `VerticalBar`. An optional label is written as ``.
+
+**`RenderBand(StringBuilder sb, LayoutBand band, Theme theme, double scale)`**
+
+Writes a `` with `DepthFillColors[0]` fill. For `Horizontal` bands writes a
+`` with `transform="translate(...) rotate(-90)"` on the left edge; for `Vertical`
+bands writes a horizontal `` at the top. Children are rendered recursively.
+
+**`RenderLifeline(StringBuilder sb, LayoutLifeline lifeline, Theme theme, double scale)`**
+
+Writes a `` header centered at `CentreX` filled with `DepthFillColors[0]`, a
+bold centered `` label, and a dashed `` stem (`stroke-dasharray="6 3"`) from
+the bottom of the header to `BottomY`.
+
+**`RenderActivation(StringBuilder sb, LayoutActivation activation, Theme theme, double scale)`**
+
+Writes a `` with `fill="white"` and a stroke border. Width is `LabelPadding * 2`,
+centered at `CentreX`, spanning `TopY` to `BottomY`.
+
+**`RenderGrid(StringBuilder sb, LayoutGrid grid, Theme theme, double scale)`**
+
+Iterates rows and cells, accumulating X/Y positions. Each cell gets a `` filled
+with `DepthFillColors[1]` for header rows or `DepthFillColors[0]` for body rows, plus a
+stroke border, plus a `` vertically centered in the row and aligned per
+`LayoutGridCell.Align`. Header cells have `font-weight="bold"` added conditionally;
+body cells use the browser default.
+
+### Error Handling
+
+`Render` throws `ArgumentNullException` when `layout`, `options`, or `output` is null.
+No other exceptions are expected under normal operation. XML special characters in labels
+are escaped via `EscapeXml` (replaces `&`, `<`, `>` with XML entities) to prevent
+malformed SVG output.
+
+### Dependencies
+
+- `DemaConsulting.SysML2Tools` — provides `IRenderer`, `LayoutTree`, all nine `LayoutNode`
+ subtypes, `RenderOptions`, `Theme`
+
+### Callers
+
+- `DiagramRenderer.RenderWorkspace` (in `DemaConsulting.SysML2Tools`) — passes the
+ renderer to `IRenderer.Render` as part of the orchestrated rendering pipeline
+- `RenderCommand.RunAsync` (in `DemaConsulting.SysML2Tools.Tool`) — creates a
+ `SvgRenderer` instance when `--format svg` is selected
+
+## Requirements Traceability
+
+| Requirement ID | Satisfied by |
+| --- | --- |
+| SysML2Tools-Core-Rendering-IRenderer | `IRenderer` implementation in `SvgRenderer` |
+| SysML2Tools-Core-Rendering-IRendererStateless | `SvgRenderer` is a stateless, pure class |
+| SysML2Tools-Svg-MediaType | `SvgRenderer.MediaType` property |
+| SysML2Tools-Svg-DefaultExtension | `SvgRenderer.DefaultExtension` property |
+| SysML2Tools-Svg-Render-Document | `SvgRenderer.Render` writes SVG root element |
+| SysML2Tools-Svg-Render-Box | `RenderBox` writes `` element |
+| SysML2Tools-Svg-Render-Label | `RenderLabel` writes `` element |
+| SysML2Tools-Svg-Render-Line | `RenderLine` writes `` element |
diff --git a/docs/design/sysml2-tools-tool/cli/context.md b/docs/design/sysml2-tools-tool/cli/context.md
index 6610a768..e8f1d0af 100644
--- a/docs/design/sysml2-tools-tool/cli/context.md
+++ b/docs/design/sysml2-tools-tool/cli/context.md
@@ -28,6 +28,19 @@ neither flag was present.
**HeadingDepth**: `int` — Heading depth for markdown output; valid range 1–6, default 1;
supplied via `--depth`.
+**Command**: `SysmlCommand` — `SysmlCommand.Lint` when `lint` is the first positional
+argument; `SysmlCommand.Render` when `render` is the first positional argument;
+`SysmlCommand.None` otherwise.
+
+**Files**: `IReadOnlyList` — file glob patterns collected from positional arguments
+after the command token.
+
+**OutputDirectory**: `string?` — path supplied after `--output`, or `null` if the option
+was absent. Used by the render command as the output directory for diagram files.
+
+**RendererFormat**: `string?` — value supplied after `--format` (e.g., `"svg"` or `"png"`),
+or `null` if the option was absent. Used by the render command to select the output format.
+
**ExitCode**: `int` (derived) — Returns 1 if `_hasErrors` is true; returns 0 otherwise.
#### Key Methods
@@ -88,3 +101,5 @@ available.
- **Program** — creates `Context` via `Context.Create` and calls `WriteLine` and `WriteError`.
- **Validation** — receives `Context` from `Program` and calls `WriteLine` and `WriteError`.
+- **RenderCommand** — reads `Files`, `RendererFormat`, and `OutputDirectory`; calls
+ `WriteLine` and `WriteError`.
diff --git a/docs/design/sysml2-tools-tool/render.md b/docs/design/sysml2-tools-tool/render.md
new file mode 100644
index 00000000..997e7192
--- /dev/null
+++ b/docs/design/sysml2-tools-tool/render.md
@@ -0,0 +1,78 @@
+### DemaConsulting.SysML2Tools.Tool — Render Subsystem
+
+#### Overview
+
+The Render subsystem implements the `render` CLI verb. It provides a single internal
+static class, `RenderCommand`, with one entry-point method `RunAsync`. The subsystem
+coordinates workspace loading, renderer selection, and file output for the Phase 4
+diagram-rendering feature.
+
+#### RenderCommand
+
+##### Purpose
+
+`RenderCommand.RunAsync` translates the user's CLI intent — expressed as file glob
+patterns, a format option, and an output directory — into rendered diagram files on
+disk. It delegates workspace loading to `WorkspaceLoader`, renderer instantiation to
+a simple string comparison, and rendering orchestration to `DiagramRenderer`.
+
+##### Data Model
+
+No instance state. All data flows through the `Context` parameter and local variables.
+
+- Input: `Context` — file patterns (`Files`), format (`RendererFormat`), output path
+ (`OutputDirectory`)
+- Intermediate: `SysmlLoadResult` — workspace and diagnostics from `WorkspaceLoader`
+- Output: files written to `OutputDirectory` via `File.Create`
+
+##### Key Methods
+
+**`RunAsync(Context context)`**
+
+Entry point for the render command. Steps:
+
+1. Validates that `context.Files` is non-empty; calls `context.WriteError` and returns
+ when no patterns are supplied.
+2. Calls `WorkspaceLoader.LoadAsync(context.Files)` to load the workspace.
+3. Reports all diagnostics from `loadResult.Diagnostics`, writing errors via
+ `context.WriteError` and other messages via `context.WriteLine`.
+4. Selects renderer: `PngRenderer` when `context.RendererFormat` equals `"png"`
+ (case-insensitive); `SvgRenderer` otherwise.
+5. Calls `DiagramRenderer.RenderWorkspace` with the loaded workspace, selected renderer,
+ and `new RenderOptions(Themes.Light)`.
+6. Writes a "No views found" message and returns when `outputs` is empty.
+7. Resolves the output directory (defaults to `Directory.GetCurrentDirectory()`), creates
+ it via `Directory.CreateDirectory`, and writes each `RenderOutput.Data` stream to a
+ file named `RenderOutput.SuggestedFileName`.
+
+##### Error Handling
+
+- Missing file patterns: `context.WriteError` is called and the method returns early.
+- Load diagnostics: reported to the context; non-fatal; rendering proceeds regardless.
+- No view declarations: informational message; no output files written; returns normally.
+- File system errors (e.g., permission denied): propagate as `IOException`; handled by
+ `Program.Main`'s outer exception handler.
+
+##### Dependencies
+
+- `WorkspaceLoader` (in `DemaConsulting.SysML2Tools.Semantic`) — loads workspace
+- `DiagramRenderer` (in `DemaConsulting.SysML2Tools.Rendering`) — renders views
+- `SvgRenderer` (in `DemaConsulting.SysML2Tools.Svg`) — produces SVG output
+- `PngRenderer` (in `DemaConsulting.SysML2Tools.Png`) — produces PNG output
+- `Themes.Light` (in `DemaConsulting.SysML2Tools.Rendering`) — default theme
+- `Context` (in `DemaConsulting.SysML2Tools.Cli`) — reads arguments; writes output
+
+##### Callers
+
+- `Program.RunToolLogicAsync` — dispatches to `RenderCommand.RunAsync` when
+ `context.Command == SysmlCommand.Render`
+
+#### Requirements Traceability
+
+| Requirement ID | Satisfied by |
+| --- | --- |
+| SysML2Tools-Tool-Render-Patterns | Input validation at start of `RunAsync` |
+| SysML2Tools-Tool-Render-Load | `WorkspaceLoader.LoadAsync` call in `RunAsync` |
+| SysML2Tools-Tool-Render-Format | Renderer selection switch in `RunAsync` |
+| SysML2Tools-Tool-Render-Output | Output directory resolution in `RunAsync` |
+| SysML2Tools-Tool-Render-Empty | Empty-outputs message in `RunAsync` |
diff --git a/docs/reqstream/sysml2-tools-core/rendering.yaml b/docs/reqstream/sysml2-tools-core/rendering.yaml
index c97a7651..0b42242e 100644
--- a/docs/reqstream/sysml2-tools-core/rendering.yaml
+++ b/docs/reqstream/sysml2-tools-core/rendering.yaml
@@ -36,8 +36,8 @@ sections:
filesystem access ensures that renderers remain testable in-process with
in-memory streams.
tests:
- - SvgRenderer_Render_ThrowsNotImplemented
- - PngRenderer_Render_ThrowsNotImplemented
+ - SvgRenderer_Render_EmptyTree_WritesValidSvg
+ - PngRenderer_Render_EmptyTree_WritesPngBytes
- id: SysML2Tools-Core-Rendering-Theme
title: >-
@@ -102,7 +102,7 @@ sections:
layout and rendering pipeline. Centralizing this loop ensures consistent
error handling and output collection across all views and all renderer types.
tests:
- - DiagramRenderer_RenderWorkspace_ThrowsNotImplemented
+ - DiagramRenderer_RenderWorkspace_NoViews_ReturnsEmptyList
- id: SysML2Tools-Core-Rendering-RenderOutput
title: >-
diff --git a/docs/reqstream/sysml2-tools-png.yaml b/docs/reqstream/sysml2-tools-png.yaml
index 2c6521a1..545e627f 100644
--- a/docs/reqstream/sysml2-tools-png.yaml
+++ b/docs/reqstream/sysml2-tools-png.yaml
@@ -1,11 +1,52 @@
---
-# SysML2Tools PNG Renderer System-Level Requirements (Phase 0 Stub)
+# SysML2Tools PNG Renderer System-Level Requirements
#
# PURPOSE:
# - Define system-level requirements for the DemaConsulting.SysML2Tools.Png package
# - PNG renderer: PngRenderer implements IRenderer using SkiaSharp (MIT)
-# - Requirements will be populated in Phase 4+ (SkiaSharp dependency added in Phase 1)
sections:
- title: SysML2ToolsPng Requirements
- requirements: []
+ requirements:
+ - id: SysML2Tools-Png-MediaType
+ title: >-
+ PngRenderer shall return "image/png" for the MediaType property.
+ justification: |
+ The media type is the standard MIME type for PNG content and is used
+ by the CLI and by callers to identify the output format without
+ inspecting the stream content.
+ tests:
+ - PngRenderer_MediaType_IsImagePng
+
+ - id: SysML2Tools-Png-DefaultExtension
+ title: >-
+ PngRenderer shall return ".png" for the DefaultExtension property.
+ justification: |
+ The default extension is used by DiagramRenderer to construct the
+ suggested file name for each rendered view output.
+ tests:
+ - PngRenderer_DefaultExtension_IsDotPng
+
+ - id: SysML2Tools-Png-Render-Signature
+ title: >-
+ PngRenderer.Render shall write a valid PNG stream to the output,
+ beginning with the four-byte PNG magic number (0x89 0x50 0x4E 0x47),
+ for any LayoutTree input.
+ justification: |
+ The PNG signature bytes are the primary conformance check for PNG
+ output. Their presence confirms that SkiaSharp produced a valid PNG
+ image file that can be opened by any standard PNG viewer.
+ tests:
+ - PngRenderer_Render_EmptyTree_WritesPngBytes
+ - PngRenderer_Render_EmptyTree_WritesPngSignature
+
+ - id: SysML2Tools-Png-Render-Box
+ title: >-
+ PngRenderer.Render shall render each LayoutBox node as a filled and
+ stroked rectangle in the PNG output without throwing.
+ justification: |
+ Box rendering must complete without error for any depth level and
+ any valid label text. The test confirms that SkiaSharp drawing
+ operations complete successfully and produce a non-empty PNG output.
+ tests:
+ - PngRenderer_Render_SingleBox_ProducesNonEmptyOutput
diff --git a/docs/reqstream/sysml2-tools-svg.yaml b/docs/reqstream/sysml2-tools-svg.yaml
index 5e73472b..3c5fd679 100644
--- a/docs/reqstream/sysml2-tools-svg.yaml
+++ b/docs/reqstream/sysml2-tools-svg.yaml
@@ -1,11 +1,72 @@
---
-# SysML2Tools SVG Renderer System-Level Requirements (Phase 0 Stub)
+# SysML2Tools SVG Renderer System-Level Requirements
#
# PURPOSE:
# - Define system-level requirements for the DemaConsulting.SysML2Tools.Svg package
# - SVG renderer: SvgRenderer implements IRenderer with zero external dependencies
-# - Requirements will be populated in Phase 4+
sections:
- title: SysML2ToolsSvg Requirements
- requirements: []
+ requirements:
+ - id: SysML2Tools-Svg-MediaType
+ title: >-
+ SvgRenderer shall return "image/svg+xml" for the MediaType property.
+ justification: |
+ The media type is the standard MIME type for SVG content and is used
+ by the CLI and by callers to determine the output format without
+ inspecting the stream content.
+ tests:
+ - SvgRenderer_MediaType_IsImageSvgXml
+
+ - id: SysML2Tools-Svg-DefaultExtension
+ title: >-
+ SvgRenderer shall return ".svg" for the DefaultExtension property.
+ justification: |
+ The default extension is used by DiagramRenderer to construct the
+ suggested file name for each rendered view output.
+ tests:
+ - SvgRenderer_DefaultExtension_IsDotSvg
+
+ - id: SysML2Tools-Svg-Render-Document
+ title: >-
+ SvgRenderer.Render shall write a valid SVG 1.1 document to the output
+ stream, containing the SVG root element, for any LayoutTree input.
+ justification: |
+ The SVG root element is required by the SVG 1.1 specification and by
+ all SVG viewers. Its presence confirms that a valid, well-formed SVG
+ document was produced.
+ tests:
+ - SvgRenderer_Render_EmptyTree_ProducesSvgDocument
+ - SvgRenderer_Render_EmptyTree_WritesValidSvg
+
+ - id: SysML2Tools-Svg-Render-Box
+ title: >-
+ SvgRenderer.Render shall produce a rect element for each LayoutBox node
+ in the LayoutTree.
+ justification: |
+ Each LayoutBox in the layout tree represents a visual rectangular
+ container. The SVG rect element is the standard output for rectangles,
+ enabling SVG viewers to render diagram boxes correctly.
+ tests:
+ - SvgRenderer_Render_SingleBox_ProducesRectElement
+
+ - id: SysML2Tools-Svg-Render-Label
+ title: >-
+ SvgRenderer.Render shall produce a text element for each LayoutLabel node
+ in the LayoutTree.
+ justification: |
+ Standalone text labels in a layout tree must be rendered as SVG text
+ elements to appear correctly in SVG viewers and document export pipelines.
+ tests:
+ - SvgRenderer_Render_SingleLabel_ProducesTextElement
+
+ - id: SysML2Tools-Svg-Render-Line
+ title: >-
+ SvgRenderer.Render shall produce a path element for each LayoutLine node
+ in the LayoutTree.
+ justification: |
+ Connection lines between diagram elements are represented as LayoutLine
+ nodes. SVG path elements are the standard output for polylines and
+ support arrowhead markers through the SVG marker mechanism.
+ tests:
+ - SvgRenderer_Render_SingleLine_ProducesPathElement
diff --git a/docs/reqstream/sysml2-tools-tool/cli/context.yaml b/docs/reqstream/sysml2-tools-tool/cli/context.yaml
index f57a22fa..4505a4e4 100644
--- a/docs/reqstream/sysml2-tools-tool/cli/context.yaml
+++ b/docs/reqstream/sysml2-tools-tool/cli/context.yaml
@@ -95,3 +95,21 @@ sections:
compliance workflows.
tests:
- Context_Create_LogFlag_InvalidPath_ThrowsInvalidOperationException
+
+ - id: Template-Context-RenderArgs
+ title: >-
+ The Context class shall parse the render command's --format and --output arguments
+ into the RendererFormat and OutputDirectory properties.
+ justification: |
+ The render command relies on Context to supply the renderer format and output
+ directory without needing to re-parse command-line arguments. The render command
+ token must also set Command to SysmlCommand.Render and collect subsequent
+ positional arguments into Files.
+ tests:
+ - Context_Create_RenderCommand_SetsCommandRender
+ - Context_Create_RenderCommand_WithFormat_SetsSvgFormat
+ - Context_Create_RenderCommand_WithPngFormat_SetsPngFormat
+ - Context_Create_RenderCommand_WithOutput_SetsOutputDirectory
+ - Context_Create_RenderCommand_WithFiles_SetsFiles
+ - Context_Create_RenderCommand_FormatWithoutValue_ThrowsArgumentException
+ - Context_Create_RenderCommand_OutputWithoutValue_ThrowsArgumentException
diff --git a/docs/reqstream/sysml2-tools-tool/render.yaml b/docs/reqstream/sysml2-tools-tool/render.yaml
new file mode 100644
index 00000000..874462a5
--- /dev/null
+++ b/docs/reqstream/sysml2-tools-tool/render.yaml
@@ -0,0 +1,62 @@
+---
+# Render Subsystem Requirements
+#
+# PURPOSE:
+# - Define requirements for the DemaConsulting.SysML2Tools.Tool Render subsystem
+# - RenderCommand implements the CLI render verb
+# - Requirements describe observable behavior of the render command
+
+sections:
+ - title: Render Subsystem Requirements
+ requirements:
+ - id: SysML2Tools-Tool-Render-Patterns
+ title: >-
+ The render command shall accept one or more file glob patterns as positional
+ arguments and report an error when no patterns are supplied.
+ justification: |
+ File glob patterns are the primary input mechanism for the CLI tool. An error
+ on missing patterns gives users an actionable message instead of silent failure.
+ tests:
+ - RenderSubsystem_NoFiles_ReportsError
+
+ - id: SysML2Tools-Tool-Render-Load
+ title: >-
+ The render command shall load a SysML workspace from the supplied file patterns
+ and report any load diagnostics before rendering.
+ justification: |
+ Reporting diagnostics before rendering ensures that users can identify and
+ correct model errors that would affect diagram output.
+ tests:
+ - RenderSubsystem_WithFiles_LoadsWorkspace
+
+ - id: SysML2Tools-Tool-Render-Format
+ title: >-
+ The render command shall select the SVG renderer when the --format option is
+ "svg" or absent, and the PNG renderer when --format is "png".
+ justification: |
+ Format selection through a named option makes the CLI self-documenting and
+ avoids dependence on file extension inference.
+ tests:
+ - RenderSubsystem_FormatSvg_UsesSvgRenderer
+ - RenderSubsystem_FormatPng_UsesPngRenderer
+
+ - id: SysML2Tools-Tool-Render-Output
+ title: >-
+ The render command shall write rendered output files to the directory specified
+ by the --output option, defaulting to the current working directory when the
+ option is absent.
+ justification: |
+ Defaulting to the current working directory matches common CLI tool conventions
+ and avoids requiring the option for simple single-directory use cases.
+ tests:
+ - RenderSubsystem_NoOutputDir_UsesCurrentDirectory
+
+ - id: SysML2Tools-Tool-Render-Empty
+ title: >-
+ The render command shall report a diagnostic message and write no output files
+ when the workspace contains no view declarations.
+ justification: |
+ An explicit message when no views are found prevents user confusion when
+ a model loads successfully but produces no diagram output.
+ tests:
+ - RenderSubsystem_NoViews_ReportsNoOutput
diff --git a/docs/verification/sysml2-tools-core/rendering.md b/docs/verification/sysml2-tools-core/rendering.md
index a961b503..c4ed20cd 100644
--- a/docs/verification/sysml2-tools-core/rendering.md
+++ b/docs/verification/sysml2-tools-core/rendering.md
@@ -18,11 +18,11 @@ All test inputs are constructed inline. No external network access or services a
- All unit tests pass with zero failures across all three target frameworks.
- `SvgRenderer.MediaType` returns `"image/svg+xml"`.
- `SvgRenderer.DefaultExtension` returns `".svg"`.
-- `SvgRenderer.Render` throws `NotImplementedException` with message containing "Phase 4".
+- `SvgRenderer.Render` with an empty `LayoutTree` writes a non-empty SVG document to the output stream.
- `PngRenderer.MediaType` returns `"image/png"`.
- `PngRenderer.DefaultExtension` returns `".png"`.
-- `PngRenderer.Render` throws `NotImplementedException` with message containing "Phase 4".
-- `DiagramRenderer.RenderWorkspace` throws `NotImplementedException` with message containing "Phase 4".
+- `PngRenderer.Render` with an empty `LayoutTree` writes a valid PNG stream beginning with the PNG magic number.
+- `DiagramRenderer.RenderWorkspace` with a workspace containing no view declarations returns an empty list.
- `Themes.Light` is non-null and has `DepthFillColors.Count >= 1` and non-empty `StrokeColor`.
- `Themes.Dark` is non-null and has `DepthFillColors.Count >= 1` and non-empty `StrokeColor`.
- `Themes.Print` is non-null and has `DepthFillColors.Count >= 1` and non-empty `StrokeColor`.
@@ -39,10 +39,10 @@ identifies itself with the correct MIME type for SVG output.
`DefaultExtension` property is asserted to equal `".svg"`. This confirms the renderer
provides a correct default extension for output file naming.
-**SvgRenderer_Render_ThrowsNotImplemented**: A `SvgRenderer` instance is constructed and
-`Render` is called with an empty `LayoutTree`, default `RenderOptions`, and a
-`MemoryStream`; a `NotImplementedException` is expected. This confirms the Phase 4
-deferral stub behavior.
+**SvgRenderer_Render_EmptyTree_WritesValidSvg**: A `SvgRenderer` instance is constructed
+and `Render` is called with an empty `LayoutTree`, default `RenderOptions`, and a
+`MemoryStream`; the stream is asserted to be non-empty and contain `