Repository navigation
SVG rendering completeness: preserveAspectRatio, clipPath/mask, patterns, image, full filter primitives, CSS styling, feBlend - #29
Conversation
… support
- Implement preserveAspectRatio parsing ('[defer] <align> [<meetOrSlice>]', all 10
aligns, meet/slice) via a shared ComputePreserveAspectRatioFit helper reused by:
- root <svg> viewBox fitting (default unchanged: implicit case already was
'xMidYMid meet', confirmed via a dedicated pixel-for-pixel regression test)
- symbol/use viewBox-fit (new capability, resolving use/symbol width/height with
a use -> symbol -> viewport fallback chain)
- marker's own explicit preserveAspectRatio attribute only; the pre-existing
default (no-attribute) marker fit is preserved byte-for-byte unchanged
- Add viewport-relative percentage-based geometry resolution through the existing
ParseGeometryCoordinate/GetFloatAttribute/GetOptionalFloat choke point, so
existing finite/magnitude guards still apply after resolution:
- horizontal/vertical attributes resolve against current viewport width/height
- axis-agnostic attributes (r, stroke-width, stroke-dasharray) resolve against
the diagonal basis sqrt(w^2+h^2)/sqrt(2)
- font-size resolves against the parent element's own cascaded font-size
- stroke-miterlimit remains the sole exception (a unitless ratio, not a length)
- current viewport threaded via new RenderState.ViewportWidth/ViewportHeight
- Descope gradient userSpaceOnUse percentage plumbing (judgment call #5) as a
documented, bounded follow-up simplification; unrelated gradient coordinate
resolution (GetGradientCoordinateOrDefault) is untouched and unaffected.
- Update companion docs: design (svg-codec.md ViewBox Fitting Policy rewrite,
new Percentage-Based Geometry Resolution section, marker/use/symbol behavior),
reqstream (ViewBoxFitting rewrite, PercentageGeometryRejected ->
PercentageGeometryResolved, new MarkerPreserveAspectRatio/SymbolViewBoxFitting
requirements, all linked into codecs.yaml/canvas-net.yaml child chains with
reqstream --enforce confirming zero orphans), verification scenarios, and the
stale user-guide preserveAspectRatio claim.
- Add/repurpose tests: an 18-row root align/meetOrSlice Theory, an align=none
stretch test, an explicit default-vs-xMidYMid-meet pixel-identity regression,
symbol/use viewBox-fit tests, a marker explicit-preserveAspectRatio slice test
(meet is provably pixel-identical to the default path once refX/refY
ref-anchoring is applied, so slice is the only visibly distinguishable case),
and a full percentage-geometry matrix (rect/circle/line/gradient/stroke-width/
font-size/stroke-dasharray), repurposing the two prior percentage-rejection
facts into percentage-resolution proofs rather than deleting their coverage.
All 4446 tests pass across net8.0/net9.0/net10.0; build.ps1 and lint.ps1 both
clean; reqstream --enforce reports zero orphaned requirements.
- Add clip-path=/mask= support for SVG elements/groups (userSpaceOnUse and objectBoundingBox unit modes for clipPathUnits/maskUnits, separate maskContentUnits for mask content coordinate system). - clip-path implemented as offscreen alpha-buffer coverage intersection (no native clip-region primitive exists on the Canvas/Surface abstraction); union of clip children falls out of shared coverage buffer rasterization. - mask implemented via luminance (0.2125R+0.7154G+0.0721B, SVG default) attenuation of an offscreen-rendered mask-content buffer, reusing the existing filter SourceGraphic-buffer/compositing infrastructure. - Ordering fixed for combined filter + clip-path/mask cases: clip then mask now apply to pre-filter content before the filter chain runs, matching spec order geometry -> fill/stroke -> clip -> mask -> filter -> group opacity. - Resource safety: clip/mask offscreen buffer charges reuse the existing shared FilterWorkBudget; new flat MaxClipPathShapesPerClipPath (256) cap (silent truncation) on clipPath children, which are rasterized directly and cannot cycle. Fixed a genuine pre-existing stack-overflow bug where elementDepth was not incremented before recursing into mask content. - Scoped out (documented in code + design doc): nested clip/mask/filter on a clipPath's own children or on the clipPath/mask element itself, use/g/line as clip-path children, alpha mask mode, href/xlink:href inheritance for clipPath/mask elements. - 21 new SvgCodecTests cases: clipPath unit modes, multi-shape union, mask luminance semantics, mask region/content unit variants, combined filter+clip/mask ordering, budget-exhaustion fallback for both clip-path and mask sharing the same FilterWorkBudget, 256-shape cap truncation, and regressions confirming pre-existing filter-only and plain-shape rendering are unaffected. - Updated design/verification docs and reqstream requirement chain (CanvasNet-Lib-SvgClipPathMaskRendering -> CanvasNet-Codecs-SvgClipPathMaskRendering -> 4 new unit requirements), 0 orphans confirmed via reqstream --lint.
RenderShapeEffectsPipeline/RenderGroupWithEffects each charged exactly one region-area work unit against the shared FilterWorkBudget before allocating the primary content buffer, regardless of how many of clip-path/mask/filter actually applied - so a combined clip+mask (or clip/mask+filter) element allocated up to 3x the memory it was charged for. Extract a shared ComputeEffectsPipelineWorkUnits helper used by both call sites: it charges one region-area unit for the content buffer (scaled by the filter's own primitive count when a filter applies, unchanged from before), plus one additional flat region-area unit for each of clip-path/mask that applies, covering ApplyClipPath's own coverage buffer and ApplyMask's own maskSource buffer. Updated the two pre-existing single-effect budget-exhaustion tests' expected shape counts to match the corrected per-application charge, and added a new regression test combining clip-path and mask together to prove the combined charge (not just one effect's own charge) correctly exhausts the shared cumulative budget. Clarified the design doc's resource-safety discussion accordingly.
…urceRef, flaky timing test
- Add SvgCodec.Patterns.cs implementing <pattern> element resolution: href/xlink:href inheritance chain with cycle detection, patternUnits and patternContentUnits (objectBoundingBox/userSpaceOnUse) matching existing gradient/clipPath precedent, patternTransform composition, viewBox/preserveAspectRatio reuse from Phase 1, tile rendering to an offscreen buffer with per-pixel inverse-transform wrap sampling and coverage-multiply compositing clipped to the actual fill/stroke path. - Thread pattern-aware params through RenderShape/RenderFill/RenderStroke and dispatch pattern paints before existing ResolvePaint/gradient path; gradients, solid colors, and fill=url(#nonexistent) fallback behavior are unaffected. - Bound tile-buffer allocation via Surface.MaxDimension and existing FilterWorkBudget charging; bound pattern-of-pattern recursion via the existing MaxElementDepth guard. - Add 18 deterministic tests covering patternUnits x patternContentUnits (4 combinations), patternTransform, viewBox/preserveAspectRatio, href inheritance (including cycles), fill+stroke usage, nonexistent/wrong-type pattern references, and resource-safety behavior (no wall-clock timing assertions). - Update design docs, reqstream requirements (linked into codecs.yaml children chain, 0 orphans confirmed), verification docs, and the SysML2 svg-codec unit sourceRef list for the new source file.
Add CanvasNet-Lib-SvgPatternRendering system-level requirement, linking down to the previously-orphaned CanvasNet-Codecs-SvgPatternRendering subsystem requirement, matching the structure of its sibling filter/clip-path-mask/marker/gradient system-level requirements.
- Add <image> element rendering (SvgCodec.Image.cs): data: URI base64 raster decode reusing existing PNG/JPEG/BMP/TIFF/GIF codecs - Reuse Phase 1 preserveAspectRatio helper for placement-rect fitting - Non-tiled per-pixel inverse-transform compositing (Phase 3 pattern precedent, without tiling/wrap) - Dispatch through the shared element-rendering pipeline so clip-path/mask/filter/opacity apply automatically - Security: non-data hrefs (external file/URL) and nested SVG-in-image (data:image/svg+xml or .svg refs) are explicit, documented no-ops; no filesystem/network access is added - Resource/DoS safety: FilterWorkBudget charged with region area + base64 length before decode; decoded dimensions bounded by each raster codec's existing Surface.MaxDimension check - Add 15 new tests: PNG/JPEG data URIs, preserveAspectRatio variants, clip-path/mask/filter/opacity on <image>, malformed/truncated base64, external href no-op, nested-SVG no-op, deterministic oversized-budget test (no wall-clock timing) - Update companion artifacts: design doc, verification doc, sysml2 sourceRef, and requirement chain at unit/subsystem/system levels (system-level link in docs/reqstream/canvas-net.yaml verified this time, addressing the gap missed in Phase 3)
…unsupported embedded raster images RenderImageWithEffects's raster-decode catch filter only handled FormatException, InvalidDataException, and ArgumentOutOfRangeException. PngCodec.Load throws UnsupportedImageFeatureException (derives from IOException, not InvalidDataException) for well-formed but unsupported raster features such as Adam7-interlaced PNG, so an embedded interlaced PNG <image> href propagated an uncaught exception all the way out of SvgCodec.Load, aborting the entire SVG render instead of skipping just that element per this codec's documented tolerant per-element no-op contract. Widened the catch filter to also catch IOException, so any recognized- but-unsupported raster feature (from any sibling raster codec, not just PNG) is tolerantly skipped. Checked JPEG/BMP/TIFF/GIF: none currently define a similar unsupported-feature exception type - their unsupported- feature cases are already thrown as InvalidDataException, which was already caught. Added a regression test embedding PngSuite's own interlaced basi0g01.png fixture as a data:image/png;base64 href, proving SvgCodec.Load completes without throwing and that sibling elements before/after the skipped image still render correctly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The kernel coefficient lookup in ApplyConvolveMatrixPremultiplied and ApplyConvolveMatrixPreservingAlpha performed plain correlation (no rotation) instead of true convolution per the SVG Filter Effects spec's kernelMatrix(orderX-J-1, orderY-I-1) term. This produced spec-incorrect output for any non-point-symmetric kernel. - Flip the kernel-matrix index in both call sites in SvgCodec.Filters.Convolution.cs; sampleX/sampleY offset math is unchanged (already spec-correct). - Correct SvgCodec_Load_FeConvolveMatrixEdgeDetectKernel_ProducesExpectedEdgeResponse and SvgCodec_Load_FeConvolveMatrixEdgeModeWrap_SamplesAcrossOppositeEdge assertions to the spec-correct hand-derived expected pixel values. No requirement/design/verification/review-set changes needed - this is a pure correctness fix to already-documented behavior, not new user-visible behavior.
Add four tests covering blue/alpha channel selectors, a negative scale (direction reversal), and a larger-magnitude scale, bringing feDisplacementMap test depth in line with its 7 sibling filter primitives (only R/G selectors and one scale value were previously covered).
Phase 6 of the SVG filter roadmap. Adds spec-exact implementations: - SvgCodec.Filters.Lighting.cs: shared Sobel surface-normal estimation (9 kernel variants: interior + 4 edges + 4 corners), feDistantLight / fePointLight / feSpotLight resolution (including pointsAt/limitingConeAngle/ specularExponent falloff), feDiffuseLighting (Lambertian), and feSpecularLighting (Blinn-Phong, alpha = max(R,G,B) of the specular color). - SvgCodec.Filters.Turbulence.cs: the SVG spec's own reference Perlin noise algorithm (permutation table init, noise2/turbulence, turbulence vs fractalNoise remap, per-channel gradient offsets, baseFrequency/numOctaves/ seed). stitchTiles=stitch tolerantly falls back to noStitch (documented). - SvgCodec.Core.cs: MaxTurbulenceOctaves = 32 cap, enforced via InvalidDataException, charged through the existing FilterWorkBudget pattern used by feConvolveMatrix/feMorphology. - SvgCodec.Filters.cs: dispatch cases for the 3 new primitives; work-unit accounting for feTurbulence numOctaves; trimmed the no-op primitive list to feBlend only (the sole remaining unimplemented primitive). - SvgCodecTests.cs: 16 new facts covering distant/point/spot light x diffuse/specular hand-computed values, specular alpha != 1 proof, the spec's own PRNG conformance test vector (1043618065), determinism, turbulence-vs-fractalNoise remapping, numOctaves variation/cap, and stitch-tiles tolerant fallback. Verified via full regression run: all 1574 pre-existing tests pass unchanged, plus all 16 new tests (1590 total). The InkscapeFilters.svg fixture's hard-coded pixel assertion was independently re-verified against real rendering output and requires no change.
- docs/design/canvas-net/codecs/svg-codec.md: new prose paragraphs describing the shared Sobel surface-normal estimation, the three light-source element types, the diffuse/specular formulas (including the Sa=max(R,G,B) specular alpha subtlety), and the feTurbulence reference-algorithm-fidelity approach (naming the Park-Miller PRNG conformance test vector); trimmed the no-op primitive list to feBlend only; extended the resource-safety-caps sentence with MaxTurbulenceOctaves. - docs/reqstream/canvas-net/codecs/svg-codec.yaml: added CanvasNet-Codecs-SvgCodec-FeDiffuseLighting, -FeSpecularLighting, and -FeTurbulence unit requirements; extended FilterPrimitiveResourceSafety's title/justification/tests for the new numOctaves cap. - docs/reqstream/canvas-net/codecs.yaml: linked the 3 new unit ids into CanvasNet-Codecs-SvgFilterRendering's children/title/tests. - docs/reqstream/canvas-net.yaml: updated CanvasNet-Lib-SvgFilterRendering's title/tests to name the three new primitives. - docs/verification/canvas-net/codecs/svg-codec.md: 3 new subsections mirroring the existing FeMorphology/FeConvolveMatrix style; extended the FilterPrimitiveResourceSafety subsection for the new numOctaves cap test. - docs/sysml2/model/canvas-net/codecs/svg-codec.sysml: appended the 2 new source file paths to the unit's sourceRef comment. - .reviewmark.yaml: added the 2 new source files under CanvasNet-Codecs-SvgCodec's review paths, tagged '# phase 6 filter partial'.
…erms to cspell - docs/verification/canvas-net/codecs/svg-codec.md: fixed a markdown-fixer line-wrap artifact from fix.ps1 (a mid-sentence dash was reflowed onto its own line, forming an accidental list item); reworded with parentheses instead to avoid the ambiguity. - .cspell.yaml: added the genuine technical terms introduced by this phase (Sobel, Lambertian, Blinn, Phong, Perlin, PRNG/Prng, Schrage) to the project word list.
…view findings
- SvgCodec.cs class remarks: filter primitive no-op paragraph listed
feTurbulence, feDiffuseLighting, feSpecularLighting, feColorMatrix,
feConvolveMatrix, feComponentTransfer, feMorphology, feDisplacementMap,
feImage, feTile, and feDropShadow as unimplemented no-op passthroughs.
All are now implemented (Phases 5-6). Rewrote the paragraph to state
that only feBlend remains a tolerant no-op passthrough, matching the
wording already used by EvaluateFilterChain's XML doc remarks, and
added the feImage MaxUseDepth recursion-guard note already documented
in docs/design/canvas-net/codecs/svg-codec.md.
- Renamed test SvgTurbulencePrng_10000thValueFromSeedOne_MatchesSpecPublishedTestVector
to SvgCodec_TurbulenceTablesRandom_10000thValueFromSeedOne_MatchesSpecPublishedTestVector
to conform to the {ClassName}_{MethodUnderTest}_{Scenario}_{ExpectedBehavior}
naming convention (method under test is SvgCodec.TurbulenceTables.Random).
Updated the matching reference in SvgCodec.Filters.Turbulence.cs XML docs,
and all requirements traceability references in
docs/reqstream/canvas-net.yaml, docs/reqstream/canvas-net/codecs.yaml,
docs/reqstream/canvas-net/codecs/svg-codec.yaml, and
docs/verification/canvas-net/codecs/svg-codec.md.
- CssMatchWorkBudget now charges each stylesheet's precomputed MatchWorkPerElement (sum, per rule, of every comma-separated selector's own worst-case weight: 1 for its rightmost compound, +1 per further child-combinator segment, +MaxElementDepth per further descendant-combinator segment) instead of the bare Rules.Count, so the charge genuinely tracks real per-element matching CPU cost (selector-list length and descendant ancestor-walk cost), closing the gap that let a selectors-per-rule-heavy stylesheet drive ~29x real wall-clock cost for an identical charged budget value. - ParseRawRules/FindMatchingBrace no longer let an unterminated <style> rule swallow every later rule in the stylesheet: on failing to find a rule's own closing brace, the parser resynchronizes onto the next rule attempt (via the new FindSelectorRecoveryStart) rather than aborting the rest of the scan, so well-formed rules after a single malformed one are still parsed and applied. - Strengthened SvgCodec_Load_CssMalformedRule_SkipsOnlyThatRuleAndStillLoads to assert the specific, now-documented recovery behavior instead of hedging, and added regression tests proving the CSS match-work budget scales with selector-list/combinator-chain length rather than bare rule count. - Updated svg-codec.md's CSS malformed-input/resource-safety section to describe both fixes.
Task 1 - feBlend: - New SvgCodec.Filters.Blend.cs implementing ApplyFeBlend with in/in2 two-input resolution and all 14 CSS Compositing Level 1 modes (normal, multiply, screen, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity). Non-separable modes use the spec's exact whole-RGB Lum/ClipColor/SetLum/ Sat/SetSat reference algorithm. Blending operates on straight color, composited via the CSS Compositing simple-alpha formula, mirroring CompositeFeOperator's premultiply/un-premultiply convention. - Wired case "feBlend" into EvaluateFilterChain's primitive switch. Task 2 - feComposite arithmetic bug fix: - ApplyFeComposite's "arithmetic" operator was previously unimplemented and fell through to a no-op passthrough, causing feDiffuseLighting's always-opaque alpha to propagate unchanged through filter50's chain (InkscapeFilters.svg), producing an opaque rectangular blob instead of following the source silhouette. - Added ApplyFeCompositeArithmetic implementing the premultiplied k1*i1*i2 + k2*i1 + k3*i2 + k4 formula (color and alpha), clamped to [0,1], per CSS Filter Effects spec (the one Porter-Duff-family operator defined on premultiplied values). - Also fixes the same class of bug in InkscapeFilters.svg's filter48, recomputing that fixture test's pixel expectations accordingly. Tests: 20 new hand-computed tests (11 feBlend separable modes via Theory, normal-equals-over cross-check, alpha-compositing, 2 non-separable luminosity cases, mode-fallback, 4 arithmetic-operator/regression tests). Repurposed FilterUnsupportedPrimitive test off feBlend onto a genuinely bogus primitive tag. Docs: updated design/verification docs, 3-level reqstream requirements, SysML2 sourceRef, .reviewmark.yaml, and user guide per the verified implementation plan in .agent-logs/planning-febend-arithmetic-alpha-bug-8f3c21e0.md.
- introduction.md: remove image/pattern from the out-of-scope list (both are fully implemented - pattern in Phase 3, image in Phase 4) and add them to the supported-feature narrative alongside filter/clipPath/mask - svg-codec.md: update the 'In-scope subset' filter bullet to list all 17 currently-implemented fe* primitives instead of the stale 5-item Phase 5/6-era list, reconciled against the accurate narrative section elsewhere in the same document
…ction Fix stale filter primitive enumeration in introduction.md's SvgCodec narrative (feFlood/feGaussianBlur/feOffset/feComposite/feMerge only), found during Phase 8 final formal-review recheck as inconsistent with the just-corrected svg-codec.md design doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved moderate findings remain across CSS behavior, filter correctness and tolerance, and requirements traceability.
Review effort: Lite
Findings: 5
Open (5)
What changed in this PR
Expands SVG rendering support with CSS styling, aspect-ratio handling, patterns, images, masking, and advanced filters.
Changes:
- Adds CSS parsing, selectors, cascading, and fixtures.
- Implements additional geometry, paint servers, images, and filter primitives.
- Updates documentation, requirements traceability, tests, and review configuration.
| File | Summary |
|---|---|
test/DemaConsulting.CanvasNet.Tests/SvgFixtures/README.md |
Registers SVG fixtures. |
test/DemaConsulting.CanvasNet.Tests/SvgFixtures/css-styling.svg |
Adds CSS styling coverage. |
test/DemaConsulting.CanvasNet.Tests/Codecs/SvgFixtureTests.cs |
Adds fixture assertions. |
test/DemaConsulting.CanvasNet.Tests/CanvasNetTests.cs |
Adds integration coverage. |
src/DemaConsulting.CanvasNet/Codecs/SvgCodec.cs |
Updates SVG integration and documentation. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Text.cs |
Adds percentage geometry handling for text. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Shapes.cs |
Adds percentage-based shape geometry. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.PreserveAspectRatio.cs |
Implements aspect-ratio fitting. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.PathRender.cs |
Adds pattern rendering. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Parsing.cs |
Applies root fitting behavior. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Gradients.cs |
Adds bounds-based paint mapping. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.Tile.cs |
Implements feTile. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.Morphology.cs |
Implements feMorphology. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.FeImage.cs |
Implements feImage. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.DropShadow.cs |
Implements feDropShadow. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.DisplacementMap.cs |
Implements displacement mapping. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.Convolution.cs |
Implements convolution filters. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.ComponentTransfer.cs |
Implements component-transfer filters. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.ColorMatrix.cs |
Implements color-matrix filters. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Filters.Blend.cs |
Implements blend-mode evaluation. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Css.Selectors.cs |
Parses and matches CSS selectors. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Css.Parser.cs |
Parses CSS rules and declarations. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Core.cs |
Adds filter and resource safety limits. |
src/DemaConsulting.CanvasNet/Codecs/Svg/SvgCodec.Attributes.cs |
Adds percentage parsing. |
README.md |
Updates SVG capability documentation. |
docs/user_guide/introduction.md |
Documents expanded SVG support. |
docs/sysml2/model/canvas-net/codecs/svg-codec.sysml |
Updates SysML traceability. |
docs/reqstream/canvas-net/codecs.yaml |
Updates subsystem requirements and verification. |
docs/reqstream/canvas-net.yaml |
Updates system requirements and verification. |
.reviewmark.yaml |
Registers reviewed files. |
.cspell.yaml |
Adds technical vocabulary. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…t-combinator backtracking, and CSS cascade coverage for filter/clip-path/mask/clip-rule/stop-color/stop-opacity - Add missing feBlend overlay mode (Overlay(Cb,Cs) = HardLight(Cs,Cb) per CSS Compositing Level 1), the one blend mode omitted from Phase 8's original 14. - Fix CSS selector matching: descendant-combinator segments previously committed to the nearest matching ancestor without backtracking, causing selectors like 'g > .a .target' to under-match when the nearest .a wasn't the one satisfying the child combinator. Now backtracks recursively through every candidate ancestor. Updated CssMatchWorkBudget's cost model from an additive to a multiplicative (saturating) formula to correctly bound the new backtracking algorithm's real worst-case cost. - Route filter/clip-path/mask/clip-rule/stop-color/stop-opacity through the same 3-tier CSS cascade chokepoint (ResolveElementProperty) already used for fill/stroke/font/marker properties, so stylesheet rules and inline style declarations now actually apply to these previously cascade-bypassed, XML-attribute-only properties. Added a per-element style context cache to avoid double-charging the CSS budget across the multiple call sites that now resolve properties on the same element. Investigated 2 further Copilot findings (feColorMatrix/feComponentTransfer allegedly needing premultiplied-alpha handling) and determined both are false positives: this codebase's Surface.PremultiplyAlpha docs state straight/unassociated alpha is the representation every codec uses, and the SVG Filter Effects spec explicitly defines both primitives to operate on non-premultiplied values - the existing straight-byte implementation is already correct per spec and per this codebase's own architecture. Test count: 4899 -> 4926, all passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for catching this - fixed in ca525dc. Presentation properties (\filter, \clip-path, \mask, \clip-rule, \stop-color, \stop-opacity) now route through the same 3-tier CSS cascade chokepoint (\ResolveElementProperty) already used for fill/stroke/font/marker properties. Added regression tests proving a stylesheet-only rule (no XML attribute) now correctly applies \filter/\clip-path/\mask. Also added a per-element style-context cache so the extra call sites don't double-charge the CSS work budget. |

Summary
Full SVG rendering-completeness roadmap: 8 phases implemented on a shared long-lived branch, each independently code-reviewed and formally reviewed locally (per cost-saving workflow - see phase notes below) before this single consolidated PR.
Phases
<pattern>paint server, tile-wrap math,patternUnits/patternContentUnits/patternTransform.<image>element - raster embedding viadata:URIs (externalhrefis a documented safe no-op; no filesystem/network access).feColorMatrix(all types),feComponentTransfer,feMorphology,feConvolveMatrix,feDisplacementMap,feTile,feDropShadow,feImage.feDiffuseLighting/feSpecularLighting(feDistantLight/fePointLight/feSpotLight),feTurbulence(spec-exact Perlin noise).<style>element / selector support - parser, selector matching, 3-tier cascade (presentation attribute < stylesheet rule < inlinestyle).feBlend(all 14 CSS Compositing Level 1 blend modes) + afeComposite operator="arithmetic"fix (was a silent no-op, causing filter chains combining lighting + arithmetic composites to render as opaque rectangular blobs instead of following the source shape's silhouette).Verification
build.ps1(0 warnings) andlint.ps1(markdownlint-cli2, cspell, yamllint, dotnet format, reqstream, versionmark, reviewmark, sysml2tools) before and after every fix.code-reviewpass (several found real defects via executed reproduction cases and hand/spec-verified numeric checks, not just static inspection) and aformal-reviewpass (compliance/traceability/documentation rigor), with fixes re-reviewed until clean.docs/reqstream/canvas-net.yaml) and subsystem (docs/reqstream/canvas-net/codecs.yaml) levels for every new requirement; SysML2 modelsourceRefkept current.SvgFixtures/InkscapeFilters.svg, 59 distinct filter definitions) compared against ImageMagick's reference rendering of the same file.Test plan
pwsh ./build.ps1- 4899/4899 tests passing, 0 warningspwsh ./lint.ps1- cleancode-reviewandformal-reviewpasses per phase, all approvedCo-authored-by: Copilot 223556219+Copilot@users.noreply.github.com