Skip to content

htmlcss: promote paint-opacity + box-padding to L0.exact#685

Merged
softmarshmallow merged 3 commits intohtmlcssfrom
feature/nice-satoshi-568a9f
Apr 22, 2026
Merged

htmlcss: promote paint-opacity + box-padding to L0.exact#685
softmarshmallow merged 3 commits intohtmlcssfrom
feature/nice-satoshi-568a9f

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented Apr 22, 2026

Summary

Two Chromium-parity promotions for the cg htmlcss renderer, driven through the dev-cg-htmlcss-feature 5-phase loop.

1. paint-opacity — float-native alpha fix

  • crates/grida-canvas/src/htmlcss/paint.rs:120 used set_alpha((opacity * 255.0) as u8). Rust as u8 truncates toward zero, so opacity values that don't align to u8 boundaries diverged from Blink by 1 unit per channel.
  • Example: opacity: 0.5 on black-over-white yielded rgb(127,127,127) in cg vs rgb(126,126,126) in Chromium; 0.25 yielded (191,191,191) vs (190,190,190).
  • Blink's opacity stacking-context layer lands in SkCanvas::saveLayerAlphaf(bounds, float)SkPaint::setAlphaf. WebRender similarly carries f32 end-to-end.
  • Fix: swap to set_alpha_f(style.opacity). skia-safe's float-native API maps straight to SkPaint::setAlphaf.
  • Before: similarity 0.9600 (19,200 / 480,000 diff pixels). After: 1.0000 (0 / 480,000).

2. box-padding — fixture stabilization (no code change)

The 912-pixel divergence on box-padding was entirely fixture noise, not a renderer bug. Three anti-patterns from the skill:

  • .outer { border-radius: 8px } / .inner { border-radius: 4px } — decoration unrelated to the padding subject; removed.
  • .inner content was font-shaped text "content"; its advance width varied ~1px between engines and propagated to outer block width via stretch. Replaced with explicit width: 80px; height: 24px.
  • .label was auto-sized by text advance width, driving flex-item max-content width, which then drove .outer stretch width. Pinned with .label { width: 200px; height: 16px }.

Before: similarity 0.9932 (912 / 133,200 diff pixels). After: 1.0000 (0 / 153,600). No cg code change needed.

3. Suite promotions

  • fixtures/test-html/suites/L0.exact.json now gates three fixtures at floor: 1.0, threshold: 0, aa: false:
    • box-dimensions (pre-existing)
    • box-padding (new — this PR)
    • paint-opacity (new — this PR)
  • paint-opacity.html also strips inner text from its opacity boxes (text-under-opacity-layer hits a second-order Chromium compositing path, even with color: transparent).
  • box-padding viewport bumped from 222 → 256 to match the new natural document height after the label width change.

Test plan

  • cargo check -p cg -p grida-canvas-wasm -p grida-dev clean
  • cargo test -p cg --lib htmlcss passes (149/149, including the pre-existing test_render_opacity)
  • just fmt clean; lefthook pre-commit (oxfmt + cargo-fmt + clippy) passes
  • Chromium reftest via @grida/reftest shows L0.exact at floor=1.0 for all three fixtures after the change
  • Before/after diff PNGs reviewed per-row for the sub-1.0 residuals (opacity 0.5 box confirmed 126→126 matching Chromium; 0.75 confirmed no residual)

Follow-ups

  • Separate chip was flagged during this work: @grida/reftest compare CLI silently drops --threshold and --json flags (defaults win regardless of CLI arg). Library path works correctly. Small bug, separate PR.
  • paint-border-radius (0.9894) and paint-background-solid (0.9792) remain in L0.coverage; next candidates for future iterations.

Summary by CodeRabbit

  • Bug Fixes

    • Improved opacity handling for layered rendering to preserve full-float transparency for smoother, more accurate visuals.
  • Tests

    • Updated and added visual test fixtures and viewport settings; adjusted padding examples and removed displayed percentage text from opacity demos.
  • Documentation

    • Added guidance on captions and labels for test fixtures to prevent incidental text from affecting layout and standardize authoring.

Two Chromium-parity fixes for the cg htmlcss renderer, both driven to
byte-exact via the dev-cg-htmlcss-feature loop.

- paint-opacity: replace `set_alpha((opacity * 255.0) as u8)` with
  `set_alpha_f(opacity)` in htmlcss/paint.rs so opacity compositing is
  float-native, matching Blink's `SkCanvas::saveLayerAlphaf` path.
  The u8 truncation diverged by 1 per channel at non-255-aligned
  opacity values (0.5 → 127 vs Chromium's 126; 0.25 → 63 vs 64).
  paint-opacity fixture goes from 0.9600 → 1.0000 similarity.
- box-padding: fixture-hygiene only (no code change). Removed
  unrelated border-radius on .outer/.inner, replaced font-shaped
  inner "content" text with explicit `width: 80px; height: 24px`,
  pinned `.label { width: 200px; height: 16px }` to eliminate
  font-advance-width-driven flex item sizing. Fixture goes from
  0.9932 → 1.0000 similarity.

Both fixtures also strip inner text from their visual probes to
avoid text-under-opacity-layer / font-shaping noise in the
pre-gate phase.

L0.exact now gates box-dimensions, box-padding, and paint-opacity at
floor 1.0.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Apr 22, 2026 4:41pm
docs Ready Ready Preview, Comment Apr 22, 2026 4:41pm
grida Ready Ready Preview, Comment Apr 22, 2026 4:41pm
viewer Ready Ready Preview, Comment Apr 22, 2026 4:41pm
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
backgrounds Ignored Ignored Preview Apr 22, 2026 4:41pm
code Ignored Ignored Apr 22, 2026 4:41pm
legacy Ignored Ignored Apr 22, 2026 4:41pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 249676f2-b35b-497d-b2c8-af90274440af

📥 Commits

Reviewing files that changed from the base of the PR and between a5d4b29 and ffd5c98.

📒 Files selected for processing (1)
  • .agents/skills/fixtures/SKILL.md

Walkthrough

Replace 8-bit quantized layer alpha with Skia's floating-point alpha when creating opacity/save-layers in the canvas paint path; update HTML/CSS fixtures and test-suite entries/viewport and add fixture-authoring guidance.

Changes

Cohort / File(s) Summary
Canvas paint change
crates/grida-canvas/src/htmlcss/paint.rs
Use Paint::set_alpha_f(style.opacity) for save-layer alpha instead of quantizing via (style.opacity * 255.0) as u8.
HTML fixtures (visual tests)
fixtures/test-html/L0/box-padding.html, fixtures/test-html/L0/paint-opacity.html
Adjust CSS/layout: add explicit dimensions, remove decorative text/border-radius, and clear displayed percentage text from opacity boxes; simplify inner elements.
Test suites / viewports
fixtures/test-html/suites/L0.coverage.json, fixtures/test-html/suites/L0.exact.json
Update box-padding.html viewport height from 222 → 256; add box-padding.html and paint-opacity.html entries to the exact suite and adjust JSON formatting.
Fixtures docs & guidance
fixtures/test-html/README.md, .agents/skills/fixtures/SKILL.md
Add "Captions and labels" guidance: keep incidental text short, pin label container dimensions, and prefer using a hide-text stylesheet for non-subject text.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Suggested labels

cg, canvas, enhancement

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The linked issue #39 references fixing a typo in README.md, but the PR changes multiple files including paint.rs and fixtures; alignment with this single issue is unclear. Clarify whether issue #39 encompasses all changes in this PR or if additional linked issues should be referenced.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: promoting two fixtures (paint-opacity and box-padding) to L0.exact suite.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives: the paint.rs fix, fixture updates, viewport adjustments, and documentation improvements are all in-scope for promoting paint-opacity and box-padding to L0.exact.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/nice-satoshi-568a9f

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/grida-canvas/src/htmlcss/paint.rs (1)

118-120: Consolidate opacity handling across painter paths to avoid precision loss.

The htmlcss layer fix correctly uses set_alpha_f(style.opacity), but related painter code still quantizes opacity: painter/painter.rs (lines 403, 593, 2632) and painter/gradient.rs (lines 88, 137, 190, 277) convert to integer alpha via (opacity * 255.0) as u32/u8. This mixing of quantized and float-based opacity can cause subtle rendering mismatches. Standardize on float-based opacity (e.g., set_alpha_f, set_alpha_f(gradient.opacity)) or clearly document why specific paths require integer quantization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida-canvas/src/htmlcss/paint.rs` around lines 118 - 120, The
codebase mixes float-based alpha (Paint::set_alpha_f) with integer-quantized
alpha casts (e.g., (opacity * 255.0) as u8/u32) in painter/painter.rs and
painter/gradient.rs; update those places to use float alpha consistently by
calling set_alpha_f(...) with the original opacity (e.g.,
set_alpha_f(gradient.opacity) or set_alpha_f(style.opacity)) instead of
computing integer alpha, and remove the (opacity * 255.0) as ... conversions so
all painting paths use float alpha and avoid precision loss.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/grida-canvas/src/htmlcss/paint.rs`:
- Around line 118-120: The codebase mixes float-based alpha (Paint::set_alpha_f)
with integer-quantized alpha casts (e.g., (opacity * 255.0) as u8/u32) in
painter/painter.rs and painter/gradient.rs; update those places to use float
alpha consistently by calling set_alpha_f(...) with the original opacity (e.g.,
set_alpha_f(gradient.opacity) or set_alpha_f(style.opacity)) instead of
computing integer alpha, and remove the (opacity * 255.0) as ... conversions so
all painting paths use float alpha and avoid precision loss.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30f89a69-a8b6-4a73-af24-8c4f541cd798

📥 Commits

Reviewing files that changed from the base of the PR and between d94eece and 072d24d.

📒 Files selected for processing (5)
  • crates/grida-canvas/src/htmlcss/paint.rs
  • fixtures/test-html/L0/box-padding.html
  • fixtures/test-html/L0/paint-opacity.html
  • fixtures/test-html/suites/L0.coverage.json
  • fixtures/test-html/suites/L0.exact.json

Add a short section to the test-html README clarifying that captions
next to specimens are fine — the mistake is letting them drive layout
or forgetting that hide-text.css already neutralizes color/shaping
noise when text is incidental. Motivated by a reftest iteration where
labels were stripped instead of pinning the enclosing dimensions.
Extend the Labeled specimens bullet to spell out two things the
test-html README already says: keep label text short, and pin the
dimensions of any container holding a label so font-advance-width
differences can't propagate into geometry. Also point to
`hide-text.css` as the text-neutralizer so labels stay useful to
future readers instead of getting stripped mid-reftest cycle.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cg Core Graphics css

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant