Skip to content

Bump the nuget-dependencies group with 2 updates - #1

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/test/DemaConsulting.TemplateDotNetLibrary.Tests/nuget-dependencies-5f6f23b065
Closed

dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/test/DemaConsulting.TemplateDotNetLibrary.Tests/nuget-dependencies-5f6f23b065

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Updated Microsoft.NET.Test.Sdk from 18.10.0 to 18.10.1.

Release notes

Sourced from Microsoft.NET.Test.Sdk's releases.

18.10.1

What's Changed

Full Changelog: microsoft/vstest@v18.10.0...v18.10.1

Commits viewable in compare view.

Updated Microsoft.Testing.Extensions.TrxReport from 2.4.0 to 2.4.1.

Release notes

Sourced from Microsoft.Testing.Extensions.TrxReport's releases.

No release notes found for this version range.

Commits viewable in compare view.

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore <dependency name> major version will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
  • @dependabot ignore <dependency name> minor version will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
  • @dependabot ignore <dependency name> will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
  • @dependabot unignore <dependency name> will remove all of the ignore conditions of the specified dependency
  • @dependabot unignore <dependency name> <ignore condition> will remove the ignore condition of the specified dependency and ignore conditions

Bumps Microsoft.NET.Test.Sdk from 18.10.0 to 18.10.1
Bumps Microsoft.Testing.Extensions.TrxReport from 2.4.0 to 2.4.1

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nuget-dependencies
- dependency-name: Microsoft.Testing.Extensions.TrxReport
  dependency-version: 2.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nuget-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Dependency updates .NET Pull requests that update .NET code labels Sep 18, 2026
@dependabot @github

dependabot Bot commented on behalf of github Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Looks like these dependencies are no longer updatable, so this is no longer needed.

@dependabot dependabot Bot closed this Sep 19, 2026
@dependabot
dependabot Bot deleted the dependabot/nuget/test/DemaConsulting.TemplateDotNetLibrary.Tests/nuget-dependencies-5f6f23b065 branch September 19, 2026 13:27
Malcolmnixon pushed a commit that referenced this pull request Sep 25, 2026
… review round 6)

- Gate the MaxProbeSegmentCount check (both GetInfo's ProbeDimensions
  and Load's Decode) so it is a no-op until the soft cap has already
  been crossed. Previously it counted from segment #1 unconditionally,
  incorrectly rejecting legitimate files under the 1 MiB soft cap that
  simply have many small metadata segments.
- Bound IncrementalProbeBuffer's pre-soft-cap chunk reads by the actual
  remaining bytes needed (target - Length) instead of the soft cap
  remainder, preventing GetInfo from reading a full 4096-byte chunk -
  and potentially into entropy-coded scan data - when only a few bytes
  are needed to reach the next marker or the SOF segment boundary.
- Update the four JPEG segment-count regression tests to build leading
  filler that crosses the soft cap before exercising the segment-count
  cap, matching the corrected behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Malcolmnixon added a commit that referenced this pull request Sep 25, 2026
…JPEG, and harden SvgCodec XXE settings explicitly (#17)

* Add Surface.Clear/Canvas.Clear, fix GetInfo/Load parity for TIFF and JPEG, and harden SvgCodec XXE settings explicitly

- Surface.Clear(Rgba32): vectorized broadcast-fill bulk pixel operation, matching the
  existing PremultiplyAlpha/CompositeOver row-buffer pattern. Canvas.Clear(Rgba32) added
  as a thin, transform-independent passthrough.
- TiffCodec.GetInfo: buffers non-seekable streams via the same ReadAllBytes/ByteArrayTiffDataSource
  path Load already uses, instead of throwing NotSupportedException.
- JpegCodec.GetInfo: MaxProbeHeaderBytes becomes a soft cap (internal const) - once reached
  without finding SOF0/SOF2, falls back to a one-time bulk read of the stream remainder and
  continues scanning, matching Load's own unbounded buffering, instead of throwing.
- ImageInfo.cs and both codecs' GetInfo XML docs now state the invariant: GetInfo never
  throws for an input that Load would successfully decode.
- SvgCodec.cs: explicit DtdProcessing.Prohibit / XmlResolver = null on both XmlReaderSettings
  instances (already-safe .NET defaults, made explicit for defense-in-depth clarity; no
  behavior change).
- Full companion artifacts: reqstream requirements (unit/subsystem/system rollups), design
  docs, verification docs, user guide updates, unit tests, and 3 new
  CanvasNet_SystemIntegration_* system-level regression tests.

* Fix JPEG GetInfo soft-cap fallback and TIFF non-seekable parity test (PR #17 review round 1)

- JpegCodec: IncrementalProbeBuffer no longer bulk-drains the stream to EOF
  once MaxProbeHeaderBytes is crossed without finding SOF0/SOF2. It now
  continues scanning segment-by-segment past the soft cap, bounded to
  exactly what each request needs, so GetInfo stops the instant SOF0/SOF2
  is found and never reads entropy-coded scan data, even for streams with
  unusually large leading metadata or streams that never reach EOF.
- Added JpegCodec_GetInfo_SofShortlyAfterProbeCap_StopsAtSofWithoutDrainingStream
  and a new InfiniteTailStream test double, proving the fix (and failing
  against the old bulk-drain behavior).
- TiffCodecTests: fixed TiffCodec_GetInfo_NonSeekableStream_SucceedsAndMatchesLoadResult
  to genuinely verify GetInfo/Load parity by comparing against
  TiffCodec.Load's decoded Surface dimensions, instead of comparing two
  GetInfo calls against each other.
- Updated companion design/requirements/verification/user-guide docs and
  .reviewmark.yaml to keep them consistent with the corrected behavior.

* Bound JPEG GetInfo post-soft-cap scan with an explicit hard limit (PR #17 review round 2)

Fixes a High severity finding: after the previous review round made
JpegCodec.GetInfo's post-soft-cap (MaxProbeHeaderBytes, 1 MiB) fallback
scan continue segment-by-segment instead of bulk-draining the stream,
that scan had no upper bound at all besides genuine end-of-stream -
reintroducing a denial-of-service vector for a malformed/no-SOF JPEG or
a slow/infinite stream.

Adds a new internal const MaxProbeHeaderBytesHardLimit (16 MiB, 16x the
soft cap): IncrementalProbeBuffer.TryEnsureLength now refuses outright
once a request would require buffering more than that hard ceiling,
and ProbeDimensions/GetInfo throws InvalidDataException once it is
reached without a SOF0/SOF2 marker ever being found, rather than
reading or buffering data without bound. The soft cap's existing
continue-past-the-cap behavior for legitimately large leading metadata
(e.g. large ICC/XMP blocks) is preserved unchanged below the new hard
limit.

Adds a regression test reusing the existing InfiniteTailStream/
BoundedReadStream test doubles to prove the hard limit is enforced
(and completes quickly rather than hanging or reading unboundedly),
and verifies the existing SofShortlyAfterProbeCap regression test is
unaffected. Updates the companion reqstream requirements, design, and
verification documentation, and the user guide, to describe the new
two-tier soft-cap/hard-limit design.

* Add segment-count cap as defense-in-depth for JPEG GetInfo probe (PR #17 round 3)

Adds JpegCodec.MaxProbeSegmentCount (512) as a second, independent
resource ceiling on ProbeDimensions' post-soft-cap fallback scan,
alongside the existing byte-based MaxProbeHeaderBytesHardLimit
(16 MiB). A JPEG marker segment can be as small as 4 bytes, so the
byte-based hard limit alone would not cheaply bound the number of
scan iterations for a malformed stream built from many minimal-size
segments (up to ~4 million iterations before the byte cap trips).
The new segment-count cap bounds iterations directly.

The check is applied only to non-terminating segments (stray restart
markers and the general APPn/COM/DQT/DHT/etc. fallback path) and
never to the terminating SOF0/SOF2 marker segment itself, so a
well-formed file whose SOF marker happens to land exactly on what
would otherwise be the limit-exceeding segment still probes
successfully - preserving the 'GetInfo never throws for an input
Load would successfully decode' invariant.

Adds two regression tests:
- JpegCodec_GetInfo_ManyMinimalSegmentsNoSof_StopsAtSegmentCountLimitBeforeByteLimit
  proves the segment-count cap (not the byte cap) catches a
  many-minimal-segment attack shape well before the byte-based
  hard limit could ever be approached.
- JpegCodec_GetInfo_SofAsSegmentImmediatelyAfterSegmentCountLimit_StillSucceeds
  proves the segment-count cap never rejects a well-formed file
  whose SOF marker lands exactly on the boundary segment.

Updates companion documentation (reqstream requirements, design doc,
verification doc test counts/narrative, user guide) to describe the
new cap and both new tests. Both local code-review and formal-review
passed (an initial formal-review round caught and this commit fixes
a High-severity off-by-one boundary defect in the first draft of the
segment-count check).

All 3,819 tests pass (net8.0/net9.0/net10.0).

* Narrow GetInfo/Load parity invariant for JPEG hard ceilings; add Canvas.Clear system test (PR #17 review round 4)

Responds to 4 new cloud-review findings on PR #17:

Finding A (High): JpegCodec.GetInfo's MaxProbeHeaderBytesHardLimit (16 MiB) and
MaxProbeSegmentCount (512) ceilings break the documented "GetInfo never throws for an
input Load would successfully decode" parity invariant, since Load enforces no
equivalent ceiling (it buffers the entire stream unconditionally via ReadAllBytes).
Per an explicit prior team decision NOT to add a matching cap to Load (out of scope -
a separate, previously deferred concern about Load's own unbounded-size read), this
is resolved by narrowing and explicitly documenting the invariant instead:

- ImageInfo.cs: added a new remarks paragraph documenting this as a deliberate,
  narrow, explicitly-accepted exception to the general "GetInfo never throws for an
  input Load would accept" invariant, naming both ceilings.
- JpegCodec.cs: added a matching <remarks> block on GetInfo(Stream) stating the same
  narrowed invariant precisely, worded as an intentional design decision.
- docs/reqstream/canvas-net.yaml: updated the root CanvasNet-Lib-JpegGetInfo
  requirement's title/justification, which previously (inaccurately) said GetInfo
  throws "only when the stream genuinely ends" - now describes both hard ceilings as
  a documented, narrow parity exception. Added the new proof test to its tests: list.
- New regression test JpegCodec_GetInfoVsLoad_LeadingMetadataExceedsHardLimit_IsAcceptedParityException
  (JpegCodecTests.cs): builds leading APP0 filler segments comfortably exceeding
  MaxProbeHeaderBytesHardLimit before a genuinely valid, decodable SOF0/SOS/entropy
  tail, and asserts Load succeeds while GetInfo on the identical bytes throws
  InvalidDataException referencing the hard limit - proving the divergence is real,
  intentional, and narrowly scoped, not a regression.
- Updated companion design doc (new architectural-decision subsection), verification
  doc (new test narrative + tests: list entry), and user guide prose to match.

Finding B: docs/reqstream/canvas-net.yaml's root CanvasNet-Lib-CanvasClear
requirement's only system-level test evidence exercised Surface.Clear directly;
Canvas.Clear (the Rendering.Canvas -> Surface passthrough) was only exercised by
Rendering unit tests, so a regression in the passthrough could leave the root
requirement green. Added a new system integration test
CanvasNet_SystemIntegration_CanvasClearThenReadWrappedSurfacePixel_ReturnsExpectedColor
(CanvasNetTests.cs) that constructs a Rendering.Canvas wrapping a Surface, calls
Canvas.Clear, and reads pixels back directly from the wrapped Surface reference (not
just via Canvas) to prove the passthrough genuinely reaches the underlying buffer.
Added this test to the CanvasNet-Lib-CanvasClear tests: list alongside (not
replacing) the existing Surface.Clear-only evidence, and added a matching new
section to docs/verification/canvas-net.md (scenario count 23 -> 24).

Verification:
- Build: 0 warnings/errors across net8.0/net9.0/net10.0.
- Full test suite: 3825/3825 passed (3819 baseline + 2 new tests x 3 TFMs).
- pwsh ./fix.ps1 and pwsh ./lint.ps1: both clean.
- Local code-review: PASS.
- Local formal-review: PASS (0 Critical/High/Medium/Low blocking issues; 1
  informational-only stylistic note, not actionable).

* Add JPEG segment-count cap and extend hard limit to Load for true GetInfo/Load parity

- Add MaxProbeSegmentCount (512) as a defense-in-depth segment-count
  ceiling in GetInfo's post-SOI probe walk, independent of the existing
  byte-based MaxProbeHeaderBytesHardLimit (16 MiB). Bounds worst-case
  loop iterations directly, since a malformed stream of many minimal
  4-byte segments could otherwise take ~4M iterations before the byte
  cap trips.
- Reverse the previously-shipped "documented accepted exception" for
  Load vs GetInfo divergence on pathological >16 MiB leading-metadata
  JPEGs. Instead, extend both MaxProbeHeaderBytesHardLimit and the new
  MaxProbeSegmentCount to Load's own Decoder.Decode segment walk, so
  Load and GetInfo now throw InvalidDataException consistently - true,
  unconditional parity rather than a documented carve-out.
- Add CountsTowardSegmentLimit(marker) helper mirroring exactly which
  marker kinds ProbeDimensions' segment-count check counts (excludes
  SOF0/SOF2, SOS, and unsupported-SOF/frame markers), so Load and
  GetInfo never diverge on exception *message* at segment-count
  boundaries, not just exception type.
- Fix an ordering bug in Decode's ceiling checks: capture
  state.SofSeen *before* calling ProcessSegment (not the live
  post-segment value), since ProcessSegment sets SofSeen=true while
  processing the SOF segment itself - using the post-segment value
  wrongly exempted the terminating SOF segment's own bytes from the
  byte-limit check.
- Update ImageInfo.cs and JpegCodec.cs XML docs, reqstream files
  (root/subsystem/unit), design doc, verification doc, and user guide
  to describe true parity (no carve-out language).
- Add/update tests: JpegCodec_GetInfoVsLoad_LeadingMetadataExceedsHardLimit_BothThrowConsistently,
  JpegCodec_GetInfoVsLoad_LeadingSegmentCountExceedsLimit_BothThrowConsistently,
  JpegCodec_GetInfoVsLoad_SofSegmentStraddlesHardLimit_BothThrowConsistently,
  JpegCodec_GetInfoVsLoad_SosBeforeSofAtSegmentCountBoundary_BothThrowSosMessageNotSegmentLimit.

3834/3834 tests passing (net8.0/net9.0/net10.0). Local code-review and
formal-review both PASS.

* Fix JPEG segment-count guard gating and pre-cap chunk over-read (PR #17 review round 6)

- Gate the MaxProbeSegmentCount check (both GetInfo's ProbeDimensions
  and Load's Decode) so it is a no-op until the soft cap has already
  been crossed. Previously it counted from segment #1 unconditionally,
  incorrectly rejecting legitimate files under the 1 MiB soft cap that
  simply have many small metadata segments.
- Bound IncrementalProbeBuffer's pre-soft-cap chunk reads by the actual
  remaining bytes needed (target - Length) instead of the soft cap
  remainder, preventing GetInfo from reading a full 4096-byte chunk -
  and potentially into entropy-coded scan data - when only a few bytes
  are needed to reach the next marker or the SOF segment boundary.
- Update the four JPEG segment-count regression tests to build leading
  filler that crosses the soft cap before exercising the segment-count
  cap, matching the corrected behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix quadratic buffer growth in JPEG probe and correct SVG XXE verification doc overclaim (PR #17 review round 7)

- IncrementalProbeBuffer.EnsureCapacity now grows the backing array
  geometrically (doubling, capped at hardLimit) instead of resizing to
  the exact capacity needed on every post-soft-cap read step. Growing
  in ~200 fixed 80 KiB steps toward the 16 MiB hard limit previously
  copied the entire buffer on each resize; doubling makes the total
  copying amortized linear instead of quadratic.
- Corrected docs/verification/canvas-net/codecs/svg-codec.md's XXE
  hardening section, which overclaimed test coverage: no test exercises
  an external parameter entity (only a general entity is tested), and
  the tests demonstrate rejection of the DOCTYPE-bearing document, not
  (via a resolver spy or similar) that no external fetch is attempted -
  that guarantee rests on .NET's documented DtdProcessing.Prohibit
  behavior, not on anything the tests themselves observe.

3834/3834 tests pass across net8.0/net9.0/net10.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Malcolm Nixon <Malcolm.Nixon@hiarc.inc>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Malcolmnixon pushed a commit that referenced this pull request Sep 29, 2026
Supersede the earlier option (a) fix (a private, SVG-package-local
tangent helper) with option (b): a deliberate, documented, tested
PUBLIC core API, since a future PDF/PPTX/VSDX codec package will
plausibly need the same path-segment-tangent computation for its own
marker/arrowhead-style rendering.

- Add PathCommand.ComputeTangents(start) (public instance method) to
  DemaConsulting.CanvasNet, computing a command's outgoing/incoming
  unit tangent directions for LineTo/QuadraticBezierTo/CubicBezierTo,
  returning (null, null) for ArcTo/Close.
- Make PathCommand.LineTo(end) public (previously internal), so an
  external consumer can synthesize a standalone line-segment command
  (needed to represent an implicit closing edge, which is never
  itself recorded as its own PathCommand).
- Update SvgCodec.Markers.cs to call command.ComputeTangents(current)
  and PathCommand.LineTo(subpath.Start).ComputeTangents(...) instead
  of its own private duplicate of this logic.
- Add PathCommandTests.cs (9 tests) to DemaConsulting.CanvasNet.Tests
  covering LineTo and ComputeTangents for every PathCommandType.
- Add CanvasNet-Geometry-PathCommand-PublicLineToFactory and
  CanvasNet-Geometry-PathCommand-ComputeTangents requirements to
  docs/reqstream/canvas-net/geometry/path.yaml, and corresponding
  design/verification coverage in
  docs/design/canvas-net/geometry/path.md and
  docs/verification/canvas-net/geometry/path.md.
- Add the new test file to the CanvasNet-Geometry-Path review-set in
  .reviewmark.yaml.

The cross-package InternalsVisibleTo grant remains removed, as
before; SvgCodec.Markers.cs no longer needs it because it now calls
only PathCommand's public API.
Malcolmnixon added a commit that referenced this pull request Sep 29, 2026
* Split SVG codec into DemaConsulting.CanvasNet.Svg package

Extracts the SVG rasterization codec into its own NuGet package and
software system (CanvasNetSvg), separate from the core CanvasNet
package/system, per software-items.md. Moves source, tests, and
fixtures into new src/DemaConsulting.CanvasNet.Svg and
test/DemaConsulting.CanvasNet.Svg.Tests projects, renames the SVG
codec namespace to DemaConsulting.CanvasNet.Svg, and promotes SVG's
requirements/design/verification/SysML2 model to top-level
canvas-net-svg artifacts parallel to canvas-net.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove unsafe git-stash workaround from lint.ps1 dotnet format check

Replace the stash-push/format/diff/checkout/stash-pop dance (added in
739b55d to work around dotnet/sdk#41422) with a simple non-mutating
dry-run check. The stash-based approach snapshots the full worktree
but only reverts a pathspec-scoped subset, which corrupts the repo
when combined with bulk git-mv renames (as encountered while
splitting the SVG codec into its own package). dotnet/sdk#41422 is
confirmed still open and macOS-specific; its diagnostic text
("failed to verify formatting") is printed reliably regardless of
the exit-code bug, so checking exit code OR that text is a safe
fallback that never touches git state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix 9 Copilot PR review findings on PR #30 (SVG package split)

1. Remove cross-package InternalsVisibleTo from CanvasNet to
   CanvasNet.Svg; SvgCodec.Markers.cs now computes the closing-edge
   tangent directly via NormalizeOrNull instead of calling internal
   PathCommand.LineTo.
2. Add canvas-net-svg.md and canvas-net-svg/svg-codec.md to
   docs/design/definition.yaml's input-files.
3. Add docs/reqstream/canvas-net-svg/platform-requirements.yaml
   (mirrors canvas-net/platform-requirements.yaml), wire it into
   requirements.yaml and the CanvasNetSvg-Design review-set in
   .reviewmark.yaml.
4. Add 16 new CanvasNetSvg_SystemIntegration_* system-level tests to
   SvgSystemIntegrationTests.cs covering every top-level CanvasNetSvg
   requirement that previously had only unit-test (SvgCodec_*)
   evidence, and link them in docs/reqstream/canvas-net-svg.yaml.
5. Add canvas-net-svg.md and canvas-net-svg/svg-codec.md to
   docs/verification/definition.yaml's input-files, and mention
   CanvasNetSvg coverage in docs/verification/introduction.md's scope.
6. Rewrite the design doc's Risk Control Measures section to
   separate tolerantly-skipped no-effect constructs from reference
   cycles and resource-disproportionate constructs, both of which are
   rejected via a thrown InvalidDataException.
7. Correct docs/sysml2/model/canvas-net-svg.sysml wording: CanvasNet
   and CanvasNetSvg share a git repository but are separate NuGet
   packages.
8. Reword the "no I/O" claim in docs/verification/canvas-net-svg.md
   to distinguish no network/service I/O from supported local
   file-path I/O via Load(string)/GetInfo(string), and add/rename
   verification test-scenario descriptions to match the current
   public test suite.
9. Rename the two CanvasNet_SystemIntegration_* tests in
   SvgSystemIntegrationTests.cs to the CanvasNetSvg_ prefix, and
   update every reqstream/verification cross-reference to the old
   names.

* Revise finding #1 fix: expose public PathCommand.ComputeTangents API

Supersede the earlier option (a) fix (a private, SVG-package-local
tangent helper) with option (b): a deliberate, documented, tested
PUBLIC core API, since a future PDF/PPTX/VSDX codec package will
plausibly need the same path-segment-tangent computation for its own
marker/arrowhead-style rendering.

- Add PathCommand.ComputeTangents(start) (public instance method) to
  DemaConsulting.CanvasNet, computing a command's outgoing/incoming
  unit tangent directions for LineTo/QuadraticBezierTo/CubicBezierTo,
  returning (null, null) for ArcTo/Close.
- Make PathCommand.LineTo(end) public (previously internal), so an
  external consumer can synthesize a standalone line-segment command
  (needed to represent an implicit closing edge, which is never
  itself recorded as its own PathCommand).
- Update SvgCodec.Markers.cs to call command.ComputeTangents(current)
  and PathCommand.LineTo(subpath.Start).ComputeTangents(...) instead
  of its own private duplicate of this logic.
- Add PathCommandTests.cs (9 tests) to DemaConsulting.CanvasNet.Tests
  covering LineTo and ComputeTangents for every PathCommandType.
- Add CanvasNet-Geometry-PathCommand-PublicLineToFactory and
  CanvasNet-Geometry-PathCommand-ComputeTangents requirements to
  docs/reqstream/canvas-net/geometry/path.yaml, and corresponding
  design/verification coverage in
  docs/design/canvas-net/geometry/path.md and
  docs/verification/canvas-net/geometry/path.md.
- Add the new test file to the CanvasNet-Geometry-Path review-set in
  .reviewmark.yaml.

The cross-package InternalsVisibleTo grant remains removed, as
before; SvgCodec.Markers.cs no longer needs it because it now calls
only PathCommand's public API.

* Fix orphaned requirements from PathCommand public API addition

The CI "Build Documents" job failed reqstream --enforce with 2
orphaned requirements: CanvasNet-Geometry-PathCommand-PublicLineToFactory
and CanvasNet-Geometry-PathCommand-ComputeTangents. These leaf
requirements were added to docs/reqstream/canvas-net/geometry/path.yaml
but never linked into the System -> Unit -> leaf decomposition chain
(reqstream requires exact-id children references at every tier, not
prefix matching).

Add both ids to the existing CanvasNet-Geometry-Path unit-level
requirement's children list in docs/reqstream/canvas-net/geometry.yaml,
and update its title/justification to describe the new public
LineTo/ComputeTangents surface. Also updates the corresponding
system-level CanvasNet-Lib-GeometryPath title/justification in
docs/reqstream/canvas-net.yaml for consistency (children unchanged,
since CanvasNet-Geometry-Path already covers the new leaves).

Verified locally: dotnet reqstream --enforce no longer reports any
orphaned requirements; build.ps1 and lint.ps1 both pass cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix second Copilot review pass findings on PR #30

- lint.ps1: fix macOS dotnet-format fallback regex to match the actual
  MSBuild-style ': error <CODE>:' diagnostic text instead of a
  nonexistent 'failed to verify formatting' phrase
- PathCommand.NormalizeOrNull: compute squared length in double
  precision to avoid spurious float-squaring overflow for large-but-
  finite vectors (~1.8e19+ magnitude), which previously caused valid
  directions to be misreported as degenerate; add a covering unit test
- SvgSystemIntegrationTests: strengthen the SvgFilterRendering system
  test to assert a halo pixel outside the source rect (matching the
  unit-level assertion), proving the filter chain actually ran
- docs/reqstream/canvas-net-svg.yaml: reword the SvgFilterRendering
  parent requirement to reflect the true split behavior - oversized
  filter regions/blur radii are tolerated (skipped/clamped) while
  excessive convolution-order/morphology-radius/turbulence-octave
  parameters are rejected with InvalidDataException, matching the
  FilterPrimitiveResourceSafety child requirement and its tests
- docs/reqstream/canvas-net-svg.yaml and
  docs/reqstream/canvas-net-svg/svg-codec.yaml: replace out-of-scope
  construct examples that contradicted this same file's own supported-
  feature requirements (filters, masks, style, clipPath, pattern,
  image) with the actual unsupported constructs per the design doc
  (nested svg, SMIL animation elements, foreignObject)
- SvgSystemIntegrationTests.cs: fix a duplicated '///' XML-doc marker
  embedded in a summary comment's text
- cspell: add SMIL to canvas-net-svg.yaml's ignore list
- docs/reqstream/canvas-net.yaml: CanvasNet-Lib-GeometryPath's title
  promises the public PathCommand.LineTo factory and ComputeTangents
  behavior, but its only linked system test never exercised either
  API; add CanvasNet_SystemIntegration_LineToAndComputeTangents_
  ReturnsNormalizedDirection to test/DemaConsulting.CanvasNet.Tests/
  CanvasNetTests.cs exercising both through the public API, and link
  it on the requirement alongside the existing test

* fix: widen PathCommand tangent delta computation to double before subtraction

NormalizeOrNull previously accepted a pre-subtracted Vector2, so the
point-to-point delta (e.g. EndPoint - start) still overflowed in float
for finite-but-extreme coordinates (near +/-float.MaxValue) before any
double-precision widening could help. NormalizeOrNull now accepts the
two endpoints directly and computes the delta in double, fixing
ComputeTangents for such inputs.

Also replaced exact-float-equality assertions in PathCommandTests with
a tolerance-based AssertDirectionApproximatelyEqual helper, since the
production code's double-widened computation and the tests' pure-float
Vector2.Normalize(...) reference values can legitimately differ by a
single ULP.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: reject only zero-length tangent deltas and model SVG's subsystem uses as dependencies

- NormalizeOrNull was comparing the double-precision squared length
  against float.Epsilon, which rejected valid tiny-but-distinct
  segments (e.g. length ~1e-30) even though the public contract
  promises a unit tangent for every nonzero finite direction. Now
  rejects only an exactly zero squared length.
- Added the two large-coordinate ComputeTangents tests to the
  CanvasNet-Geometry-PathCommand-ComputeTangents verification scenario,
  which previously omitted them despite listing them as evidence.
- Modeled CanvasNetSvgSystem's use of the core CanvasNet system's
  Canvas/Geometry/Drawing/Fonts subsystems as non-owning dependency
  relationships instead of ordinary part usages, since the latter
  incorrectly modeled them as additional owned children of
  CanvasNetSvgSystem (duplicating core subsystems in architecture
  queries/diagrams) rather than as a dependency on the sibling system.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: fix stale design contracts and add missing Codecs dependency

- Documented Surface's full valid width/height range (non-positive AND
  exceeding Surface.MaxDimension (8192) both throw
  ArgumentOutOfRangeException) in canvas-net-svg.md's API description,
  interface constraints table, and data-flow validation step.
- Updated the Geometry Path unit's caller description to name the
  in-repository Drawing.PathFiller/PathStroker callers instead of
  claiming no rasterizer exists, while retaining the external SVG
  marker caller.
- Added the missing Codecs subsystem dependency to the
  CanvasNetSvgSystem sysml2 model (SvgCodec.GetInfo returns the shared
  Codecs.ImageInfo record struct).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: list Codecs dependency for CanvasNetSvg and fix stale factory-method comment

- Added the Codecs subsystem to introduction.md's three CanvasNetSvg
  dependency mentions and to canvas-net.sysml's reverse-dependency
  comment, matching the sysml2 model's usesCodecs dependency and
  SvgCodec.GetInfo's actual use of Codecs.ImageInfo.
- Added the missing large-finite-coordinate LineTo test name to the
  ComputeTangents verification scenario.
- Corrected PathCommand's private constructor doc comment, which still
  said only "internal factory methods" construct instances even
  though LineTo is now a public factory method.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Malcolm Nixon <Malcolm.Nixon@hiarc.inc>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Malcolmnixon added a commit that referenced this pull request Oct 8, 2026
* Add CanvasNetVsdx design, requirements, and SysML2 skeleton

Lays the documentation groundwork for a new DemaConsulting.CanvasNet.Vsdx
NuGet package providing Microsoft Visio (.vsdx) diagram-rendering support,
following the software-items.md standard:

- docs/design/canvas-net-vsdx.md + canvas-net-vsdx/vsdx-document.md: system
  and unit design docs, describing the complete final feature set (shipped
  as a single PR) with an ordered 9-milestone internal implementation plan.
- docs/sysml2/model/canvas-net-vsdx.sysml + subfolder: SysML2 architecture
  model, wired into docs/sysml2/views/design-views.sysml.
- docs/reqstream/canvas-net-vsdx.yaml + subfolder: requirement entries
  (CanvasNetVsdx-* / CanvasNetVsdx-VsdxDocument-* IDs) covering package
  parsing, geometry/transform, master/stencil inheritance, style
  resolution, text, connectors, color/theme, and the public Render API;
  wired into root requirements.yaml.
- docs/verification/canvas-net-vsdx.md + subfolder: TODO verification
  stubs, to be completed once the test suite exists.
- docs/design/introduction.md and .reviewmark.yaml updated to reference
  the new system.

A research pass (not committed - see session artifacts) grounded this
design in real sample .vsdx files from the permissively-licensed
dave-howard/vsdx (BSD-3-Clause) and jgreywolf/vsdx-js (MIT) repositories;
14 of those sample files are staged for the later test-suite milestone at
test/DemaConsulting.CanvasNet.Vsdx.Tests.Fixtures-STAGING/.

No C# source/project files yet - implementation follows in subsequent
local commits per the phase plan in docs/design/canvas-net-vsdx.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add CanvasNetVsdx project scaffolding (Milestone 1)

Creates the empty, compiling project skeletons for the new
DemaConsulting.CanvasNet.Vsdx system, per Milestone 1 ("Project
scaffolding") of docs/design/canvas-net-vsdx.md's Implementation
Phase Plan.

- src/DemaConsulting.CanvasNet.Vsdx/DemaConsulting.CanvasNet.Vsdx.csproj:
  new library project mirroring the sibling Pptx/Pdf/Charts csproj
  conventions (net8.0/net9.0/net10.0, analyzers, SBOM, ApiMark,
  SourceLink), referencing only the core DemaConsulting.CanvasNet
  project (no Charts/Svg/Pdf dependency, per the design doc). Uses the
  newest package versions currently in use anywhere in the repo
  (DemaConsulting.ApiMark.MSBuild 0.5.4, SonarAnalyzer.CSharp
  10.35.0.4138, Microsoft.CodeAnalysis.NetAnalyzers 10.0.401,
  Microsoft.Sbom.Targets 4.1.13, Microsoft.SourceLink.GitHub
  10.0.401).
- src/DemaConsulting.CanvasNet.Vsdx/NamespaceDoc.cs: forward-looking
  namespace XML doc describing the final VsdxDocument feature set
  (OPC package layer, page/master/style model, geometry+transform,
  master/stencil inheritance, text, connector/glue-point routing,
  color/theme resolution, public Render API), with a remarks note
  that implementation is in progress and detailed scope will be
  filled in on VsdxDocument as milestones land.
- src/DemaConsulting.CanvasNet.Vsdx/VsdxDocument.cs: an empty, public
  sealed partial class VsdxDocument with only a one-line summary -
  partial so later milestones can add VsdxDocument.*.cs files
  (mirroring PptxDocument's own partial-class-per-concern
  convention) without touching this file.
- test/DemaConsulting.CanvasNet.Vsdx.Tests/
  DemaConsulting.CanvasNet.Vsdx.Tests.csproj: new xunit v3 test
  project mirroring the sibling *.Tests.csproj conventions,
  referencing only the new Vsdx project. The shared TestSupport
  synthetic-font-helper link is intentionally omitted - this
  milestone has no text-rendering code yet; a later milestone can
  add it.
- test/DemaConsulting.CanvasNet.Vsdx.Tests/VsdxDocumentTests.cs: one
  trivial smoke test confirming VsdxDocument exists and is
  referenceable.
- CanvasNet.slnx: wires both new projects into the existing /src/
  and /test/ solution folders, positioned immediately after their
  Pptx counterparts (matching the existing, non-alphabetical but
  consistent ordering).

Deferred to later milestones per the phase plan: OPC package
parsing/Open(...), page enumeration, shape/geometry model,
master/stencil inheritance, style resolution, text rendering,
connector/glue-point routing, theme/color resolution, the public
Render API, and promoting
test/DemaConsulting.CanvasNet.Vsdx.Tests.Fixtures-STAGING/ into this
test project (left untouched here).

Verified: dotnet build CanvasNet.slnx (0 warnings, 0 errors across
all three target frameworks); dotnet test on the new
DemaConsulting.CanvasNet.Vsdx.Tests project (3/3 passed - net8.0,
net9.0, net10.0); fix.ps1 run (no changes needed beyond what was
already formatted); dotnet format --verify-no-changes clean. lint.ps1
still reports pre-existing, out-of-scope cspell findings in
docs/design/introduction.md, docs/design/canvas-net-vsdx.md, and
test/DemaConsulting.CanvasNet.Vsdx.Tests.Fixtures-STAGING/ from the
prior commit; none are introduced by this change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix cspell gaps in VSDX design/fixture docs

Adds missing technical terms (Visio, slnx, jgreywolfvsdxjs, Greywolf,
Lucidchart, basicshapes, diagramwithstyles, flowchartshapes) to existing
cspell:ignore comments, surfaced by lint.ps1 after the design-doc and
fixture-staging commits. Full lint.ps1 now passes clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 2: OPC package layer + page enumeration

Implements the OPC/ZIP package layer and page index resolution for
DemaConsulting.CanvasNet.Vsdx, mirroring the proven PptxDocument precedent:

- VsdxDocument.Open(Stream) / Open(string path): opens a .vsdx as a
  read-only ZIP archive with bounded buffering (MaxPackageBytes).
- VsdxDocument.Package.cs: generic OPC mechanics - [Content_Types].xml
  parsing, relationship parsing/caching (_rels/.rels and part-level
  .rels), "../"-relative target resolution, ResolveRelationshipByType /
  TryResolveRelationshipByType, XXE-hardened XML loading.
- VsdxDocument.Pages.cs: resolves visio/document.xml (required) and
  visio/pages/pages.xml (required) via relationship type; resolves
  visio/masters/masters.xml and visio/theme/theme1.xml if present but
  does not require them (confirmed optional via real fixture evidence
  and the design doc's own exception contract); parses each <Page>'s
  Name/NameU, PageWidth/PageHeight cells (inches -> EMU, ignoring any
  U= formatting-hint attribute).
- VsdxPageInfo: readonly record struct (Name, WidthEmu, HeightEmu)
  mirroring PptxSlideSize's shape/unit convention.
- PageCount property and GetPageSize(int) method exposed on
  VsdxDocument, with idempotent Dispose().
- Exception contract mirrors Pptx exactly: ArgumentNullException/
  ArgumentException for null/empty stream or path; InvalidDataException
  for corrupt ZIP or a missing/empty required part. No new custom
  exception type was needed.
- Full Milestone-2 test suite in VsdxDocumentTests.cs covering all
  forward-declared reqstream test names, the full negative/validation
  matrix, and real-fixture sanity checks (including a loop over all 14
  staged sample files from the Fixtures-STAGING folder, wired into the
  test project via a Link-based fixture include).

Deferred to later milestones (3+): shape/geometry parsing, masters/
styles/theme content, text, connectors, and rendering. Page-content
parts (visio/pages/pageN.xml) are intentionally not resolved yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 3: Shape/geometry model + master/stencil inheritance + style resolution

Implements the internal per-shape resolution model for .vsdx diagrams:

- Shape tree parsing: generic Cell/Section cell-bag model for <Shapes>/<Shape>
  XML (VsdxCell, VsdxCellBag, VsdxShapeNode), forward-compatible with future
  group/nested-shape composition (Milestone 6).
- Master/Stencil resolution: visio/masters/masters.xml + per-master
  masterN.xml parsing, with cell and geometry-row override-merge (including
  the geometry-row-delete pattern) when a page shape references Master="N".
- StyleSheet chain resolution from visio/document.xml's <StyleSheets>,
  including built-in style IDs, with a shape's direct cell overrides taking
  final precedence.
- Geometry row parsing (MoveTo/LineTo/RelMoveTo/RelLineTo); other row types
  (ArcTo, RelCubBezTo, Ellipse, etc.) are skipped gracefully per the design
  doc's Risk Control Measures section, not thrown.
- 2-D and 1-D shape-local-to-page-coordinate affine transform, verified
  against the format reference's worked rotation example with exact
  expected-coordinate assertions.
- FillPattern 0/1 solid-or-none color/fill resolution with literal-RGB
  fallback; Themed colors get a tolerant neutral-gray fallback pending full
  theme/built-in-palette support.
- Reuses existing core Path/PathBuilder/Vector2/Rgba32 primitives from
  DemaConsulting.CanvasNet; no parallel geometry/paint types invented.

New files: VsdxCell.cs, VsdxCellBag.cs, VsdxGeometry.cs, VsdxShapeTransform.cs,
VsdxResolvedPaint.cs, VsdxColorPalette.cs, VsdxUnsupportedFeatureException.cs,
VsdxShapeNode.cs, VsdxDocument.Shapes.cs, VsdxDocument.Masters.cs,
VsdxDocument.CellMerge.cs, VsdxDocument.Geometry.cs, VsdxDocument.Transform.cs,
VsdxDocument.Styles.cs, VsdxDocument.Paint.cs, plus VsdxTestPackages.cs and
five new test classes (Geometry/Transform/MasterInheritance/
StyleResolution/FixtureShapeResolution).

Deferred to later milestones: text (M4), connectors/glue-points (M5),
recursive group/nested-shape composition (M6), Themed/full built-in color
palette (M6), public Render API / pixel painting onto Surface (M7).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 4: text parsing, TextStyle resolution, and layout

Implements Milestone 4 ("Text rendering") of the Vsdx 9-milestone plan:

- Parse each shape's <Text> element (character data interleaved with
  <cp IX="..."> / <pp IX="..."> run and paragraph markers) into an
  ordered sequence of raw text runs (VsdxDocument.Text.cs,
  VsdxTextRun.cs).
- Resolve character properties (font family/size/color/bold/italic) via
  Section N="Character" cells and paragraph properties (alignment,
  spacing) via Section N="Paragraph" cells, through the same TextStyle
  StyleSheet chain-walking pattern already used for LineStyle/FillStyle
  in Milestone 3 (instance -> Master -> Stencil -> built-in defaults),
  plus direct per-shape text-section overrides
  (VsdxDocument.TextStyle.cs, VsdxDocument.CellMerge.cs,
  VsdxDocument.Styles.cs).
- Text-box positioning: implicit default equal to the shape's own
  geometry bounding box in shape-local coordinates, with support for
  explicit TxtPinX/TxtPinY/TxtWidth/TxtHeight/TxtAngle override cells
  (VsdxDocument.TextBox.cs, VsdxTextBoxTransform.cs).
- Word-wrap/line-breaking and horizontal/vertical alignment within the
  resolved text box, mirroring the Pptx text-layout tokenize/pack/
  position pipeline, and glyph-ink path resolution via the shared
  TrueTypeFont/SystemFontCatalog primitives
  (VsdxDocument.TextLayout.cs, VsdxTextLayout.cs). A standalone glyph
  painter (VsdxDocument.TextRender.cs) is included but intentionally
  left unwired from any public API.
- VsdxShapeNode now carries resolved text runs, text-box transform, and
  text layout alongside the existing geometry/paint data.

Adds VsdxTextParsingTests, VsdxTextStyleTests,
VsdxTextBoxPositioningTests, VsdxTextLayoutTests, VsdxTextRenderTests,
and VsdxFixtureTextResolutionTests (real-fixture coverage), growing the
Vsdx test suite from 201 to 297 passing tests.

Deferred to later milestones: connectors/glue-points and group/nested-
shape recursive composition (Milestone 5/6), themes (Milestone 6), and
the public Render API / pixel painting to a Surface (Milestone 7).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 5: connector/glue-point routing

Implements connector endpoint resolution and arrowhead rendering support
for DemaConsulting.CanvasNet.Vsdx, per the 9-milestone VSDX plan.

- Parse <Connects> sections per page into VsdxConnect records (connector
  shape ID, endpoint, target shape ID, target cell, connection-point
  index), associating connector shapes with their glued targets for
  future use (e.g. hit-testing). Dangling/unresolved targets are
  tolerated, not thrown.
- Endpoint-resolution strategy CONFIRMED against real fixture evidence:
  trust the pre-baked BeginX/BeginY/EndX/EndY V= cell values already
  resolved by Milestone 3's 1-D transform path, rather than dynamically
  re-solving glue-point geometry. Verified across three real connector
  fixtures (davehoward-test4-connectors.vsdx, jgreywolfvsdxjs-connectors.vsdx,
  jgreywolfvsdxjs-diagramwithstyles.vsdx): every connector's Begin/End
  cells carry a literal numeric V even when F= is a formula such as
  _WALKGLUE(...) or PAR(PNT(...)), matching the format reference's
  guidance that Visio bakes current glue coordinates at save time.
- Resolve BeginArrow/EndArrow/BeginArrowSize/EndArrowSize cells through
  the existing StyleSheet-chain-then-direct-override precedence
  (reusing the line-cell resolver), producing a VsdxArrowhead
  descriptor (style + size) per endpoint. Implemented a conservative
  style subset (None/OpenArrow/Arrow/Stealth/Circle/Diamond) with
  graceful degrade-to-None for any unrecognized or non-numeric index,
  never throwing. A real fixture (BeginArrow=13) exercises this
  degrade path directly.
- New resolved-connector model types: VsdxConnect, VsdxConnectEndpoint,
  VsdxConnectorEndpoints, VsdxArrowhead, VsdxArrowheadStyle. Wired into
  VsdxShapeNode (Connects, ConnectorEndpoints) and VsdxResolvedPaint
  (BeginArrowhead, EndArrowhead). Still no pixel painting to Surface.
- Added VsdxDocument.Connects.cs and VsdxDocument.Arrowheads.cs
  following the existing partial-class-per-concern convention.
- Tests: VsdxConnectsParsingTests, VsdxGluePointResolutionTests,
  VsdxArrowheadResolutionTests, VsdxConnectorFixtureTests (+20 tests;
  297 -> 357 aggregated across net8.0/net9.0/net10.0, all green).
- Updated docs/reqstream/canvas-net-vsdx/vsdx-document.yaml wording for
  ArrowheadRendering to reflect degrade-never-throw behavior.

Deferred to later milestones: group/nested-shape recursion and
theme/built-in-palette resolution (Milestone 6); public Render API and
pixel painting of resolved connectors/arrowheads to Surface
(Milestone 7).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 6: Theme color resolution and group/nested-shape transform composition

- Add VsdxTheme / VsdxDocument.Theme.cs: lazy, non-throwing parsing of visio/theme/themeN.xml
  color scheme (12 RGBA slots), with graceful fallback when no theme part is present.
- Wire THEMEVAL(slot)-style cell formulas into the existing color resolution pipeline
  (VsdxColorPalette.cs, VsdxDocument.Paint.cs, VsdxDocument.Styles.cs) alongside the
  existing explicit RGB and FillPattern 0/1 paths.
- Add VsdxDocument.Groups.cs: recursive <Shapes> group/nested-shape resolver that composes
  each child shape's local transform with its parent group's transform to arbitrary nesting
  depth, with a depth-budget and resolved-shape-count guard throwing InvalidDataException on
  pathological/cyclic structures.
- Extend VsdxShapeNode with a Parent reference to support the composed shape tree; rewire
  VsdxDocument.Shapes.cs to use the new recursive resolver.
- Add VsdxThemeResolutionTests.cs and VsdxGroupResolutionTests.cs, grounded in real fixtures
  (jgreywolfvsdxjs-* for theme parts, davehoward-test10-nested-shapes.vsdx and
  davehoward-test3-house.vsdx for group nesting / Master-driven group children), plus
  synthetic tests for the no-theme fallback, canonical theme-slot resolution, and the
  depth/count budget exception paths that are unreachable via any real fixture.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 7: Public Render API integration

Implement VsdxDocument.Render(...) public API: page-indexed rendering to
a Surface via width/height and DPI overloads, with VsdxRenderOptions for
background color control (default opaque white). Walk the resolved
shape tree (including nested groups from Milestone 6) in document
order, accumulating page-to-pixel coordinate transforms via the new
VsdxShapeTransform.ToPageMatrix(), and dispatch each shape to geometry
fill/stroke painting (Milestone 3), text layout painting via
PaintTextLayout (Milestone 4, now wired in for the first time), and
connector line + arrowhead painting (Milestone 5) using the new
VsdxArrowheadGeometry local-space arrowhead builder.

Add end-to-end rendering regression tests covering shape-only, text,
connector, and group fixtures, plus synthetic unit tests for argument
validation, background defaults, DPI consistency, document-order
z-ordering, lazy shape-tree caching, and arrowhead painting. Link test
coverage to the pre-registered reqstream requirement IDs for the public
render API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 8: promote Vsdx fixtures into test project

Move the 14 .vsdx fixtures, README.md, and the two LICENSE files out of
the test/DemaConsulting.CanvasNet.Vsdx.Tests.Fixtures-STAGING staging
folder into an in-project test/DemaConsulting.CanvasNet.Vsdx.Tests/VsdxFixtures
folder, mirroring the sibling Pptx package's PptxFixtures convention
exactly. Update the Vsdx test project's .csproj to use the same bare
<None Include="VsdxFixtures\**"> item form as Pptx, remove the
Staging Note section from the moved README.md, and update
.reviewmark.yaml's stale STAGING path reference on the
CanvasNetVsdx-VsdxDocument review-set. The STAGING folder is deleted
entirely now that all fixture-dependent tests pass from the new
location (confirmed: AppContext.BaseDirectory-based fixture resolution
required no code changes).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 8: backfill reqstream traceability, verification docs, and coverage gap tests

Reqstream traceability backfill (docs/reqstream/canvas-net-vsdx.yaml and
docs/reqstream/canvas-net-vsdx/vsdx-document.yaml): fix the drifted
tests: entries so every requirement ID references a real,
namespace-qualified, currently-passing test method name. Correct
requirement text for FillPatternDeferral (CanvasNetVsdx-ColorFill /
CanvasNetVsdx-VsdxDocument-FillPatternDeferral): the originally-planned
behavior (throw VsdxUnsupportedFeatureException on unsupported fill
patterns) was never implemented; the actual, already-approved behavior
is graceful degradation to solid fill, and the requirement text/title
now describe that - no new exception type was implemented (that would
be scope creep).

Two additional drift corrections discovered while writing this
milestone's own test coverage (documentation-only, no production code
changed, analogous to and consistent with the plan-authorized
FillPatternDeferral fix):
- GeometryUnrecognizedRowSkip: EllipticalArcTo/NURBSTo were claimed to
  convert to approximating Bezier path segments; they are actually
  skipped like any other unrecognized geometry row.
- ColorTableResolution: a document-level Colors/FaceNames table was
  claimed to be parsed and resolved against; no such element is parsed
  anywhere in VsdxDocument's source.
Both docs/design/canvas-net-vsdx.md and
docs/design/canvas-net-vsdx/vsdx-document.md are corrected to match,
each with a 'Correction note (Milestone 8)' annotation.

Verification documentation: replace the TODO stubs in
docs/verification/canvas-net-vsdx.md (system-level, 25 scenario
subsections) and docs/verification/canvas-net-vsdx/vsdx-document.md
(unit-level, 35 requirement-keyed subsections) with real test-strategy,
coverage-summary, and traceability content, mirroring the Pptx
sibling's structure and level of detail.

New test/DemaConsulting.CanvasNet.Vsdx.Tests/VsdxSystemIntegrationTests.cs:
25 test methods (24 Fact + 1 Theory over all 14 real fixtures), one per
system-level requirement, mirroring PptxSystemIntegrationTests.cs's
pattern, including a true end-to-end render-every-page-of-a-real-fixture
scenario.

Close the 5 concrete coverage gaps identified in the plan:
- Master-resolution caching (VsdxMasterInheritanceTests.cs)
- EllipticalArcTo/NURBSTo geometry rows skip tolerantly
  (VsdxGeometryTests.cs)
- Geometry-row replace-by-IX merge semantics
  (VsdxMasterInheritanceTests.cs)
- StyleSheet ID-0 ('No Style') chain termination
  (VsdxStyleResolutionTests.cs)
- Color-table resolution: built-in palette index and direct hex literal
  (new VsdxColorPaletteTests.cs)

Narrow, documented source change: VsdxDocument.Masters.cs's
ResolveMasterShape changed from private to internal (zero behavior
change) solely to allow the new caching-coverage test to call it
directly and assert the parsed master content tree is reused by
reference across repeated resolutions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 9: final local review and documentation reconciliation

- Removed stale STAGING fixture-path reference in the vsdx-document.sysml
  comment (pre-dated Milestone 1; test fixtures were promoted out of staging
  in Milestone 8).
- Reconciled docs/sysml2/model/canvas-net-vsdx.sysml and its vsdx-document
  subfolder descriptive text with the Milestone 8 design-doc corrections:
  no document-level <Colors>/<FaceNames> table is parsed (only the built-in
  palette, literal hex, and the Themed sentinel), EllipticalArcTo/NURBSTo/
  InfiniteLine rows are tolerantly skipped rather than resolved into Path
  segments, and no Drawing.Gradient type is referenced (every non-solid
  FillPattern degrades to solid fill).
- Refreshed the vsdx-document.sysml sourceRef/testRef comments from a stale
  pre-implementation planning-skeleton file list to the actual, current
  source/test file layout.
- Refreshed the CanvasNetVsdx-VsdxDocument .reviewmark.yaml path comments
  from '(planned; not yet created)' to match the completed implementation.
- Added a DemaConsulting.CanvasNet.Vsdx Features bullet and Installation
  section to README.md, mirroring the existing Svg/Pdf/Charts/Pptx entries
  (previously absent despite the package being fully implemented).

Full-repo validation: pwsh ./build.ps1 (11,127/11,127 tests passing across
net8.0/net9.0/net10.0) and pwsh ./lint.ps1 (markdownlint-cli2, cspell,
yamllint, dotnet format, reqstream, versionmark, reviewmark, sysml2tools)
both exit 0. dotnet reviewmark --enforce passes with no required-review
violations; the Vsdx-document review set's remaining coverage-warning gap
(*.Tests.csproj project file) is a pre-existing, repo-wide pattern shared
by every sibling test project, not a Vsdx-specific gap, and is left
untouched per scope discipline. An independent code-review pass over the
full src/DemaConsulting.CanvasNet.Vsdx and
test/DemaConsulting.CanvasNet.Vsdx.Tests diff (range 1b97b3e..HEAD) found
no defects: exception contracts, geometry/transform math, Master/StyleSheet
resolution, group-recursion budgets, resource disposal, thread-safety, and
public-API XML-doc coverage were all confirmed consistent with the
documented design.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 10: Bug #1 - exclude deleted shape stubs and suppress NonPrinting self-paint

Fixes two distinct root causes of spurious/duplicate text confirmed against
the apache/poi test-data corpus's real-world fixtures, verified visually
against Visio COM reference PNGs via the external smoke-test harness:

- VsdxDocument.Shapes.cs: ParseShapeElements now excludes a group child
  <Shape Del="1"> stub (shape-level deletion, distinct from the already-
  handled row-level geometry Del marker) from the resolved shape tree.
  Confirmed, via a controlled git-stash A/B pixel diff, to eliminate
  60973.vsdx's duplicate "OOB: N/A"/"[B] Lo1: N/A"/"TS: N/A" placeholder
  text.
- VsdxDocument.Render.cs: RenderShapeRecursive now skips a shape's own
  self-paint (fill/stroke/text/arrowheads) when its effective NonPrinting
  cell resolves true, while still recursing into its children. Confirmed
  to eliminate 44501b.vsdx's spurious "Activity" heading.

Adds VsdxDocument_GroupChildDeletedStub_ExcludedFromResolvedShapeTree to
VsdxGroupResolutionTests.cs covering the Del-stub exclusion with a small
synthetic fixture (the NonPrinting-suppression test rides along with
Bug #2's commit in VsdxRenderFixtureTests.cs, since both new tests were
appended as one contiguous diff hunk).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 10: Bug #2 - visible hairline stroke floor plus transform-cell merge fix for missing connector lines

Fixes 60973.vsdx's missing connector-line defect, confirmed via the
external smoke-test harness against the Visio COM reference PNG. This
required two complementary changes beyond the plan report's original
stroke-width-only diagnosis, discovered via root-cause iteration after
the stroke-width floor alone did not resolve the visual discrepancy:

- VsdxDocument.Render.cs: PaintShapeGeometry's PaintStroke call now
  applies a MinStrokeWidthPixels = 1f floor to the resolved stroke width,
  so a sub-pixel LineWeight (confirmed ~0.5px at 150 DPI on this
  fixture's connectors) still rasterizes a visible hairline instead of
  vanishing.
- VsdxDocument.CellMerge.cs: discovered during deeper root-cause
  investigation that the generic Master-wins merge rule was also
  collapsing connector shape 802's resolved position to its Master's
  own small, unrelated template-local corner, because its own correct,
  baked PinX/PinY/LocPinX/LocPinY cells were marked F="Inh". Adds
  TransformCellNames/PreferInstanceTransformCells so an instance's own
  cell for the nine transform cells (PinX/PinY/Width/Height/LocPinX/
  LocPinY/Angle/FlipX/FlipY) always wins over the Master's same-named
  cell, even when marked Inh - narrowly scoped to exactly these cells,
  since every other (genuinely shared) style cell keeps the original
  Master-wins-over-Inh rule.

Adds MasterInheritance_InstanceInhMarkedTransformCell_PreferredOverMasterValue
and MasterInheritance_InstanceInhMarkedNonTransformCell_StillDefersToMasterValue
to VsdxMasterInheritanceTests.cs, and Render_SubPixelLineWeight_StillPaintsVisibleHairlineStroke
plus Render_NonPrintingShape_SkipsOwnFillButStillRendersChildren (Bug #1(b)'s
own test, appended in the same diff hunk) to VsdxRenderFixtureTests.cs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 10: Bug #3 - honor FillForegndTrans/LineColorTrans transparency cells

Fixes 60973.vsdx's "Virtual Devices" container rendering fully opaque and
obscuring its own children, confirmed via the external smoke-test harness
against the Visio COM reference PNG.

VsdxDocument.Paint.cs's ResolvePaint now resolves FillForegndTrans/
LineColorTrans through the same FillStyle/LineStyle StyleSheet chain
already used for FillForegnd/LineColor, and a new ApplyTransparency
helper modulates the resolved FillColor/StrokeColor's alpha channel by
the parsed 0..1 transparency fraction. Confirmed via direct pixel
sampling: the container's resolved fill (127,127,127,153) blends to
(178,178,178) over white, matching the rasterizer's own alpha
compositing and the reference PNG's lighter, mostly-see-through frame.

Adds StyleResolution_FillForegndTrans_ReducesResolvedFillAlpha and
StyleResolution_LineColorTrans_ReducesResolvedStrokeAlpha to
VsdxStyleResolutionTests.cs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 10: companion documentation for the 3 rendering-fidelity fixes

Updates the companion design/requirements/verification artifacts for this
milestone's 3 code fixes plus one doc-only note, per the originating plan
report's companion-artifact table:

- docs/design/canvas-net-vsdx/vsdx-document.md: documents the shape-level
  Del="1" exclusion, the NonPrinting self-paint suppression, the stroke
  hairline floor, the *Trans alpha resolution, and the CellMerge.cs
  transform-cell-preference refinement discovered during this milestone's
  own Bug #2 root-cause investigation (a deviation beyond the plan
  report's original stroke-width-only diagnosis).
- docs/design/canvas-net-vsdx.md: records test.vsdx's header-bar/star as
  a confirmed real-world sample of the already-documented Themed
  neutral-fallback limitation - a documentation-only addition, no code
  change, per the plan report's own non-bug determination.
- docs/reqstream/canvas-net-vsdx/vsdx-document.yaml: adds requirement
  entries for deleted-shape exclusion, NonPrinting suppression, the
  minimum visible stroke width, transparency honoring, and extends the
  existing CellMerge requirement for the transform-cell exception.
- docs/verification/canvas-net-vsdx/vsdx-document.md: adds verification
  entries for all 5 new/extended behaviors, tracing to their new unit
  tests and to the external smoke-test visual-comparison evidence.
- .cspell.yaml: adds 'unconsulted', a genuine technical term (following
  this file's own existing un-prefixed-word precedent) needed by the new
  design/reqstream/verification prose and VsdxDocument.Paint.cs's own
  ApplyTransparency remarks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* vsdx milestone10 retry1: narrow CellMerge transform-cell exception to 1-D shapes (Finding #3)

Scope the PreferInstanceTransformCells exception in VsdxDocument.CellMerge.cs to
1-D (connector) shapes only, reusing the existing BeginX/EndX detection
convention from VsdxDocument.Groups.cs, rather than applying it to every
shape type including 2-D shapes where the Master's own value can be
legitimately inherited.

- Updated the existing 1-D test fixture
  (MasterInheritance_InstanceInhMarkedTransformCell_PreferredOverMasterValue)
  to include BeginX/BeginY/EndX/EndY cells so it still exercises the
  narrowed 1-D-only code path correctly.
- Added MasterInheritance_2DShapeInhMarkedTransformCell_DefersToMasterValue
  proving a legitimate 2-D Master-inheritance-of-position scenario still
  resolves correctly post-guard.
- Restored 2 assertions silently dropped from
  MasterInheritance_InstanceGeometryRowWithoutDel_ReplacesMasterRowAtMatchingIndex
  in a prior milestone 10 commit (Finding #4).
- Re-verified via smoke-test harness that 60973.vsdx's connector lines
  (Bug #2 fix) are unaffected by the narrowing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* vsdx milestone10 retry1: add full-transparency locking tests (Finding #1)

Independently re-verified quality's claimed github260.vsdx visual
regression (a LineColorTrans="1" shape allegedly losing a border shown by
the Visio reference) by pixel-scanning the actual Visio-reference PNG at
the shape's own bounding box: no border/stroke ring exists in the
reference at all - it is a pure, borderless fill. The current invisible
rendering of that shape is caused by an unrelated, already-documented
NURBS-skip geometry limitation (confirmed via raw XML inspection and via
'git show f9c46f9 --stat', which touched only Paint.cs), not by the Bug #3
transparency fix.

No code change was made to VsdxDocument.Paint.cs. Added
StyleResolution_LineColorTrans_FullyTransparent_HasNoVisibleStroke and
StyleResolution_FillForegndTrans_FullyTransparent_HasNoVisibleFill to lock
in the existing, re-verified-correct V="1" (fully transparent) behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* vsdx milestone10 retry1: correct documentation for Findings #1-#3 (docs)

- docs/design/canvas-net-vsdx/vsdx-document.md: document the 1-D-only
  scoping of the CellMerge transform-cell exception (Finding #3), add the
  Finding #1 re-verification note to the transparency resolution
  narrative, and clarify the Del="1" stub-exclusion fix addresses the
  stale-stub clutter text only, not the AIRttt/AIRuuu defect (Finding #2).
- docs/design/canvas-net-vsdx.md: cite github260.vsdx's circle as a second
  confirmed instance of the NURBS-skip limitation, and add a new, named
  deferred-limitation bullet documenting the AIRttt/AIRuuu defect as a
  distinct, pre-existing, out-of-scope issue (Finding #2).
- docs/reqstream/canvas-net-vsdx/vsdx-document.yaml: update the CellMerge
  entry's title/justification/tests for the 1-D-only scope, update the
  TransparencyResolution entry's justification/tests with the Finding #1
  re-verification evidence, and clarify the DeletedShapeExclusion entry's
  justification to distinguish the Del="1" fix from the AIRttt/AIRuuu
  defect.
- docs/verification/canvas-net-vsdx/vsdx-document.md: mirror the same
  scope corrections and new test-scenario names in the verification
  narrative for CellMerge, DeletedShapeExclusion, and
  TransparencyResolution.
- .cspell.yaml: add genuine proper nouns/fixture identifiers surfaced by
  the above documentation (AIRttt, AIRuuu, Lucidchart, lucidchart,
  borderless) so `lint.ps1` passes cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 11: cell-by-cell geometry-row merge (Group A: Findings #2, #3-frame contribution)

Fixes MergeGeometryRows (VsdxDocument.CellMerge.cs) to merge a matched instance/Master geometry
row cell-by-cell instead of whole-row replacement, so an instance row overriding only a subset
of its own cells still inherits the Master row's other, un-overridden cell values rather than
collapsing them to CLR defaults.

Adds MasterInheritance_GeometryRowPartialOverride_MergesCellByCellNotWholesale regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 11: EllipticalArcTo/ArcTo geometry conversion (Group B: Finding #1)

Adds EllipticalArcTo (general ellipse/arc) and ArcTo (circular, bow-height-derived) geometry-row
conversion to VsdxDocument.Geometry.cs's ResolveGeometry, producing an ArcTo path command that
reaches the row's own destination point instead of tolerantly skipping the row. A zero-bow ArcTo
row (A=0) degrades to a plain LineTo rather than throwing or producing a degenerate zero-radius
arc.

Adds Geometry_EllipticalArcToRow_ConvertsToArcReachingDestination,
Geometry_ArcToRow_ConvertsToArcWithBowDerivedRadius, and
Geometry_ArcToRow_ZeroBow_DegradesToLineTo, replacing the now-outdated
Geometry_EllipticalArcToAndNurbsToRows_AreSkippedWithoutThrowing test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 11: HideText suppression (Group C: Finding #8)

Adds HideText cell resolution (VsdxDocument.Paint.cs) and consults it in VsdxDocument.Render.cs
to skip only a shape's own text-run paint when HideText resolves truthy, leaving its own
fill/stroke/arrowheads and its children's painting unaffected - distinct from the broader
NonPrinting suppression which also skips fill/stroke.

Adds Render_ShapeWithHideTextCell_SuppressesOwnTextButNotFillOrStroke regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 11: fld element text parsing (Group D: Finding #3 label sub-symptom)

Adds a <fld> (Field reference) element case to BuildRawRuns (VsdxDocument.Text.cs) so a field's
own nested, save-time-cached text content is parsed as a literal run instead of being silently
dropped; a <fld> interleaved between plain text segments merges into the surrounding run exactly
like a plain XText node would.

Adds TextParsing_FldElement_NestedTextContentParsedAsLiteralRun and
TextParsing_FldElementInterleavedWithText_MergesIntoSurroundingRun regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 11: arrowhead index 4/254 mapping (Group E: Finding #5)

Maps confirmed-in-use arrowhead index 254 to a new hollow/unfilled-triangle VsdxArrowheadStyle
(VsdxArrowhead.cs, VsdxArrowheadGeometry.cs, VsdxDocument.Arrowheads.cs), found via a full-
document scan of the poi-fixtures corpus, and adds coverage for index 4 (existing solid-arrow
style family). Index 254 previously silently degraded to the unrecognized-index plain-line-end
fallback.

Adds ArrowheadResolution_EndArrowIndex4_ResolvesToArrowStyle and
ArrowheadResolution_EndArrowIndex254_ResolvesToHollowTriangleStyle regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Vsdx Milestone 11: companion documentation updates for 5 real-world fixes

Updates design docs (canvas-net-vsdx.md, canvas-net-vsdx/vsdx-document.md), reqstream
requirements (vsdx-document.yaml), verification entries (vsdx-document.md), and the sysml2
model (vsdx-document.sysml) to document:
- EllipticalArcTo/ArcTo geometry conversion (new ArcGeometryResolution requirement)
- cell-by-cell geometry-row merge correction (GeometryRowMerge)
- fld element text parsing (TextRunParsing)
- arrowhead index 4/254 mapping (ArrowheadRendering)
- HideText suppression (new HideTextSuppression requirement)

Also documents, as confirmed-but-deferred limitations (no code change):
- the shared 2-D shape transform-cell-merge-exclusion root cause behind Findings #3's frame/
  label and #6's residual text-overlap symptoms
- Finding #4's partial-fix nuance (triangle/bar shape fixed; unrelated pre-existing text-overlap
  position defect persists)
- Finding #5's arrowhead-size-proportional-to-stroke-width caveat as a pre-existing, unchanged
  design limitation
- a second confirmed real-world instance of the documented neutral-theme-fallback limitation
  (60973.vsdx's Nwwww/Neeeee rack-slot bars)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(vsdx): unconditionally overlay instance geometry-row cells over Master merge

Milestone 11 quality-retry (retry 1): fixes a confirmed geometry-row
cell-merge regression that fused/mis-sized ellipses in 60489.vsdx (Shape
114/122) and produced a floating, mispositioned, wrong-colored triangle in
44501e.vsdx (Shape 64, Master 28). Root cause: MergeGeometryRow applied the
generic "instance literal wins, else Master's cell" rule to geometry-row
coordinate cells, but these are almost always cached as F="Inh"
(formula-derived from Width/Height), so the generic rule treated them as
absent and substituted the Master's own differently-scaled cached value.

Fix: generalize PreferInstanceTransformCells to PreferInstanceCells(merged,
instanceCells, names), add an ArrowCellNames array (BeginArrow/EndArrow/
BeginArrowSize/EndArrowSize) applied through the existing 1-D-only gate
alongside TransformCellNames, and have MergeGeometryRow unconditionally
overlay the instance row's own present cells (literal or Inh) over the
generic merge result, preserving Master fallback only for cells genuinely
absent from the instance row.

While adding regression coverage for the ArrowCellNames extension, a new
test caught that PreferInstanceCells's original design (swapping which
VsdxCell wins, preserving its Formula) silently failed to affect arrowhead
resolution at all when overlaying an Inh-marked cell: ResolveLineCellValue/
ResolveFillCellValue (VsdxDocument.Paint.cs) use TryGetLiteral, which treats
any Inh-marked cell as "defer to the StyleSheet chain" rather than an
override. Fixed by writing the overlaid cell back as a literal
(Formula: null) - confirmed safe for TransformCellNames too, since its
consumer never inspects Formula.

Investigated the related arrowhead-rendering quality finding (44501e.vsdx
page 1) against the real fixture: the floating triangle is the same
geometry-row regression (fixed above); the separate "dash stub" artifacts
are a pre-existing, deliberate architecture boundary (arrowhead-decoration
painting is gated on 1-D ConnectorEndpoints; the dash-producing shapes are
ordinary 2-D Shape-typed icons) - an accepted, out-of-scope limitation, not
force-fixed.

Added regression tests:
- MasterInheritance_GeometryRowAllCellsInhButInstanceSizeDiffers_InstanceCoordinatesWin
- MasterInheritance_GeometryRowCellGenuinelyAbsentOnInstance_StillFallsThroughToMaster
- ArrowheadResolution_InstanceInhMarkedArrowCell_PreferredOverMasterValue
- ArrowheadResolution_2DShapeInhMarkedArrowCell_DefersToMasterValue

Verified via the external smoke-test harness: 60489.vsdx page 0 now renders
two distinct, correctly-sized ellipses; 44501e.vsdx page 1 no longer shows
the floating triangle and continues to render the EndArrow=254 hollow
triangle correctly; no regressions in 44501d.vsdx, 44501a.vsdx, 60973.vsdx
(pages 0-3), test.vsdx, and github260.vsdx.

dotnet build CanvasNet.slnx: 0 warnings, 0 errors.
Vsdx test project: 657/657 passed.
Full solution: 11193/11193 passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(vsdx): update companion docs for Milestone 11 quality-retry (retry 1)

Updates the reqstream YAML, design docs, and verification doc to match the
actual, evidence-based outcome of this quality-retry cycle rather than the
plan's hypothesis:

- GeometryRowMerge: corrected wording to the verified unconditional-overlay
  rule; cites both the 60489.vsdx ellipse and 44501e.vsdx Shape-64 triangle
  evidence; lists the 2 new geometry regression tests.
- CellMerge: documents the ArrowCellNames generalization and the
  literalization fix discovered via testing; lists the 2 new arrow
  regression tests.
- ArrowheadRendering: documents the investigation's conclusion - the
  floating triangle is fixed via the geometry-row fix; the dash-stub
  artifacts are characterized as an accepted, pre-existing, out-of-scope
  1-D-only rendering-architecture limitation, not a regression and not
  force-fixed. No requirement scope change.

docs/design/canvas-net-vsdx.md gains a new "Milestone 11's quality-retry
cycle (retry 1)" list entry; docs/design/canvas-net-vsdx/vsdx-document.md
corrects the geometry-row-merge cell-by-cell description and documents the
fix's rationale; docs/verification/canvas-net-vsdx/vsdx-document.md adds
matching verification notes with before/after visual-confirmation evidence
for GeometryRowMerge, CellMerge, and ArrowheadRendering.

pwsh ./fix.ps1 and pwsh ./lint.ps1 both ran clean after these changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix 2-D group-child transform-cell merge gap (Milestone 12 Finding #3)

MergeCells now applies the TransformCellNames (PinX/PinY/Width/Height/
LocPinX/LocPinY/Angle/FlipX/FlipY) instance-wins overlay when the merged
shape is a Group child (isGroupChild: parent is not null), independent of
the existing 1-D-only test. This fixes a stale Master template-default
transform cell incorrectly winning over a resized Group child instance's
own correct, F="Inh"-marked value.

Confirmed via the external smoke-test harness against the real-world
corpus: Test_Visio-Some_Random_Text.vsdx's "View" table shape no longer
over-wraps its text (TxtWidth no longer falls back to a stale Master
Width), test.vsdx's header bar now sits flush at the top of its group,
and 44501e.vsdx's "-has" connector label now resolves to its own correct
external position. The top-level (non-group-child) 2-D shape exclusion
is untouched and remains locked by
MasterInheritance_2DShapeInhMarkedTransformCell_DefersToMasterValue.

Adds synthetic-fixture tests:
- MasterInheritance_2DGroupChildInhMarkedTransformCell_PrefersInstanceValue
- TextBox_GroupChildInhMarkedWidthDiffersFromMaster_TxtWidthReflectsInstanceWidth
- TextLayout_CorrectedGroupChildWidth_FitsOneLineWhereStaleMasterWidthOverWrapped

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Document Milestone 12 group-child cell-merge fix and corpus findings

docs/design/canvas-net-vsdx.md: revise the Milestone 11 "2-D shape
transform-cell-merge exclusion" deferred-limitation bullet to record it
is now fixed for the group-child case, while 60973.vsdx's rack-frame
remains invisible for a separate, already-documented Themed-color/
gradient-fill reason, not a transform-cell problem. Adds a new bullet
extending the Milestone 4 neutral-theme-fallback deferral with this
milestone's exhaustive cross-fixture Themed-color investigation
(test_text_extraction.vsdx/60489.vsdx needing unfilled vs. 60973.vsdx
needing filled with real theme colors; no safe discriminator found).
Documents Finding #5 (UML class-name text crowding the compartment's own
top border in 44501d.vsdx/44501e.vsdx) as deferred/unresolved with
precise follow-up scope.

docs/verification/canvas-net-vsdx/vsdx-document.md and
docs/reqstream/canvas-net-vsdx/vsdx-document.yaml: update the
CanvasNetVsdx-VsdxDocument-CellMerge requirement/verification entry to
describe the group-child transform-cell carve-out, list the new tests,
and record the real-world-corpus confirmation evidence.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Strengthen HideText suppression test to scan full rect for glyph ink

Code review flagged that Render_ShapeWithHideTextCell_SuppressesOwnTextButNotFillOrStroke
only sampled a single interior pixel, which would still pass even if the
HideText suppression were removed entirely. Now scans every pixel in the
shape's full rendered bounds and asserts each matches the pure fill color,
proving no glyph ink was painted anywhere.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: fix formal-review traceability/documentation gaps for Vsdx

Addresses all Critical/High/Medium findings (and one trivial Low) from the
7 formal-review reports run against the Vsdx work on this branch. No C#
production source behavior changes; one new unit test added to close a
coverage gap, plus a cspell-ignore fix in an existing test file.

Purpose review (F9, High):
- Add a full `VsdxDocument` User Guide API Reference section and a new
  `Example 16` to docs/user_guide/introduction.md, mirroring the existing
  PdfDocument section structure/tone.

Decomposition review (DES-01/DES-02):
- Reword the self-contradictory ChartModel unit-count sentence in
  docs/design/introduction.md (two units, not "a single unit... and a
  ChartRenderer unit").
- Add the missing CanvasNetVsdxView.svg diagram embed to
  docs/design/canvas-net-vsdx/vsdx-document.md.

CanvasNetVsdx-Architecture review (DES-02, High):
- Add CanvasNetVsdx to docs/design/introduction.md's Software Structure and
  Folder Layout sections.
- Document 6 existing unit tests (geometry, group-resolution, style,
  render) as system-level Test Scenario coverage for the Milestone 10/11
  capabilities in docs/verification/canvas-net-vsdx.md, since they already
  exercise the full public API end-to-end.

CanvasNetVsdx-Design review:
- Link the 6 orphaned Milestone 10/11 unit requirements under their system
  parent requirements in docs/reqstream/canvas-net-vsdx.yaml (arc-geometry
  resolution, deleted-shape exclusion, transparency resolution,
  non-printing suppression, minimum stroke width, hide-text suppression).
- Fix the broken MasterShapeInheritance -> CellMerge cross-references and
  the stale "unimplemented"/planning-skeleton header comment in
  docs/reqstream/canvas-net-vsdx/vsdx-document.yaml.

CanvasNetVsdx-Verification review:
- Add the missing Acceptance Criteria section to
  docs/verification/canvas-net-vsdx/vsdx-document.md.
- Add CanvasNetVsdx to docs/verification/introduction.md's Scope section.
- Correct the stale "EllipticalArcTo is never converted" claim in
  docs/verification/canvas-net-vsdx.md (Milestone 11 implemented real
  elliptical-arc geometry resolution).

CanvasNetVsdx-AllRequirements review:
- Covered by the same reqstream/vsdx-document.yaml fixes above.

CanvasNetVsdx-VsdxDocument review (F-1 Medium, F-2/F-3 Low):
- Update docs/design/canvas-net-vsdx/vsdx-document.md's Key Methods entry
  for ResolveEffectiveShape/CellMerge to describe the Milestone 12
  isGroupChild carve-out, and the Data Model section to describe the
  Milestone 11 arrowhead-index 4/254 recognition.
- Add a regression test
  (ArrowheadResolution_2DGroupChildInhMarkedArrowCell_DefersToMasterValue)
  proving the isGroupChild carve-out is deliberately scoped away from
  ArrowCellNames, closing the F-3 coverage gap.

Verified clean with `pwsh ./lint.ps1` (markdownlint, cspell, yamllint,
reqstream, versionmark, reviewmark, sysml2tools) and a full
`pwsh ./build.ps1` run (11,205 tests passed, 0 failed, 0 skipped).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix PR #42 review comments: multi-section fill/line flags, rotation-invariant scale, TxtAngle text rotation, exception accessibility, stale docs, and ApiMark bump

Addresses all 8 GitHub Copilot automated review findings on PR #42:

1. VsdxDocument.Paint.cs: ResolvePaint no longer derives shape-wide HasFill/HasLine
   from only geometry section 0's NoFill/NoLine; shape-wide flags now come purely
   from FillPattern/LinePattern, with per-section suppression handled exclusively
   by PaintShapeGeometry's existing per-section check. Regression test added.

2. VsdxDocument.Render.cs: AverageScale now computes the lengths of the transformed
   basis vectors instead of the raw matrix diagonal, making it rotation-invariant.
   Regression test compares stroke thickness of a rotated vs unrotated line.

3. VsdxDocument.Render.cs/TextLayout.cs/VsdxTextBoxTransform.cs: TxtAngle is now
   composed into a dedicated text-to-pixel transform (ResolveTextToPixelTransform)
   applied at paint time, rotating the text box about its own pin - a contained,
   low-risk fix (approach a). Added a direct unit test of the composed transform.

4. VsdxUnsupportedFeatureException.cs: made public (was internal) to match its own
   documented "callers can distinguish unsupported features" contract and the
   sibling PptxUnsupportedFeatureException's public convention. Confirmed via
   repo-wide search it has zero throw-sites today; kept (not removed) since
   existing approved docs already describe it as intentional future scaffolding.

5. docs/sysml2/model/canvas-net-vsdx.sysml: corrected stale row-support list -
   EllipticalArcTo/ArcTo are implemented (Milestone 11, resolved into real ArcTo
   path commands); only NURBSTo/InfiniteLine/RelCubBezTo/SplineStart/SplineKnot/
   PolylineTo/Ellipse remain genuinely skipped/deferred.

6. docs/sysml2/model/canvas-net-vsdx/vsdx-document.sysml: corrected stale claim
   that Foreign shapes and non-solid FillPattern values throw
   VsdxUnsupportedFeatureException - non-solid fills degrade to solid, and
   Foreign shapes have no distinct Type-based handling at all.

7. VsdxDocument.cs: rewrote the class's stale Milestone-2-era XML remarks (which
   claimed no shape/geometry/style/text/connector parsing existed) to accurately
   describe current capabilities, mirroring PptxDocument's remarks style/tone.
   Also corrected a stale "deferred to Milestone 7" remark on VsdxTextLayout.

8. VsdxTextStyleTests.cs: strengthened the VerticalAlign/margin test, which
   previously only asserted TextLayout was non-null, to assert the resolved
   glyph's actual X/Y origin differs measurably between the StyleSheet-inherited
   default and a shape's own overriding VerticalAlign/LeftMargin cells.

Also bumps DemaConsulting.ApiMark.MSBuild to 0.5.7 across every package in the
repo (previously an inconsistent mix of 0.5.1/0.5.4/0.5.4 on Vsdx) - 0.5.7 picks
up fixes for cref link resolution and type-only cref rendering between 0.5.4 and
0.5.7 that are directly relevant to this repo's heavy XML-doc <see cref> usage.

Full solution build and all 11214 tests pass; lint.ps1 exits 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix PR #42 review round 2 findings (7 Copilot review comments + 2 formal-review recheck doc gaps)

Addresses all 7 findings from GitHub Copilot's automated review round 2 on
PR #42 (after commit f6e3cf0 fixed the first 8), plus 2 additional
doc-propagation gaps found by a formal-review recheck of that same commit.

1. (High) VsdxDocument.Shapes.cs: ParseShapeElement/ParseShapeElements now
   enforce the same MaxGroupNestingDepth/MaxResolvedShapeCount budget during
   the parse pass itself (not only during ResolveShapeRecursive), so a
   deeply nested or very wide untrusted page/master can no longer exhaust
   the call stack or memory before either safety budget is reached. Added
   direct ParseShapeElement unit tests (bypassing the resolver entirely) for
   both the depth and count budgets.
2. (Medium) VsdxDocument.Groups.cs / VsdxTextRun.cs / VsdxDocument.Text.cs:
   added VsdxRawText.HasElement so an explicit, empty <Text/> element is
   distinguished from a genuinely absent one; the Master-text fallback now
   checks HasElement instead of Runs.Count, so an instance's intentional
   empty <Text/> no longer reintroduces the Master's text. Added regression
   tests for both the empty-and-present and absent cases.
3. (Medium) VsdxDocument.Pages.cs: ParseSizeCellEmu now catches the EMU
   conversion's OverflowException for a pathologically large page
   dimension and re-throws the documented InvalidDataException, preserving
   the public error contract. Added a regression test with a 1e300-inch
   PageWidth asserting InvalidDataException (not OverflowException).
4. (Low) NamespaceDoc.cs: replaced the stale "implementation is in
   progress" remarks with an accurate description of the shipped
   VsdxDocument/Render functionality and its known limitations (FillPattern
   edge cases, one unsupported gradient pattern, minor UML text padding).
5. (Low) VsdxDocument.TextRender.cs: corrected the class summary - the text
   painter is wired into RenderShapeRecursive via PaintTextLayout, not
   "not yet wired into any public API".
6. (Low) VsdxShapeNode.cs: removed the incorrect "only for page-level
   shapes" qualifier from the EffectiveCells doc - ResolveShapeRecursive
   assigns it for every descendant shape too.
7. (Low) VsdxFixtures/README.md: confirmed the existing 14-file
   davehoward-*/jgreywolfvsdxjs-* provenance list/count is accurate, and
   added a clarifying note that the separate Apache-POI-derived
   12-file/16-page corpus and smoke-test scratch tooling mentioned in the
   PR description were never checked into this repository.
8. Removed the stale "VsdxUnsupportedFeatureException is thrown for
   Foreign shapes / non-solid FillPattern" claim from VsdxDocument.cs's own
   class remarks, docs/design/canvas-net-vsdx.md, and
   docs/design/introduction.md, matching the wording already corrected in
   docs/sysml2/model/canvas-net-vsdx/vsdx-document.sysml by commit f6e3cf0:
   neither case is distinctly branched on or throws today.
9. Documented the TxtAngle/text-box-rotation fix
   (ResolveTextToPixelTransform) from f6e3cf0: added a Key Methods entry in
   docs/design/canvas-net-vsdx/vsdx-document.md, a new requirement
   (CanvasNetVsdx-VsdxDocument-TextBoxRotationRendering) in
   docs/reqstream/canvas-net-vsdx/vsdx-document.yaml, and a matching
   verification section in docs/verification/canvas-net-vsdx/vsdx-document.md.

pwsh ./build.ps1: Build succeeded, 0 warnings, 0 errors; 11229/11229 tests
passed across all target frameworks.
pwsh ./lint.ps1: exit code 0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Link orphaned TextBoxRotationRendering requirement under CanvasNetVsdx-Render

The CanvasNetVsdx-VsdxDocument-TextBoxRotationRendering unit requirement
added for the TxtAngle text-box rotation fix (PR #42 round 1) was never
added as a child of its system-level parent, causing reqstream --enforce
to report it as orphaned (unreachable from any system/quality-tagged
requirement).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Malcolm Nixon <Malcolm.Nixon@hiarc.inc>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Malcolmnixon pushed a commit that referenced this pull request Oct 9, 2026
Addresses 3 review findings treated as design issues rather than surface bugs:

1. Isolate _pendingClipFillRule across nested content-stream executions
   (src/DemaConsulting.CanvasNet.Pdf/PdfDocument.{Images,Fonts.Type3,Patterns.Tiling}.cs).
   Form XObjects, Type 3 glyph procedures, and tiling-pattern cells all
   re-enter ExecuteOperators but previously left the outer W/W* pending-clip
   flag live and shared: a path-painting operator inside the nested stream
   (for example the tile cell's own fill) would read/clear the outer's
   still-pending clip and install it onto the nested, throwaway graphics
   state instead - silently discarding the outer clip once the nested
   execution returned. Each nested re-entry point now saves the outer value,
   resets it to null for its own scope, and restores it afterward, exactly
   like it already does for _resources/_gs/_gsStack. Added a regression test
   proving a W-then-pattern-fill path keeps restricting a later, unrelated
   fill to the clip rectangle.

2. Count (not just flag) skipped zero-length "on" dash entries
   (src/DemaConsulting.CanvasNet/Drawing/DashSplitter.cs). The previous fix
   for a leading zero-length dot used a boolean SkippedOnEntry, so a pattern
   with multiple consecutive zero-length "on" entries at the phase boundary
   (for example [0, 0, 0, 2], two distinct dots) only ever emitted one dot
   instead of one per skipped entry. LocatePhase/LocatePhaseForward/
   LocatePhaseBackward now return a SkippedOnEntryCount, and BuildOnIntervals
   emits one (0, 0) interval per skipped entry. Added a regression test for
   [0, 0, 0, 2].

3. Normalize an empty <a:fld type=""> attribute to null
   (src/DemaConsulting.CanvasNet.Pptx/PptxDocument.Text.cs), matching
   PptxRunItem.FieldType's documented contract that an empty attribute is
   equivalent to no attribute - previously stored as string.Empty, which
   could be mistaken for a populated field type by a caller checking only
   for non-null. Added a regression test.

Verified locally: `pwsh ./lint.ps1` (exit 0) and `pwsh ./build.ps1`
(11,421/11,421 tests passed, 6 more than baseline from the new regression
tests). Confirmed test #1 fails without its corresponding fix by temporarily
reverting PdfDocument.Patterns.Tiling.cs and re-running it in isolation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Malcolmnixon added a commit that referenced this pull request Oct 9, 2026
…dash/stroke, TIFF LZW, PPTX table growth/slide-number) (#43)

* fix(pdf): implement W/W* clipping-path operators per PDF 32000-1 8.5.4

PdfDocument.Render previously ignored the content-stream clipping
operators W (nonzero winding) and W* (even-odd), silently falling
through the default case in DispatchOperator. This caused geometry
that a real PDF viewer (e.g. PDFium) clips away to instead be painted
in full, producing visible artifacts such as stray frames around page
content and stray boxes next to TOC entries in WeasyPrint-produced
PDFs.

W/W* now defer clip application until the next path-painting operator
executes (e.g. n, f, S), per spec: the operator still paints according
to its own semantics, and the current path becomes the new clip,
intersected with (never replacing) any previously active clip. W uses
nonzero winding and W* uses even-odd specifically to interpret the
path for clipping purposes, independent of the painting operator's own
fill rule.

Adds a new internal ClipMask type (Drawing layer): a per-pixel
antialiased coverage mask bound to the surface's pixel extent, built
by rasterizing a path with a given fill rule via ScanlineRasterizer,
and combined with a prior clip via elementwise coverage intersection.
New internal clip-aware overloads are threaded through
PathFiller/ScanlineRasterizer fill paths without changing any existing
public API signatures.

PdfDocument.GraphicsState now carries the active Clip, cloned/restored
through q/Q so a clip set inside a q...Q block cannot leak outside it.
The clip is enforced for path fill/stroke, image Do compositing, and
glyph-fill text painting.

Adds ClipMaskTests, additional PathFillerTests, and new PdfDocument
clipping tests covering: W n clip + fill, clip scoping via q/Q, W*
even-odd clip semantics, nested clip intersection, clip + image Do,
and clip + glyph fill.

Updates docs/reqstream, docs/design, docs/verification, sysml2 model,
and .reviewmark.yaml companion artifacts for traceability.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(drawing): correct even-odd coverage for multi-toggle rows in ScanlineRasterizer

The per-row cell-based signed area/cover accumulation in CoverageSweep computes,
per column, a single scalar approximating the row-height-weighted integral of the
winding number, then folds it via magnitude % 2 for FillRule.EvenOdd. This fold
is only valid when at most one edge toggles within a device-pixel row for a given
column; it is mathematically wrong whenever two or more edges' start/end y
boundaries land strictly inside the same row for the same column, since
fold(integral(w dy)) != integral(parity(w(y)) dy) once w takes values above 1
within the row.

This caused WeasyPrint's CSS �order: double (and any even-odd fill with nested
same-x-range subpaths whose y-boundaries land within one device pixel row) to
rasterize as a single solid bar instead of two separate lines, confirmed via the
reported content stream:

  0.6 0.6 0.6 rg 96 65 624 930 re 96 61.67 624 934 re 96 63.33 624 932 re
  96 60 624 936 re f*

Fix: detect, per row, whether two or more edges have Y0/Y1 landing strictly
inside the row (mid-row breakpoints). For FillRule.NonZero, and for EvenOdd rows
with fewer than two breakpoints, the original single-pass accumulation and
ResolveCoverage fold are used completely unchanged (verified bit-for-bit via the
full pre-existing test suite). For EvenOdd rows with two or more breakpoints, the
row is split into exact sub-intervals at each breakpoint y; each RowEdge's affine
x(y) mapping is re-interpolated per sub-interval (exact for slanted edges, not
just axis-aligned ones), each sub-interval is resolved via the same unmodified
fold, and the sub-interval results are combined via a height-weighted average to
produce the final per-column coverage.

This only changes asymptotic cost for the rare multi-breakpoint-per-row EvenOdd
case (O(edges + columns * breakpoints) instead of O(edges + columns)); the common
single-toggle-per-row case and all NonZero fills retain their original cost and
output.

Added regression tests:
- ScanlineRasterizerTests: nested-rectangles-in-one-row counter-example (~0.334
  coverage), single-boundary-per-row even-odd case unchanged, slanted-edge
  multi-sub-row-toggle case, and explicit NonZero-unaffected coverage.
- PdfSystemIntegrationTests: PDF-level regression reproducing the literal
  bug-report content stream via a new fixture, asserting the gap between the two
  lines is not fully opaque.

Updated docs/design, docs/verification, and docs/reqstream for
canvas-net/drawing/path-filler to describe the corrected algorithm and new tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(pdf): implement sh shading operator to stop silent black-fill mis-rendering

The content-stream dispatcher's default branch silently skipped the `sh`
operator (PDF 32000-1 section 8.7.4.2), which paints a shading directly onto
the current clipping path without a path or Pattern color space. Producers
(e.g. Visio PDF export) that draw a black fallback shape and then paint a
gradient over it via `sh` therefore kept showing the black fallback with no
exception, matching the reported "gradient drawn as solid black" symptom.

Add `case "sh"` dispatch plus `OpPaintShading`, extract the existing
`/ShadingType`/`/ColorSpace`/`/Function`/`/Domain`/`/Coords`/`/BBox`
resolution shared by the working scn+Pattern path into
`ResolveShadingDescriptor` (BuildShadingPattern is now a thin wrapper over
it, behavior-preserving), and reuse the existing clip-mask-aware
PathFiller.Fill overload from the W/W* clipping feature so sh composes with
an active clip. When no clip is active, bound the paint region using the
shading's own /BBox (or the full surface, further bounded by PathFiller's
surface-bounds intersection) instead of ever painting unboundedly. Unsupported
/ShadingType or color space values reached via sh now throw the same
UnsupportedImageFeatureException the scn+Pattern path already throws, so the
previously-silent case is now reported.

Add unit tests for direct axial/radial sh painting, clip composition, BBox
fallback bounding, and unsupported-shading-type/undeclared-name failures, plus
a system-integration reproduction of the bug report (black rectangle fill
followed by an sh gradient overlay) proving the gradient is now visible.

Update design docs, reqstream requirements, and verification docs/sysml2
model to add the new CanvasNetPdf-PdfDocument-ShadingOperator requirement and
its traceability.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(drawing): emit zero-length on-interval for dash-dot pattern entries

DashSplitter.AdvanceDash previously skipped over zero-length dash-pattern
entries entirely while advancing, including zero-length 'on' entries. This
is the standard technique producers (PDF/SVG/CSS, e.g. WeasyPrint) use to
render a dash-dot border: a degenerate zero-length dash combined with a
round or square line cap renders as a dot. Because the zero-length 'on'
entry was skipped rather than emitted, dash-dot patterns such as
'[D G 0 G] 0 d' rendered as plain dashes with the dot missing entirely.

AdvanceDash is collapsed to a single pattern-entry step, and
BuildOnIntervals now emits exactly one zero-length on-interval per pattern
repetition when it lands on a zero-length 'on' entry (bounded by the
existing MaxOnIntervalCount safety cap). EstimatesExceedBudget's pre-flight
formulas are corrected to account for zero-length 'on' entries now costing
iterations/intervals, while remaining algebraically identical to the
previous formulas for zero-free patterns (no regression). Degenerate
all-zero-length patterns (e.g. [0 0]) continue to be filtered upstream in
Split via GetPatternLength <= 0, so they still safely fall back to a solid
stroke with bounded, unchanged behavior. All XML-doc and inline comments
describing the old skip-all-zero-length behavior were updated to describe
the new behavior.

Adds unit tests at the DashSplitter level proving a [2,2,0,2]-style
dash-dot pattern now yields dash and dot on-intervals at the expected
positions across multiple pattern repetitions, plus regression tests
confirming zero-length 'off' entries remain invisible and degenerate
all-zero-length patterns still terminate safely with bounded output. Adds
a PDF content-stream integration test reproducing a dash-dot 'd' operator
array with a zero entry, asserting the rendered stroke now contains a
visible dot pixel distinct from and isolated by the surrounding dash gaps.

Updates design, verification, and requirement-stream documentation for the
PathStroker unit to reflect the corrected cost-model formulas and the new
dot-emission behavior, per this repository's architecture-model and
requirements traceability standards.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(pdf): stop clamping sub-pixel stroke widths to a full device pixel

Hairline table rules (e.g. a d/stroke with

* fix(tiff): correct LZW encoder code-width transition per TIFF early-change rule

TiffCodec.Save with TiffCompression.Lzw produced files that this library's
own decoder and third-party readers (Pillow/libtiff) both rejected with
"Invalid TIFF LZW code sequence" / "Using code not yet in table" once the
LZW table grew past 510 entries, because EncodeLzw widened its output code
size one table entry too early (nextCode is 511 or 1023 or 2047).

TIFF 6.0 Section 13 mandates an asymmetric "early change" convention: only
the LZW decoder widens its code size one entry earlier than classic LZW,
to compensate for the decoder's inherent one-iteration lag behind the
encoder when registering new table entries. The encoder must use the
plain/unadjusted thresholds. DecodeLzw/AddLzwTableEntry already implemented
the correct early-decode thresholds (511/1023/2047); only EncodeLzw was
wrong and needed to move to 512/1024/2048.

Verified against libtiff's tif_lzw.c (the reference implementation Pillow
delegates to) and confirmed empirically: bidirectional, pixel-exact
round-trips between this library and Pillow 12.1.1 now succeed across all
three code-width boundaries (510/1022/2046 table entries).

- Fix EncodeLzw's code-width bump thresholds (511/1023/2047 -> 512/1024/2048)
- Leave DecodeLzw/AddLzwTableEntry thresholds unchanged (already correct)
- Document the TIFF "early change" asymmetry in XML doc remarks on
  EncodeLzw, DecodeLzw, and AddLzwTableEntry, citing TIFF 6.0 Section 13
- Fix TestEncodeLzw's matching thresholds in TiffCodecTests.cs
- Add TiffCodec_SaveThenLoad_LzwLargeVariedImage_RoundTripsAcrossAllCodeWidthBoundaries,
  a deterministic 200x200 high-entropy round-trip test that crosses all
  three LZW code-width boundaries and fails against the pre-fix encoder
- Update design/verification/reqstream docs for the Tiff Codec unit

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(pptx): grow table row heights to fit wrapped cell text

Table rows previously always rendered at the stored a:tr h= EMU height,
even when a cell's text wrapped to more lines than that height could
fit, causing neighboring rows' text to overlap. Row heights now grow
(never shrink) to fit each cell's actual wrapped text content.

- Extract ResolveTextLayout's wrap/measure steps into a new
  ResolveWrappedLines helper (behavior-preserving) and add a
  MeasureRequiredTextHeightEmu entry point in
  PptxDocument.TextLayout.cs for measuring a cell's required text
  height at its resolved width.
- Add GrowRowHeightsToFitText in PptxDocument.Tables.cs: single-row
  cells grow their row to max(storedHeightEmu, requiredTextHeightEmu);
  row-spanning cells apply any shortfall between the stored span
  total and the required merged-cell height onto the last spanned
  row. ResolveCellRects/WalkResolvedCells consistently use the
  resulting effective heights for all row Y-offsets and cell rects.
- Update PptxTable.cs XML docs to describe min/effective height
  semantics instead of a fixed, never-growing height.
- Add tests covering growth, regression-safety when the stored height
  is already sufficient, row-span shortfall distribution, and
  opt-in-only default behavior when optional measurement parameters
  are omitted.
- Update design, verification, requirements (reqstream), and SysML2
  model docs to reflect the new row-height growth behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(pptx): render slide-number fields with actual per-slide number

Fix <a:fld type="slidenum"> fields rendering PowerPoint's cached
authoring-time placeholder text (e.g. the literal guillemet glyphs
<#>) on every slide, instead of showing each slide's real 1-based
slide number as PowerPoint itself does on screen/export.

- PptxTextBody.cs: add nullable FieldType to PptxRunItem to capture
  the <a:fld> type attribute instead of discarding it.
- PptxDocument.Text.cs: ParseParagraph now captures the field type;
  add pure SubstituteSlideNumberField(textBody, slideNumber) that
  replaces only slidenum-typed run text (case-insensitive) with the
  plain decimal slide number, rebuilt via primary constructors to
  avoid stale Runs-accessor pitfalls. Scope the long-standing
  'no live engine to recompute fields' doc comment to the field
  types that still legitimately fall back to cached text (e.g.
  datetime variants), since slidenum is now computed internally.
- PptxDocument.Render.cs: thread the existing 1-based slide index
  through Render -> RenderNode -> RenderShape and call
  SubstituteSlideNumberField on each shape's text body before
  layout/measurement.
- Tests: parse-level FieldType capture tests, direct
  SubstituteSlideNumberField unit tests (case-insensitivity,
  datetime/plain-run non-regression, Runs-accessor regression), and
  end-to-end multi-slide render tests proving per-slide substitution
  (slide 1 -> "1", slide 2 of 2 -> "2") with datetime/plain-run
  fields unaffected.
- Docs: new reqstream requirement, design 'Phase 2 Follow-Up'
  section, and verification section for slide-number field
  substitution; .cspell.yaml: add 'Xmls' for the new design prose.

This is the last of 8 upstream CanvasNet.Pptx issues fixed on this
branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(readme): trim Features/Installation/Usage sections to essentials

- Features: one line per bullet, moving detail to the user guide/API docs
- Installation: single generic command plus a package table instead of
  per-package dotnet/PMC duplicate blocks
- Usage: reduced to 3 representative examples (codec round-trip, PDF
  render, vector path fill); all removed examples were already present
  (or more granular) in docs/user_guide/introduction.md's Examples
  section, which is now called out as the canonical place to find them

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(api): fix ApiMark doc-comment defects found in agent review

* fix(pptx): clamp row-span shortfall to last actual table row

GrowRowHeightsToFitText computed lastSpannedRowIndex = rowIndex + rowSpan - 1
without validating it against the table's actual row count, so a malformed
PPTX declaring a rowSpan extending past the end of the table threw
ArgumentOutOfRangeException while resolving cell rects. Clamp the index to
effectiveHeightsEmu.Count - 1, mirroring the existing SumConsecutive clamp
pattern used elsewhere in the same file, so the shortfall is added onto the
last actual row instead of indexing out of bounds.

Found during the formal change-review of canvas-net-pptx/pptx-document for
branch fix/docpilot-upstream-issues.

Adds a regression test and documents the clamp behavior in the design doc.

* docs(pdf): fix stale sh/clipping unsupported-scope remarks

PdfDocument.cs's and NamespaceDoc.cs's 'documented scope boundaries' remarks
still listed the sh (shading) operator and generic path clipping (W/W*) as
unsupported, left stale after commits 506dbf4 (W/W* clipping) and 69ee60f
(sh shading) implemented both without updating these two files' doc comments
(every other dispatch comment, e.g. in ContentStream.cs, was correctly
updated at the time).

Also clarifies PdfDocument.Patterns.Shading.cs's BuildShadingGradient remarks:
OpPaintShading already honors the current clip path via ClipMask, so the
prior remark's 'clip path is not honored' framing was both stale and
conflated with a separate, still-unaddressed concept (clipping a gradient to
its own shading-geometry bounds for an exact /Extend false approximation,
currently approximated via GradientSpread.Pad only). Mesh shadings
(ShadingType 4-7) remain genuinely unsupported and are still documented as
such.

Found during the formal change-review of canvas-net-pdf/pdf-document for
branch fix/docpilot-upstream-issues.

* docs(tiff): fix stale test-method count in verification doc

The verification doc claimed 70 test methods (65 in TiffCodecTests.cs + 5 in
TiffFixtureTests.cs); the actual counts are 62 and 5 (67 total), verified by
counting public test methods in both files. Left stale by prior edits to
TiffCodecTests.cs.

Found during the formal change-review of canvas-net/codecs/tiff-codec for
branch fix/docpilot-upstream-issues.

* docs(drawing): link new PathFiller requirement, add missing test AAA comments

Add the new unit requirement
CanvasNet-Drawing-PathFiller-ScanlineEvenOddMultiToggleRow (introduced by
this branch's even-odd multi-toggle-row coverage fix, defined in
docs/reqstream/canvas-net/drawing/path-filler.yaml) to the children: list of
the parent subsystem requirement CanvasNet-Drawing-PathFill in
docs/reqstream/canvas-net/drawing.yaml - every sibling Scanline* requirement
was already linked except this one.

Also add Arrange/Act/Assert comments to
PathFiller_Fill_SolidColorWithNullClip_BehavesLikePublicOverload in
PathFillerTests.cs, matching its already-commented sibling test, for
consistency with this branch's new ClipMask-aware fill tests.

Found during the formal change-review of canvas-net/drawing/path-filler for
branch fix/docpilot-upstream-issues.

* refactor(drawing): remove dead code and unused parameter in DashSplitter

BuildOnIntervals ended with 'if (!isClosed) { return intervals; } return
intervals;' - both branches returned the identical value, a no-op isClosed
check pre-existing on main (confirmed via diff against main, not introduced
by this branch's dash-dot fix). Collapsed to a single return; isClosed was
otherwise unused within BuildOnIntervals once the dead branch was removed,
so the now-unused parameter was also removed from the method and its sole
call site (flagged by the build's own S1172 unused-parameter analyzer).

Found during the formal change-review of canvas-net/drawing/path-stroker for
branch fix/docpilot-upstream-issues.

* docs(decomposition): mention ClipMask unit, model slide-number field fix

docs/design/introduction.md's Drawing subsystem Scope/Software-Structure
prose and Folder Layout tree didn't mention the internal ClipMask
clip-coverage helper added by this branch's clip-mask-aware fill fix.

The CanvasNetPptx sysml model narratives (canvas-net-pptx.sysml and
canvas-net-pptx/pptx-document.sysml) already described the table
row-height-growth fix but were never updated for the subsequent
slide-number field-substitution fix (SubstituteSlideNumberField); both
narratives now describe it alongside the row-height-growth entry.

Found during the formal change-review of the Decomposition review-set for
branch fix/docpilot-upstream-issues. Verified with 'dotnet sysml2tools lint'.

* Address PR review findings: clip mask memory, rounding, overflow, slide numbers, docs

- ClipMask/ScanlineRasterizer: restrict the clip coverage buffer to its own
  integer bounding box (with an origin offset) instead of allocating a
  full-page-sized buffer per clip, avoiding large allocations for small clips
  on large pages.
- PdfDocument.Images: use MidpointRounding.AwayFromZero for clipped-image
  alpha rounding, matching Surface's own compositing convention.
- PptxDocument.Tables: fix two integer-overflow bugs in row-height growth
  (SumConsecutive and the row-span shortfall calculation) that could trigger
  on a malformed rowSpan near int.MaxValue; add a regression test.
- PptxDocument.Render/Tables: thread the slide number through table
  rendering so a slidenum field inside a table cell substitutes correctly
  for both row-height measurement and painting, matching the existing
  non-table shape behavior; add regression tests.
- SvgCodec: correct the stale "sixteen implemented filter primitives" count
  to seventeen.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix DashSplitter leading zero-length on-entry dot

A dash pattern whose phase lands exactly on a zero-length "on" entry at
path position zero - either the pattern's own first entry (e.g. a
dash-dot pattern starting with the dot, [0, 2]) or any entry the dash
offset normalizes onto exactly - never emitted that entry's dot.

LocatePhaseForward/LocatePhaseBackward intentionally skip forward
through zero-length entries encountered exactly at a target
pattern-space boundary so IsDashOnAtStart (and the zero-length-path
special case) identify the entry genuinely "in effect". But
BuildOnIntervals previously had no way to know such a skip happened at
the very start of the path (position zero), and its own main loop only
detects a zero-length "on" entry when it advances into it internally -
never the one LocatePhase itself consumed before the loop began.

LocatePhase now also reports whether its boundary walk skipped over at
least one even-indexed (on) zero-length entry; BuildOnIntervals surfaces
a single dot at position zero when it did. IsDashOnAtStart ignores the
new value, preserving its existing (separately tested) semantics.

Adds a regression test for a dash pattern beginning with a zero-length
on entry ([0, 2]) over a path spanning several pattern repetitions,
proving the leading dot at x=0 is now emitted alongside the
already-covered mid-pattern dots.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix orphaned requirements from DocPilot-issue unit requirement additions

The "Build Documents" CI job failed ReqStream's --enforce orphan-requirement
check: 4 unit-level requirements added while fixing the DocPilot-reported
issues in this PR were never linked into the system-requirement graph via a
parent requirement's children list, so they were unreachable from any
system/quality-tagged root requirement.

Added each orphan to the children list of its relevant system requirement:
- CanvasNetPdf-PdfDocument-ClippingPath under CanvasNetPdf-ContentStreamInterpreter
- CanvasNetPdf-PdfDocument-ShadingOperator under CanvasNetPdf-PatternFills
- CanvasNetPptx-PptxDocument-SlideNumberFieldSubstitution under CanvasNetPptx-TextLayoutAndRendering
- CanvasNetPptx-PptxDocument-TableRowHeightGrowth under CanvasNetPptx-TableShapes

Verified locally with `dotnet reqstream --requirements requirements.yaml
--tests "TestResults/**/*.trx" --enforce` that no orphan error remains.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix deeper review findings: nested pending clip, multi-skip dash dots

Addresses 3 review findings treated as design issues rather than surface bugs:

1. Isolate _pendingClipFillRule across nested content-stream executions
   (src/DemaConsulting.CanvasNet.Pdf/PdfDocument.{Images,Fonts.Type3,Patterns.Tiling}.cs).
   Form XObjects, Type 3 glyph procedures, and tiling-pattern cells all
   re-enter ExecuteOperators but previously left the outer W/W* pending-clip
   flag live and shared: a path-painting operator inside the nested stream
   (for example the tile cell's own fill) would read/clear the outer's
   still-pending clip and install it onto the nested, throwaway graphics
   state instead - silently discarding the outer clip once the nested
   execution returned. Each nested re-entry point now saves the outer value,
   resets it to null for its own scope, and restores it afterward, exactly
   like it already does for _resources/_gs/_gsStack. Added a regression test
   proving a W-then-pattern-fill path keeps restricting a later, unrelated
   fill to the clip rectangle.

2. Count (not just flag) skipped zero-length "on" dash entries
   (src/DemaConsulting.CanvasNet/Drawing/DashSplitter.cs). The previous fix
   for a leading zero-length dot used a boolean SkippedOnEntry, so a pattern
   with multiple consecutive zero-length "on" entries at the phase boundary
   (for example [0, 0, 0, 2], two distinct dots) only ever emitted one dot
   instead of one per skipped entry. LocatePhase/LocatePhaseForward/
   LocatePhaseBackward now return a SkippedOnEntryCount, and BuildOnIntervals
   emits one (0, 0) interval per skipped entry. Added a regression test for
   [0, 0, 0, 2].

3. Normalize an empty <a:fld type=""> attribute to null
   (src/DemaConsulting.CanvasNet.Pptx/PptxDocument.Text.cs), matching
   PptxRunItem.FieldType's documented contract that an empty attribute is
   equivalent to no attribute - previously stored as string.Empty, which
   could be mistaken for a populated field type by a caller checking only
   for non-null. Added a regression test.

Verified locally: `pwsh ./lint.ps1` (exit 0) and `pwsh ./build.ps1`
(11,421/11,421 tests passed, 6 more than baseline from the new regression
tests). Confirmed test #1 fails without its corresponding fix by temporarily
reverting PdfDocument.Patterns.Tiling.cs and re-running it in isolation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Budget-check skipped leading zero-length dash entries

The leading-zero-length-on-entries loop in DashSplitter.BuildOnIntervals
(added to fix 'Leading zero-length dash entries are dropped') materialized
every entry LocatePhase reported via SkippedOnEntryCount without checking
MaxOnIntervalCount. That count is bounded only by pattern.Count (a single
walk through the caller-supplied dash array), which the ratio-based
pre-flight EstimatesExceedBudget cannot see: a pattern consisting almost
entirely of leading zero-length entries followed by one small positive
entry has a tiny patternLength, so the totalLength/patternLength ratio
stays low even though pattern.Count is enormous. The loop now checks
intervals.Count against MaxOnIntervalCount on every append and bails out
as soon as the cap is hit, exactly like the main loop's own backstops.

Added a regression test that calls BuildOnIntervals directly via
reflection (the public Split API's eventual fallback converges to the
same result either way for a merely-just-over-budget input, masking the
difference) with over forty million leading zero-length entries, and
confirmed it fails without the fix (20,000,002 intervals materialized
instead of stopping at the 10,000,000 cap).

Verified locally: `pwsh ./lint.ps1` (exit 0) and `pwsh ./build.ps1`
(11,424/11,424 tests passed, 3 more than baseline from the new
regression test).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Malcolm Nixon <Malcolm.Nixon@hiarc.inc>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependency updates .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants