Skip to content

Add advanced SVG filter effects and glass/neumorphic/neon themes#156

Merged
jongalloway merged 2 commits intomainfrom
advanced-svg-filter-themes
Mar 31, 2026
Merged

Add advanced SVG filter effects and glass/neumorphic/neon themes#156
jongalloway merged 2 commits intomainfrom
advanced-svg-filter-themes

Conversation

@jongalloway
Copy link
Copy Markdown
Owner

Summary

Adds five new SVG filter effects to the rendering pipeline and three new built-in theme presets that showcase them.

New SVG Filter Effects

Filter Technique Use Case
frosted-glass Ambient shadow + inner top-edge highlight Translucent glass depth
neumorphic Dual light/dark offset shadows Embossed/extruded appearance
glass-glow Blurred color-boosted screen blend Neon glow on dark backgrounds
inner-glass Inset composite shadow Recessed panel look
ambient-shadow Soft omnidirectional blur Subtle floating elevation

New Built-in Themes

  • glass — Translucent tinted nodes with frosted-glass filter, semi-transparent palette, stronger borders
  • neumorphic — Uniform surface with dual-shadow embossing, subtle stroke, no gradients
  • neon — Dark background with multi-stop gradient borders and glass-glow filter

Changes

  • SvgRenderSupport.cs — Refactored AppendShadowFilterDefs from if-chain to switch; added 5 new filter cases
  • SvgNodeWriter.cs — Generalized shadow filter application to work with any non-none ShadowStyle
  • DiagramRenderer.cs — Extended ApplyShadowStyle with parameter clamping for all new filter types
  • Theme.cs — Added 3 new preset themes with GetByName/BuiltInThemeNames registration (25 → 28 themes)
  • README.md — Updated theme count and gallery with glass, neumorphic, and neon entries

Testing

  • All 1,117 tests pass (1,016 unit + 101 E2E)
  • 9 new unit tests covering filter output and frontmatter integration
  • 3 new E2E snapshot fixtures with baselines

Add five new SVG filter effects to the rendering pipeline:
- frosted-glass: ambient shadow + inner top-edge highlight for glass depth
- neumorphic: dual light/dark offset shadows for embossed appearance
- glass-glow: blurred color-boosted screen blend for neon glow
- inner-glass: inset shadow for recessed panel look
- ambient-shadow: soft omnidirectional shadow

Add three new built-in theme presets that showcase these filters:
- glass: translucent tinted nodes with frosted-glass filter
- neumorphic: uniform surface with dual-shadow embossing
- neon: dark background with gradient borders and glass-glow

Generalize SvgNodeWriter to apply filters for any non-none ShadowStyle.
Extend DiagramRenderer.ApplyShadowStyle with parameter clamping for all
new filter types. Update README theme gallery with the three new themes.

All 1117 tests pass (1016 unit + 101 E2E).
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds advanced SVG filter-based shadow/effect styles to the rendering pipeline and introduces three new built-in themes (glass, neumorphic, neon) that showcase those effects.

Changes:

  • Refactors SVG shadow filter emission to a switch-based implementation and adds new filter definitions (frosted-glass, neumorphic, glass-glow, inner-glass, ambient-shadow).
  • Generalizes node shadow application so any non-none shadow style can be applied.
  • Adds built-in themes + unit/E2E fixture coverage for the new styles and updates README theme gallery/count.

Reviewed changes

Copilot reviewed 10 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/DiagramForge/Rendering/SvgRenderSupport.cs Adds/implements new SVG <filter> definitions and refactors style dispatch to a switch.
src/DiagramForge/Rendering/SvgNodeWriter.cs Broadens node shadow applicability beyond just soft/glow.
src/DiagramForge/DiagramRenderer.cs Extends frontmatter shadowStyle handling with new styles and parameter clamping.
src/DiagramForge/Models/Theme.cs Registers new built-in theme names and adds Glass, Neumorphic, and Neon presets.
tests/DiagramForge.Tests/Rendering/SvgRendererPaletteTests.cs Adds unit tests asserting new filters are emitted for new themes/styles.
tests/DiagramForge.Tests/DiagramRendererTests.cs Adds a frontmatter-driven test for advanced shadowStyle filter emission.
tests/DiagramForge.E2ETests/Fixtures/mermaid-xychart-prism.expected.svg Updates snapshot baseline (formatting/output change).
tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-glass.input Adds E2E fixture input for the new glass theme.
tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-glass.expected.svg Adds E2E expected SVG baseline for glass theme rendering.
tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neumorphic.input Adds E2E fixture input for the new neumorphic theme.
tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neumorphic.expected.svg Adds E2E expected SVG baseline for neumorphic theme rendering.
tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neon.input Adds E2E fixture input for the new neon theme.
tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neon.expected.svg Adds E2E expected SVG baseline for neon theme rendering.
README.md Updates theme count and gallery to include glass/neumorphic/neon.

sb.AppendLine($"{indent} <filter id=\"{shadowFilterId}\" x=\"-16%\" y=\"-12%\" width=\"140%\" height=\"148%\" color-interpolation-filters=\"sRGB\">");
sb.AppendLine($"{indent} <feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"{F(theme.ShadowBlur * 2.5)}\" result=\"blur\"/>");
sb.AppendLine($"{indent} <feOffset in=\"blur\" dx=\"0\" dy=\"{F(Math.Max(theme.ShadowOffsetY, 3))}\" result=\"offset\"/>");
sb.AppendLine($"{indent} <feFlood flood-color=\"{Escape(theme.ShadowColor)}\" flood-opacity=\"{F(theme.ShadowOpacity * 0.7)}\" result=\"shadow-color\"/>");
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the ambient-shadow filter, flood-opacity is computed as theme.ShadowOpacity * 0.7 without clamping. If ShadowOpacity is set > ~1.43 (or negative) via JSON/custom theme, this can emit an invalid SVG opacity value outside [0,1]. Consider clamping the computed opacity to [0,1] (or at least Math.Min(…, 1.0) / Math.Clamp).

Suggested change
sb.AppendLine($"{indent} <feFlood flood-color=\"{Escape(theme.ShadowColor)}\" flood-opacity=\"{F(theme.ShadowOpacity * 0.7)}\" result=\"shadow-color\"/>");
sb.AppendLine($"{indent} <feFlood flood-color=\"{Escape(theme.ShadowColor)}\" flood-opacity=\"{F(Math.Max(0.0, Math.Min(theme.ShadowOpacity * 0.7, 1.0)))}\" result=\"shadow-color\"/>");

Copilot uses AI. Check for mistakes.
theme.ShadowStyle = "neumorphic";
theme.UseNodeShadows = true;
theme.ShadowOpacity = Math.Clamp(theme.ShadowOpacity <= 0 ? 0.50 : theme.ShadowOpacity, 0.30, 0.70);
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 4.00 : theme.ShadowBlur, 2.00, 6.00);
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shadowStyle: neumorphic does not reset ShadowOffsetX/ShadowOffsetY, but the neumorphic filter definition adds/subtracts fixed offsets (+/-5). If the base theme already has offsets (e.g., a theme with ShadowOffsetY=1.4), the resulting dark/light shadows become asymmetric and shifted. Consider explicitly setting ShadowOffsetX = 0 and ShadowOffsetY = 0 for the neumorphic frontmatter style to make it deterministic and consistent with the built-in Neumorphic preset.

Suggested change
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 4.00 : theme.ShadowBlur, 2.00, 6.00);
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 4.00 : theme.ShadowBlur, 2.00, 6.00);
theme.ShadowOffsetX = 0;
theme.ShadowOffsetY = 0;

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +69
&& !string.IsNullOrEmpty(theme.ShadowStyle)
&& !string.Equals(theme.ShadowStyle, "none", StringComparison.OrdinalIgnoreCase)
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyNodeShadow treats whitespace-only ShadowStyle values as enabled (IsNullOrEmpty returns false) which triggers a shadow filter lookup even though AppendShadowFilterDefs will trim it to empty and emit nothing. Using string.IsNullOrWhiteSpace(theme.ShadowStyle) (and/or comparing against theme.ShadowStyle.Trim()) would avoid this wasted work and keep the logic aligned with the trimming done in AppendShadowFilterDefs.

Suggested change
&& !string.IsNullOrEmpty(theme.ShadowStyle)
&& !string.Equals(theme.ShadowStyle, "none", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(theme.ShadowStyle)
&& !string.Equals(theme.ShadowStyle.Trim(), "none", StringComparison.OrdinalIgnoreCase)

Copilot uses AI. Check for mistakes.
@jongalloway
Copy link
Copy Markdown
Owner Author

@copilot apply changes based on the comments in this thread

…ffsets, use IsNullOrWhiteSpace for shadow style

Agent-Logs-Url: https://github.com/jongalloway/DiagramForge/sessions/e947f672-253e-4984-bccb-969bbe3c7eca

Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 31, 2026

@copilot apply changes based on the comments in this thread

Applied all three fixes in commit 01506c5:

  1. SvgRenderSupport.csflood-opacity in the ambient-shadow filter is now clamped to [0,1] via Math.Clamp(theme.ShadowOpacity * 0.7, 0.0, 1.0).
  2. DiagramRenderer.cs — The neumorphic frontmatter case now explicitly sets ShadowOffsetX = 0 and ShadowOffsetY = 0, making the dual-shadow effect deterministic regardless of the base theme's offsets.
  3. SvgNodeWriter.csapplyNodeShadow guard now uses string.IsNullOrWhiteSpace and compares against theme.ShadowStyle.Trim(), preventing a spurious filter lookup on whitespace-only values.

@jongalloway jongalloway merged commit 12dde0b into main Mar 31, 2026
5 checks passed
@jongalloway jongalloway deleted the advanced-svg-filter-themes branch March 31, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants