Skip to content

Render programmatic text decoration#507

Merged
wieslawsoltes merged 4 commits into
masterfrom
fix/text-decoration-programmatic
May 9, 2026
Merged

Render programmatic text decoration#507
wieslawsoltes merged 4 commits into
masterfrom
fix/text-decoration-programmatic

Conversation

@wieslawsoltes
Copy link
Copy Markdown
Owner

PR Summary

Fixes #278.

Summary

This change makes programmatically assigned SvgText.TextDecoration values render in the Skia scene graph text pipeline. Previously, parsed SVG markup with a raw text-decoration attribute could flow through the renderer, but documents created in code could set TextDecoration = SvgTextDecoration.LineThrough, serialize correctly as text-decoration="line-through", and still render without the decoration in PNG output.

The renderer now checks the normal text-decoration attribute value when the parser-specific raw text-decoration custom attribute is absent. This preserves the existing raw SVG parsing behavior for authored SVG values while allowing enum-backed programmatic values such as LineThrough to participate in the same decoration drawing pipeline.

Implementation Details

  • Added a named text-decoration attribute constant in SvgSceneTextCompiler.
  • Kept raw parsed SVG text-decoration handling as the first path, so existing markup parsing behavior remains unchanged.
  • Added a fallback for programmatic attribute values exposed through TryGetAttribute("text-decoration", ...).
  • Reused the existing SVG token parser for SVG string values such as line-through.
  • Added enum-name parsing for programmatic values such as LineThrough.
  • Rejected numeric enum strings in the fallback parser so arbitrary numeric values are not treated as valid SVG text-decoration input.

Test Coverage

Added an end-to-end PNG rendering regression test:

  • Builds a SvgDocument in code.
  • Creates one text element with SvgTextDecoration.None.
  • Creates a second otherwise identical text element with SvgTextDecoration.LineThrough.
  • Renders both documents through SKSvg.FromSvgDocument(...).Save(...).
  • Compares the resulting images and asserts that the line-through version changes the rendered pixels.

Validation

Ran the focused affected test sets on net10.0:

dotnet test tests/Svg.Skia.UnitTests/Svg.Skia.UnitTests.csproj -f net10.0 -c Release --no-restore --filter "FullyQualifiedName~SKSvgTests" --logger "console;verbosity=minimal"
dotnet test tests/Svg.Skia.UnitTests/Svg.Skia.UnitTests.csproj -f net10.0 -c Release --no-restore --filter "FullyQualifiedName~SvgSceneTextCompilerTests" --logger "console;verbosity=minimal"

Results:

  • SKSvgTests: 8 passed, 0 failed.
  • SvgSceneTextCompilerTests: 19 passed, 0 failed.

Notes

The full solution build with --no-restore was not usable in this fresh worktree before committing because many project.assets.json files were missing, and the local environment also lacks the net461 reference assemblies required by the full solution target graph. The focused net10.0 tests above were restored and then rerun with --no-restore.

@pull-request-size pull-request-size Bot added size/L and removed size/M labels May 9, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0e7b65471a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +3988 to +3991
if (element.CustomAttributes.TryGetValue(SvgStyleAttributeNames.RawTextDecorationAttributeKey, out var rawValue))
{
return TryParseTextDecorationValue(rawValue, out decorations) &&
HasRenderableDecorations(decorations);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not let stale raw text-decoration shadow programmatic values

When a document was loaded from SVG markup that already had a text-decoration attribute, SvgElementFactory leaves the raw value in CustomAttributes, and the TextDecoration property setter only updates Attributes. Because this branch returns as soon as the raw value exists, a later programmatic assignment is ignored for those parsed elements; for example loading text-decoration="none" and then setting TextDecoration = SvgTextDecoration.LineThrough still renders no decoration, while setting None over an originally underlined element still uses the old raw underline.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in f584514ce2f35838f8ad1b6e2ed0d7519d9b98d8.

The fix keeps the raw text-decoration cache authoritative for freshly parsed SVG, so invalid authored values such as comma-separated decoration lists are still rejected. The stale-cache problem is handled at the local attribute collection layer instead: when an existing text-decoration attribute is set again, the cached __svgskia:text-decoration-raw custom attribute is removed before the change notification. That matches the programmatic SvgText.TextDecoration setter path without changing external submodules.

I added regression tests for both cases called out here:

  • parsed text-decoration="none" followed by TextDecoration = SvgTextDecoration.LineThrough
  • parsed text-decoration="line-through" followed by TextDecoration = SvgTextDecoration.None

I also reran the comma-separated resvg text-decoration row that caught the raw parsing edge case.

Validation:

  • dotnet format Svg.Skia.slnx --no-restore --include src tests samples
  • dotnet test tests/Svg.Skia.UnitTests/Svg.Skia.UnitTests.csproj -f net10.0 -c Release --no-restore --filter "FullyQualifiedName~SKSvgTests.Save_ProgrammaticTextDecorationLineThrough_RendersDecoration|FullyQualifiedName~SKSvgTests.Save_ParsedTextDecoration|FullyQualifiedName~resvgTests.a_text_decoration"
  • dotnet build Svg.Skia.slnx -c Release
  • dotnet test Svg.Skia.slnx -c Release

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f584514ce2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (TryUnboxedCheck(oldVal, value))
{
base[attributeName] = value;
RemoveStaleRawTextDecoration(attributeName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear stale raw decoration on first assignment

Fresh evidence: the new cleanup runs only in the existing-attribute branch, so parsed elements whose authored text-decoration was kept only as raw text still keep RawTextDecorationAttributeKey after a programmatic assignment. For example, if the converter rejects a value such as text-decoration="underline solid red", Attributes has no text-decoration; setting TextDecoration = SvgTextDecoration.LineThrough takes the insert path, leaves the raw value, and TryGetOwnTextDecoration still returns from that stale raw value before looking at the enum, so the programmatic decoration is ignored. Clear the raw key whenever text-decoration is assigned, including the insert path.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Text-Decoration is not rendered

1 participant