fix(painting): build the ring without a path-ops difference - #3
Merged
Conversation
`Path.combine(PathOperation.difference, ...)` is unreliable on Flutter web (CanvasKit): it silently returns the first operand. `ring` and `halo` were built with it, so on web both came back as the filled shape instead of a ring, and every ring-confined layer — stroke, bloom, the pulse glows — washed across the whole child instead of hugging the border. The effect was plainly visible on the Pages gallery: card interiors flooded with colour where iOS and the goldens show a thin edge glow. For the built-in shapes `inner` is always simple and nested inside `outer`, so the even-odd fill of the two contours in one path describes exactly the same region with no path-ops pass, and renders identically on every backend — every golden passes unchanged. A custom `BeamPathContour` can be self-intersecting (the star contour is), where even-odd and non-zero disagree and the shortcut would punch a hole of its own, so those keep the path-ops route. Custom contours therefore still render wrong on web; that needs an inverse clip and costs a `saveLayer`, so it is left alone here. Verified by rebuilding `example` for web and comparing against the same gallery rendered through the Skia test renderer at identical size. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
On the Pages gallery the beam does not hug the border — it floods the whole card face. iOS, macOS and the goldens are all correct, so this reads as a web-only rendering bug rather than a renderer quality difference.
BeamRingGeometrybuilds the ring by subtracting the content box from the outer shape:PathOperation.differencesilently returns the first operand on Flutter web (CanvasKit). Soringandhalo(reach)come back as the filled shape, and every layer meant to be confined to the border — stroke, bloom, the pulse glows — paints across the entire child.Evidence
A minimal probe, no package code, same widget tree rendered both ways:
combine(difference, rrect, rrect)combine(difference, superellipse, superellipse)combine(difference, hand-built arc path ×2)combine(intersect, …)PathFillType.evenOddintersectis fine, so it isdifferencespecifically, and the path construction style is irrelevant.The fix
A
_subtracthelper that expresses the ring as one even-odd path instead of a path-ops difference. For the built-in shapesinneris always simple and nested insideouter, so even-odd describes exactly the same region — no path-ops pass, identical output on every backend.Verification
dart format,flutter analyzeclean.flutter test— all 1197 pass, every golden unchanged, so Skia/Impeller output is pixel-identical.examplefor web and screenshotted it against the same gallery rendered through the Skia test renderer at identical size: they now match.Known gap
A custom
BeamPathContourcan be self-intersecting (the star contour insurface_golden_test.dartis), where even-odd and non-zero disagree and the shortcut would punch a hole of its own. Those keep the path-ops route, so custom contours still render wrong on web. Fixing that needs an inverse clip via adstOutmask layer, which costs asaveLayerand would break the budget table insave_layer_budget_test.dart— left out of this change deliberately.No regression test is added: the bug is renderer-specific and invisible to the Skia-backed suite. Catching it would need browser tests (
flutter test --platform chrome), which the repo does not set up yet.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes a Flutter web rendering bug where
PathOperation.differencereturned the filled outer shape instead of a border ring. Built-in rings and halos now use an even-odd path, keeping strokes and glows on the border while preserving existing Skia and Impeller output.Scope
BeamPathContourvalues still use path subtraction and remain affected on web.flutter analyzepasses, and all 1,197 tests pass with unchanged goldens.Written for commit bc24105. Summary will update on new commits.