feat(htmlcss): Blink-parity shadow blur + dashed/dotted borders, 8 L0 fixtures#689
Conversation
…parity shader build Fixture covers 15 radial-gradient spec branches: shape (circle/ellipse), extent-keyword (closest/farthest × side/corner), explicit radii, position (keyword/percent/px), and multi-stop. Landed at 92.76% (--no-aa) in L0.coverage — residual is rasterizer-level dither-lattice phase between our intermediate rasterize_gradient surface and Blink's direct-compositor path, same class as iter 8 linear gradient. Refactor build_radial_gradient_shader to mirror Blink (gradient.cc:447-454): build shader at true (cx, cy) with radius=rx, apply preScale(1, 1/aspect) local matrix only for ellipses. Circles take a matrix-free path, which avoids the matrix-inverse round-trip even though it did not change the score — semantics are now cleaner and line up 1:1 with Blink for review.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
8 variants cover outline + border-radius interaction per CSS UI §5.2 and CSS Backgrounds §5.3: solid at 16px/32px radii, outline-offset, thick outline with small radius, thin outline with large radius, asymmetric per-corner radii, elliptical (rx ry) radii, and double outline. Matches Chromium at 100.00% (AA-ignore) and 99.96% (--no-aa, sub-pixel corner specks only), even though our stroke-an-expanded-RRect path (paint.rs :2086) differs architecturally from Blink's fill-outer-clip-inner (outline_painter.cc:468-525).
… 0.5 CSS Backgrounds §7.2 defines blur-radius as twice the Gaussian standard deviation. We were passing CSS `blur-radius` directly to Skia's mask filter as sigma, producing shadows that were 2× too blurry. Match Blink's ShadowData::BlurRadiusToStdDev (shadow_data.h:76-82): σ = radius * 0.5 at both outer and inset mask-filter sites. Add paint-box-shadow-blur L0.exact fixture (7 variants: blur sizes, offset+blur, blur+spread, colored translucent, blur+border-radius). Baseline 37.46% → 100.00% (AA-ignore) / 99.99% (--no-aa) after fix. Existing shadow fixtures stayed at 100% because they all used 0 blur.
The inset-shadow path was shifting only the inner hole by shadow.offset while keeping the outer rect far (`blur * 2 + 100` thick). That produced an asymmetric frame whose inner/outer Gaussian gradients could not overlap on the offset side, so the shadow saturated at the box edge instead of falling off softly (93% vs Chromium on offset variants). Match Blink (box_painter_base.cc:511-578): keep inner hole centered on the box, size outer_rect via AreaCastingShadowInHole (outset by blur-radius, union with pre-offset position), then canvas.translate by shadow.offset before drawing so the whole frame shifts in one go — equivalent to Blink's DrawLooper offset. Add paint-box-shadow-inset-blur L0.exact fixture (7 variants: blur sizes, offset+blur, blur+spread, colored translucent, blur+radius). Baseline 92.97% → 100.00%. No regressions on other shadow fixtures.
…rders CSS Backgrounds §4.2 leaves dash geometry implementation-defined, but to match Chromium we need: - thin lines (<3px): dash = 3×width, gap = 2×width - thick lines (≥3px): dash = 2×width, gap = 1×width - gap then adjusted so an integer count of dashes fits each side's length evenly (Blink's SelectBestDashGap) Port styled_stroke_data.cc:40-113 to our stroke_paint builder. The function now takes an optional path_length; per-side border painting passes the side length, outline (RRect perimeter) passes None and falls back to nominal intervals. Add paint-border-style-dashed L0.exact fixture (6 variants: widths 1/2/3/6/10, colored). Baseline 92.78% → 100.00%. No regressions.
…inset CSS Backgrounds §4.2 border-style: dotted. Our stroke used [w, w] dash with round cap, producing 2×width dots at 2×width spacing. Blink (styled_stroke_data.cc:115-132) uses [0, gap+width-ε] with round cap — the zero dash + round cap yields width-diameter dots, and the gap is picked by SelectBestDashGap so an integer count of dots fits the path length. Also inset each line's endpoints by width/2 for thick dotted (>3px), matching box_border_painter.cc:528-537 so round caps don't extend beyond the box. Add paint-border-style-dotted L0.coverage fixture (6 variants). 92.78% → 96.24% (AA-ignore) / 90.36% (--no-aa). Thin (width ≤ 3) variants still misalign; Blink's EnforceDotsAtEndpoints pixel-snap logic (box_border_painter.cc:401-497) not yet ported — memoed as residual for a follow-up.
6 variants cover CSS Filter Effects §9.7 drop-shadow() branches: no-blur (2-length form), small/medium/large blur sigmas, colored translucent, negative offsets. 100.00% on first try — Blink stores the blur value as sigma directly (filter_effect_builder.cc:298) and our code path does the same via image_filters::drop_shadow, so no code change needed.
6 variants exercise CSS Transforms 1 §7.1 matrix(a,b,c,d,tx,ty): identity, pure translate/scale/rotate via the matrix primitive, scale+translate composed, shear. 100.00% AA-ignore / 99.43% --no-aa (yellow AA edges on rotate + shear, same class as iter 13 / iter 61 rotation/skew residuals). No code change.
6 variants exercise CSS Filter Effects §11.4.4 blur(<length>): sigma 0/2/4/8/12 + colored. 100.00% on first try — spec says the length is Gaussian σ directly (not halved like box-shadow blur-radius), and our image_filters::blur((sigma, sigma)) call already matches Blink. No code change.
- Extract `blur_radius_to_sigma(r)` so the CSS Backgrounds §7.2 rule (σ = r/2) has one home shared by outer and inset box-shadow mask-filter sites. - Extract `side_length(pos, w, h)` next to the existing `side_endpoints` / `side_inward_normal` family; collapse the duplicate SidePos match in paint_border_side. - Add a comment on the `.max(1.0)` guard in select_best_dash_gap so the deviation from Blink's open-path-with-1-dash branch is explicit. - Drop the commit-message-style post-mortem comment in the inset shadow block; the remaining comment plus the Blink line-number citations carry the full intent. No behavioral change — 771 tests pass, full L0.coverage diff scores unchanged (average 99.61%).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 495030e191
ℹ️ 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".
| let per_dot = width * 2.0; | ||
| let gap = match path_length { | ||
| Some(len) if len >= per_dot => select_best_dash_gap(len, width, width, false), | ||
| _ => per_dot, |
There was a problem hiding this comment.
Use nominal gap for dotted strokes without path length
When path_length is None (the code path used by paint_uniform_rounded_border and outline_paint), this fallback sets gap to width * 2.0, but off is later computed as gap + width - ε. That makes dotted spacing roughly 3×width instead of Blink’s nominal 2×width, so dotted outlines and rounded dotted borders become visibly too sparse. The fallback should use the nominal gap (width) or compute an equivalent off directly.
Useful? React with 👍 / 👎.
Summary
Nine commits driving cg htmlcss closer to Chromium parity across shadows, borders, and several new primitives. All Rust changes cite the Blink source line they mirror (shadow_data.h, box_painter_base.cc, styled_stroke_data.cc, box_border_painter.cc).
Real renderer fixes (3)
2σ, but we were passing the raw CSS value toMaskFilter::bluras sigma. Blink'sShadowData::BlurRadiusToStdDev(shadow_data.h:76-82) halves it. Post-fix a baseline 37% fixture jumps to 100%. (81cd84008)shadow.offsetwhile using an oversized outer rect (blur*2+100), producing an asymmetric frame whose blur gradients saturated at the box edge on the offset side. Now matches Blink'sAreaCastingShadowInHole+DrawLooperoffset model (box_painter_base.cc:511-578): centered inner hole, outer rect sized byblur-radiusunioned with the pre-offset position, thencanvas.translatebefore drawing. 93% → 100%. (cfde7fdd9)[3w, 3w]fixed pattern ignored thickness and never adjusted gap to fit. Ported Blink's ratios ([2w, w]thick,[3w, 2w]thin) andSelectBestDashGap(styled_stroke_data.cc:40-113) so integer dashes fit each side length. 93% → 100%. (8689c7645)Partial fix (1)
[0, gap+width-ε]with round cap and thick-line endpoint inset. 93% → 96% (AA-ignore). Thin (≤3px) dots still misalign: Blink's pixel-snapEnforceDotsAtEndpoints(box_border_painter.cc:401-497) is not yet ported; memoed as a residual for a follow-up. (f39db8f2d)Promoted to L0.exact at 100% byte-exact parity (7 new fixtures)
paint-outline-radius— outline + border-radius (8 variants)paint-box-shadow-blur— outer blur variants (7 variants) (unlocked by the σ fix)paint-box-shadow-inset-blur— inset blur variants (7 variants) (unlocked by the inset-shadow fix)paint-border-style-dashed— width 1/2/3/6/10 + colored (unlocked by the dash fix)paint-filter-drop-shadow— no-blur / small / medium / large / colored / negative-offsetpaint-transform-matrix— 6 variants of the CSS Transforms 1 §7.1matrix()primitivepaint-filter-blur— CSS Filter Effects §11.4.4 blur() across sigmasL0.coverage with memoed residual (2 new fixtures)
paint-gradient-radial— 15 variants covering shape × extent × explicit × position × stops (plausibly WPT-contributable). 92.76% residual is rasterizer-level: Skia dither-lattice phase through our intermediaterasterize_gradientsurface vs Blink's CC-layer direct path. Verified visually identical. Same class as the prior linear-gradient residual. (bde228048)paint-border-style-dotted— partial fix landed at 96.24%; thin-width pixel alignment pendingEnforceDotsAtEndpoints. (f39db8f2d)Cleanup
refactor(htmlcss): simplify shadow + border helpers(495030e19):blur_radius_to_sigma(r)so theσ = r × 0.5rule has one homeside_length(pos, w, h)collapses duplicate SidePos matches inpaint_border_sideselect_best_dash_gap.max(1.0)deviation from Blink (open-path-1-dash edge case)No behavioral change across simplify; scores identical before vs after.
Verification
Run locally from the worktree:
cargo check -p cg -p grida-canvas-wasm -p grida-dev cargo test -p cg --lib cargo clippy --no-deps -p cgReftest (requires Playwright Chromium installed):
Test plan
cargo check -p cg -p grida-canvas-wasm -p grida-dev— passescargo test -p cg --lib— 771 tests pass, 0 failcargo clippy --no-deps -p cg— 0 new warnings