Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ language: en

# Project-specific technical terms and tool names
words:
- Aabb
- AABB
- Adler
- apimark
- ApiMark
- basn
- Beziers
- BGRA
- BITMAPCOREHEADER
- BITMAPFILEHEADER
- BITMAPINFOHEADER
- BITMAPV
- buildmark
- Casteljau
- CMYK
- coeffs
- dequantize
Expand Down Expand Up @@ -79,6 +83,7 @@ words:
- premultiplies
- premultiplying
- Qube
- rasterizer
- reqstream
- reviewmark
- Rgba
Expand All @@ -94,11 +99,14 @@ words:
- subsampling
- seekable
- Seekable
- Subpaths
- subpaths
- sysml
- tEXt
- triaging
- Truecolor
- Unpremultiply
- unioning
- unpremultiplication
- unpremultiplied
- unpremultiplies
Expand Down
76 changes: 76 additions & 0 deletions .reviewmark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,82 @@ reviews:
- "test/DemaConsulting.CanvasNet.Tests/Codecs/JpegCodecTests.cs" # unit tests
- "test/DemaConsulting.CanvasNet.Tests/Codecs/JpegFixtureTests.cs" # fixture conformance tests

# CanvasNet-Geometry Review (one per subsystem)
- id: CanvasNet-Geometry
title: Review that CanvasNet Geometry Subsystem Implementation is Correct
context:
- "docs/design/canvas-net.md"
- "docs/reqstream/canvas-net.yaml"
paths:
- "docs/reqstream/canvas-net/geometry.yaml" # subsystem requirements
- "docs/design/canvas-net/geometry.md" # subsystem design
- "docs/verification/canvas-net/geometry.md" # subsystem verification

# CanvasNet-Geometry-Rect Review (one per unit)
- id: CanvasNet-Geometry-Rect
title: Review that CanvasNet Geometry Rect Implementation is Correct
context:
- "docs/design/canvas-net.md"
- "docs/reqstream/canvas-net.yaml"
- "docs/design/canvas-net/geometry.md"
- "docs/reqstream/canvas-net/geometry.yaml"
paths:
- "docs/reqstream/canvas-net/geometry/rect.yaml" # unit requirements
- "docs/design/canvas-net/geometry/rect.md" # unit design
- "docs/verification/canvas-net/geometry/rect.md" # unit verification
- "src/DemaConsulting.CanvasNet/Geometry/Rect.cs" # unit source
- "src/DemaConsulting.CanvasNet/Geometry/NamespaceDoc.cs" # Geometry namespace documentation
- "test/DemaConsulting.CanvasNet.Tests/Geometry/RectTests.cs" # unit tests

# CanvasNet-Geometry-Path Review (one per unit)
- id: CanvasNet-Geometry-Path
title: Review that CanvasNet Geometry Path Implementation is Correct
context:
- "docs/design/canvas-net.md"
- "docs/reqstream/canvas-net.yaml"
- "docs/design/canvas-net/geometry.md"
- "docs/reqstream/canvas-net/geometry.yaml"
paths:
- "docs/reqstream/canvas-net/geometry/path.yaml" # unit requirements
- "docs/design/canvas-net/geometry/path.md" # unit design
- "docs/verification/canvas-net/geometry/path.md" # unit verification
- "src/DemaConsulting.CanvasNet/Geometry/Path.cs" # unit source
- "src/DemaConsulting.CanvasNet/Geometry/PathBuilder.cs" # unit source
- "src/DemaConsulting.CanvasNet/Geometry/Subpath.cs" # supporting data type source
- "src/DemaConsulting.CanvasNet/Geometry/PathCommand.cs" # supporting data type source
- "src/DemaConsulting.CanvasNet/Geometry/PathCommandType.cs" # supporting data type source
- "test/DemaConsulting.CanvasNet.Tests/Geometry/PathBuilderTests.cs" # unit tests

# CanvasNet-Geometry-BezierFlattening Review (one per unit)
- id: CanvasNet-Geometry-BezierFlattening
title: Review that CanvasNet Geometry BezierFlattening Implementation is Correct
context:
- "docs/design/canvas-net.md"
- "docs/reqstream/canvas-net.yaml"
- "docs/design/canvas-net/geometry.md"
- "docs/reqstream/canvas-net/geometry.yaml"
paths:
- "docs/reqstream/canvas-net/geometry/bezier-flattening.yaml" # unit requirements
- "docs/design/canvas-net/geometry/bezier-flattening.md" # unit design
- "docs/verification/canvas-net/geometry/bezier-flattening.md" # unit verification
- "src/DemaConsulting.CanvasNet/Geometry/BezierFlattening.cs" # unit source
- "test/DemaConsulting.CanvasNet.Tests/Geometry/BezierFlatteningTests.cs" # unit tests

# CanvasNet-Geometry-SvgArcConverter Review (one per unit)
- id: CanvasNet-Geometry-SvgArcConverter
title: Review that CanvasNet Geometry SvgArcConverter Implementation is Correct
context:
- "docs/design/canvas-net.md"
- "docs/reqstream/canvas-net.yaml"
- "docs/design/canvas-net/geometry.md"
- "docs/reqstream/canvas-net/geometry.yaml"
paths:
- "docs/reqstream/canvas-net/geometry/svg-arc-converter.yaml" # unit requirements
- "docs/design/canvas-net/geometry/svg-arc-converter.md" # unit design
- "docs/verification/canvas-net/geometry/svg-arc-converter.md" # unit verification
- "src/DemaConsulting.CanvasNet/Geometry/SvgArcConverter.cs" # unit source
- "test/DemaConsulting.CanvasNet.Tests/Geometry/SvgArcConverterTests.cs" # unit tests

# OTS-BuildMark Review (one per OTS item)
- id: OTS-BuildMark
title: Review that BuildMark Provides Required Functionality
Expand Down
117 changes: 93 additions & 24 deletions docs/design/canvas-net.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This document provides the system-level design for CanvasNet.
## Architecture

CanvasNet is a .NET library providing a canvas-based drawing and rendering API, following
DEMA Consulting best practices. The system consists of two implemented subsystems, plus one
DEMA Consulting best practices. The system consists of three implemented subsystems, plus one
subsystem reserved for future work:

- **Canvas subsystem** (namespace `DemaConsulting.CanvasNet.Canvas`, folder
Expand All @@ -23,9 +23,23 @@ subsystem reserved for future work:
Grayscale, strip-based TIFF 6.0 with None/PackBits/LZW/Deflate compression, either byte order),
and `JpegCodec` (a common real-world subset of JPEG: baseline/progressive decode with
4:4:4/4:2:2/4:2:0 support, baseline 4:2:0 encode). See _Codecs Subsystem Design_ (`codecs.md`).
- **Geometry subsystem** (namespace `DemaConsulting.CanvasNet.Geometry`, folder
`src/DemaConsulting.CanvasNet/Geometry/`, flat — no further nesting): vector-geometry
primitives, built directly on `System.Numerics.Vector2`/`Matrix3x2` — the `Rect` unit (an
axis-aligned bounding rectangle with a union-identity `Empty` sentinel), the `Path` unit (an
immutable vector path and its fluent `PathBuilder`, together with the supporting `Subpath`,
`PathCommand`, and `PathCommandType` types documented inline), the `BezierFlattening` unit
(adaptive quadratic/cubic Bezier curve flattening), and the `SvgArcConverter` unit
(SVG-style elliptical arc to cubic Bezier conversion). `Geometry` has no dependency on `Canvas`
or `Codecs`, and neither of those subsystems depends on `Geometry`. See
_Geometry Subsystem Design_ (`geometry.md`).
- **Drawing subsystem** (reserved): not yet implemented. No folder, namespace, or documentation
exists for it yet. It is reserved for future drawing primitives (shapes, brushes, pens,
transforms) that will build on top of the `Canvas` subsystem's `Surface` unit.
exists for it yet. It is reserved for future higher-level drawing primitives (shapes, brushes,
pens, and rendering of a `Geometry.Path` onto a `Canvas.Surface`) that will build on top of both
the `Canvas` subsystem's `Surface` unit and the `Geometry` subsystem's vector primitives.
`Geometry` is deliberately distinct from `Drawing`: `Geometry` describes shape geometry (paths,
bounds, curve math) with no notion of pixels, color, or rasterization, while the reserved
`Drawing` subsystem will own turning that geometry into pixels.

The `Codecs` subsystem depends on the `Canvas` subsystem's `Surface` unit (constructing surfaces
and reading/writing rows via `Surface.GetRowSpanBytes`); the `Canvas` subsystem has no dependency
Expand All @@ -42,6 +56,18 @@ _Surface Unit Design_ (`canvas/surface.md`), _BmpCodec Unit Design_
_TiffCodec Unit Design_ (`codecs/tiff-codec.md`), and _JpegCodec Unit Design_
(`codecs/jpeg-codec.md`) for each unit's internal collaboration.

The `Geometry` subsystem's four units collaborate as follows: `PathBuilder` records raw drawing
commands (including raw, unconverted SVG arc parameters) and produces immutable `Path` snapshots;
`Path.GetBounds` is the primary internal consumer of both `BezierFlattening` (to flatten curves
when a tighter, tolerance-based bound is requested) and `SvgArcConverter` (to convert any `ArcTo`
command to cubic Bezier segments before applying either bounds mode, since arcs carry no control
points of their own). `Rect` has no dependency on the other three units, but is the return type of
`Path.GetBounds` and is used throughout as the common bounding-box representation. See
_Geometry Subsystem Design_ (`geometry.md`), _Rect Unit Design_ (`geometry/rect.md`),
_Path Unit Design_ (`geometry/path.md`), _BezierFlattening Unit Design_
(`geometry/bezier-flattening.md`), and _SvgArcConverter Unit Design_
(`geometry/svg-arc-converter.md`) for each unit's internal collaboration.

## External Interfaces

The system exposes the following public API to external consumers:
Expand Down Expand Up @@ -108,26 +134,65 @@ The system exposes the following public API to external consumers:
range 1-100. Throws `ArgumentNullException` for a null `surface`/`stream`/`path`,
`ArgumentException` for an empty `path`, and `ArgumentOutOfRangeException` for an out-of-range
`quality`.

| Interface | Direction | Format | Constraints |
| -------------------------------- | ---------------- | ------------------------------ | ---------------------------- |
| `Surface(int, int)` | Inbound | Constructor call | `width`, `height` in 1-8192 |
| `Surface[int, int]` | Inbound/Outbound | Indexer get/set | `x`, `y` within bounds |
| `Surface.GetRowSpanBytes(int)` | Outbound | `Span<byte>` return | `y` within bounds |
| `Surface.GetRowSpan(int)` | Outbound | `Span<Rgba32>` return | `y` within bounds |
| `Surface.Crop(int,int,int,int)` | Inbound/Outbound | Method call / `Surface` return | Region within source bounds |
| `Surface.PremultiplyAlpha()` | Inbound | Method call | None |
| `Surface.UnpremultiplyAlpha()` | Inbound | Method call | None |
| `Surface.CompositeOver(Surface)` | Inbound | Method call | Equal dimensions, non-null |
| `Surface.CompositeOver(Rgba32)` | Inbound | Method call | None |
| `BmpCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid BMP stream or path |
| `BmpCodec.Save(...)` | Inbound | Method call | `surface` non-null |
| `PngCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid PNG stream or path |
| `PngCodec.Save(...)` | Inbound | Method call | `surface` non-null |
| `TiffCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid TIFF stream or path |
| `TiffCodec.Save(...)` | Inbound | Method call | `surface` non-null |
| `JpegCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid JPEG stream or path |
| `JpegCodec.Save(...)` | Inbound | Method call | `surface` non-null |
- **Rect(float x, float y, float width, float height)**: Constructor; an axis-aligned rectangle in
position-plus-size form. `Rect.Empty` is a static, publicly readable union-identity sentinel.
- **Rect.Union(Rect)** / **Rect.Union(Rect, Rect)**: Returns the smallest rectangle enclosing both
rectangles (instance and static forms).
- **Rect.Intersect(Rect)** / **Rect.Intersect(Rect, Rect)**: Returns the overlapping region, or
`Rect.Empty` if the rectangles are disjoint (instance and static forms).
- **Rect.Transform(Matrix3x2)**: Returns the smallest axis-aligned rectangle enclosing this
rectangle after applying the given transform to all four corners.
- **Rect.Contains(Vector2)**: Returns whether a point lies within the rectangle (half-open on both
axes).
- **PathBuilder()**: Constructor; a reusable, mutable, fluent path builder.
- **PathBuilder.MoveTo/LineTo/QuadraticBezierTo/CubicBezierTo/ArcTo(...)**: Fluent, `this`-returning
methods appending a drawing command to the current subpath (or starting a new one, for
`MoveTo`). `LineTo`/`QuadraticBezierTo`/`CubicBezierTo`/`ArcTo`/`Close` throw
`InvalidOperationException` if called before the first `MoveTo`, or after `Close` without an
intervening `MoveTo`.
- **PathBuilder.Close()**: Fluent method marking the current subpath as closed.
- **PathBuilder.Build()**: Returns an immutable `Path` snapshot of every command issued so far;
the builder remains usable afterward.
- **PathBuilder.Clear()**: Resets the builder to its initial empty state for reuse.
- **Path.Subpaths**: Read-only property exposing the path's ordered, independent subpaths.
- **Path.GetBounds(float flattenTolerance = 0)**: Returns a conservative (default) or, given a
positive `flattenTolerance`, a tighter flattening-based axis-aligned bounding `Rect`.
- **Path.Empty**: Static singleton with zero subpaths.
- **BezierFlattening.FlattenCubic(...)** / **BezierFlattening.FlattenQuadratic(...)**: Appends the
adaptively flattened polyline points (start point never written, end point always written
last) for a cubic or quadratic Bezier curve to a caller-supplied `IList<Vector2>`. Throws
`ArgumentOutOfRangeException` if `tolerance` is less than or equal to zero.
- **SvgArcConverter.ToBeziers(...)**: Appends the cubic Bezier segments equivalent to an SVG-style
elliptical arc to a caller-supplied output list, in end-to-end order. Never throws for any
SVG-valid input.

| Interface | Direction | Format | Constraints |
| -------------------------------- | ---------------- | ------------------------------ | ----------------------------- |
| `Surface(int, int)` | Inbound | Constructor call | `width`, `height` in 1-8192 |
| `Surface[int, int]` | Inbound/Outbound | Indexer get/set | `x`, `y` within bounds |
| `Surface.GetRowSpanBytes(int)` | Outbound | `Span<byte>` return | `y` within bounds |
| `Surface.GetRowSpan(int)` | Outbound | `Span<Rgba32>` return | `y` within bounds |
| `Surface.Crop(int,int,int,int)` | Inbound/Outbound | Method call / `Surface` return | Region within source bounds |
| `Surface.PremultiplyAlpha()` | Inbound | Method call | None |
| `Surface.UnpremultiplyAlpha()` | Inbound | Method call | None |
| `Surface.CompositeOver(Surface)` | Inbound | Method call | Equal dimensions, non-null |
| `Surface.CompositeOver(Rgba32)` | Inbound | Method call | None |
| `BmpCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid BMP stream or path |
| `BmpCodec.Save(...)` | Inbound | Method call | `surface` non-null |
| `PngCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid PNG stream or path |
| `PngCodec.Save(...)` | Inbound | Method call | `surface` non-null |
| `TiffCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid TIFF stream or path |
| `TiffCodec.Save(...)` | Inbound | Method call | `surface` non-null |
| `JpegCodec.Load(...)` | Inbound/Outbound | Method call / `Surface` return | Valid JPEG stream or path |
| `JpegCodec.Save(...)` | Inbound | Method call | `surface` non-null |
| `Rect.Union(...)` | Inbound/Outbound | Method call / `Rect` return | None |
| `Rect.Intersect(...)` | Inbound/Outbound | Method call / `Rect` return | None |
| `Rect.Transform(Matrix3x2)` | Inbound/Outbound | Method call / `Rect` return | None |
| `PathBuilder.*To(...)` | Inbound/Outbound | Method call / `this` return | Called after `MoveTo` |
| `PathBuilder.Build()` | Outbound | Method call / `Path` return | None |
| `Path.GetBounds(float)` | Outbound | Method call / `Rect` return | None |
| `BezierFlattening.Flatten*(...)` | Inbound/Outbound | Method call / list append | `tolerance` greater than zero |
| `SvgArcConverter.ToBeziers(...)` | Inbound/Outbound | Method call / list append | None |

## Dependencies

Expand All @@ -140,7 +205,11 @@ Library (`Surface`'s remaining use of `Span<T>` and `MemoryMarshal` are BCL APIs
natively on every target framework; `BmpCodec` uses only `System.IO` types; `PngCodec` and
`TiffCodec` additionally use `System.IO.Compression.DeflateStream`; `JpegCodec` additionally uses
`System.Numerics.Vector<T>` for optional SIMD acceleration; all of these are BCL APIs available on
every target framework, with no additional runtime NuGet package required). The following OTS
every target framework, with no additional runtime NuGet package required). The `Geometry`
subsystem introduces zero new runtime NuGet dependencies: it is implemented entirely against
`System.Numerics.Vector2` and `System.Numerics.Matrix3x2`, which are in-box BCL types available
natively on every target framework this library supports (net8.0, net9.0, net10.0) — no custom
point/vector wrapper types were introduced. The following OTS
items are used for building and verifying this system (not consumed at runtime); see
_OTS Integration Design_ (`docs/design/ots.md`) and each item's dedicated design document for
details:
Expand Down
Loading
Loading