fix(paint): wire style.clip-path into the paint pass - #348
Merged
Merged
Conversation
`CssStyle::clip_path` was declared, exported in the JSON Schema with seven variants and their documentation, and read by nothing. A scenario that set it validated, rendered, and looked exactly as though the property were absent. The only three `clip_path` call sites in the tree were Skia's own `Canvas::clip_path`, unrelated to the CSS property. Present since 676e49e. Five variants now build a Skia path and clip with it: `Inset` (with optional corner radius), `Circle`, `Ellipse`, `Polygon` and `Path`. `None` stays indistinguishable from the property being absent. Percentages resolve against the border box, per axis: `inset`'s verticals on the height and horizontals on the width, `ellipse`'s `rx` on the width and `ry` on the height, `polygon`'s `x`/`y` likewise. `circle`'s radius uses the CSS reference, `sqrt(w² + h²) / √2`, rather than a convenient `min(w, h)` — a 50% circle on a non-square box should match what a browser draws. `path`'s data is offset by the box's own origin, so `M0 0` is the element's corner and not the video's. The clip opens before the outset shadows and closes after the inset ones, so the element's whole rendering is masked — background, border and shadow included. That is CSS `clip-path`, and it is deliberately the opposite of the placement `overflow: hidden` uses here: that one opens after the decorations, sparing the node's own outset shadow, which an existing test pins. `NodePath { id }` is left unimplemented: reading another node's geometry needs an `id → resolved path` lookup the paint pass does not have, and #339 itself separates that variant from the other five for this reason. It is not left silent, though — a silent no-op is the defect being fixed here. It writes to stderr and says what to do instead. Tracked separately. Seven tests. Six fail against the unwired property, including the sample the issue reports: a square with `clip-path: inset{right: 120}` painted at x=330 just as it did at x=200. End to end on a rendered still, x=330 goes from rgb(139, 92, 246) to rgb(0, 0, 0) while x=200 stays violet. `rules/clip-path.md` documents the six shapes, the local-coordinates trap on `path`, the eight-point polygon for a chamfered frame, and that `node-path` is declared but inert. Before this the property's only mention in the skills was a line in `timeline-sequencing.md` saying it cannot be animated, while the exported schema advertised it globally — which is what made it read as working. Closes #339
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.
Closes #339.
The fault
CssStyle::clip_pathwas declared, exported in the JSON Schema with sevenvariants and their documentation, and read by nothing. A scenario that set it
validated, rendered, and looked exactly as though the property were absent. The
three
clip_pathhits in the tree were all Skia's ownCanvas::clip_path,unrelated to the CSS property. Present since 676e49e.
What now works
kindinset(+ optionalradius)circlesqrt(w² + h²) / √2— the CSS referenceellipserxon the width,ryon the heightpolygonxon the width,yon the heightpathnonecircleuses the spec's reference length rather than a convenientmin(w, h), soa 50% circle on a non-square box matches what a browser draws.
The placement is the opposite of
overflow: hidden, on purposeclip-pathmasks the element itself — background, border and outer shadowincluded — so the clip opens before the outset shadows and closes after the
inset ones.
overflow: hiddendoes the reverse here: it opens after the decorations,deliberately sparing the node's own outset shadow, which
overflow_hidden_does_not_clip_own_outset_box_shadowalready pins. Bothbehaviours are now covered, so neither can drift into the other.
node-pathis left out, but not left silentReading another node's geometry needs an
id → resolved pathlookup the paintpass does not have —
PaintContexthas no node-id map, and #328's dependencygraph resolves scalar properties, not path geometry. #339 separates that variant
from the other five for exactly this reason.
It would have been easy to leave it returning
None. That is the defect this PRfixes, so instead it writes to stderr and names the workaround (repeat the same
kind: pathon both nodes). Tracked separately for a follow-up.Verification
Seven tests, six of which fail against the unwired property — including the
sample from the issue:
The seventh (
clip_path_none_paints_exactly_as_no_clip_path_at_all) passes bothways, which is correct:
nonemeans no clip.End to end on a rendered still, a 400×400 violet square with
clip-path: inset{right: 120}:cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warningsand
cargo test --workspace(1521) all clean.Why it read as working, and the doc fix for that
The issue makes the point precisely: the property's only mention anywhere in the
skills was a line in
timeline-sequencing.mdsaying it cannot be animated —which implies it can be set — while the exported schema advertised it globally
with all its variants. That is what a generator reads.
New
rules/clip-path.mdcovers the six shapes, how each one's percentagesresolve, the local-coordinates trap on
path(a path copied from an editor whoseviewBox does not start at the origin arrives offset), the eight-point
polygonfor the chamfered frame #344 asks for, and that
node-pathis declared but inert.Linked from
SKILL.md's style table and its rules index.Note on comments
Written comment-free, per the codebase-wide rule being applied in #345.