Skip to content

Fix 1px borders vanishing when one side is zero-width or at fractional scales - #902

Draft
Shinyaigeek wants to merge 1 commit into
DioxusLabs:mainfrom
Shinyaigeek:repro-issue-837
Draft

Shinyaigeek wants to merge 1 commit into
DioxusLabs:mainfrom
Shinyaigeek:repro-issue-837

Conversation

@Shinyaigeek

Copy link
Copy Markdown

Summary

Fixes #837.

border: 1px solid #ff0000;
border-top-width: 0;
border-radius: 8px;

should draw the left, right and bottom sides, but Blitz drew nothing at all. Two independent bugs were behind it, in different layers; the first one alone erases the outline at every scale, the second one erases it at fractional device pixel ratios even once the first is fixed.

1. blitz-paint: a zero-width side made the corner split angle NaN

start_angle() in kurbo_css/css_box.rs decides where a rounded corner's arc is split between its two adjacent sides, from the ratio of their border widths. With bt_width == 0 the closed form evaluates w = 0, k = ry / (w·rx) = inf, √k / (√k + √2) = inf / inf = NaN. The arc then contains NaN segments, and because draw_border batches all same-coloured edges into a single fill (to avoid AA seams), Vello silently drops the whole outline — not just the one edge.

Fix: return the limiting angle for a zero-width side up front (bt == 0 → π/2, br == 0 → 0, both zero → π/4), i.e. the whole arc belongs to the side that has a width. This is the limit of the existing formula, not a different geometry.

2. blitz-dom: layout rounding zeroed one-device-pixel borders at fractional scales

Stylo already snaps lengths to whole device pixels, so at scale 1.5 a 1px border reaches layout as 0.667 CSS px. taffy::round_layout then rounds each edge to a whole CSS pixel from its cumulative position and takes the difference — which can put both edges of such a border on the same integer:

right edge:            497.33 → 497
right edge − 0.667:    496.67 → 497
rounded border width:  0

Which side gets hit depends on where the fractional part lands (1.25× for the reporter, 1.5× in the test here).

Fix: resolve_layout now calls a round_layout in blitz-dom that is taffy's algorithm verbatim (cumulative-position rounding, widths as the difference of two rounded edges, so no gaps open between boxes) but snaps to the device pixel grid: round(v · scale) / scale with scale = viewport.scale(), the same value handed to stylo as device_pixel_ratio. A border that stylo made exactly one device pixel wide therefore stays one device pixel wide. At scale 1 this is bit-for-bit v.round(), so WPT and existing tests are unaffected; at scale 2 layout now lands on integer device pixels rather than even ones.

Taffy is a git dependency here, so this lives in blitz-dom rather than changing taffy::round_layout's signature.

Tests

  • css_box.rs: handles_zero_width_sides unit test for start_angle.
  • tests/blitz-tests/tests/thin_border_one_side_zero.rs:
    • renders the issue's CSS at scale 1 and counts red pixels per edge (top 0, other three present) — isolates bug 1;
    • the same, plus radius-less and all-sides variants, at 1.25 / 1.5 / 1.75 — bug 2;
    • checks final_layout().border is a whole, non-zero number of device pixels at 1.0 / 1.25 / 1.5 / 1.75 / 2.0 without going through paint.

All three fail on main and pass with this change. cargo test for blitz-tests, blitz-dom, blitz-paint, stylo_taffy, plus cargo fmt --check and clippy, are clean.

Before / after (scale 1.5, border-top-width: 0 + border-radius: 8px):

before after
all four sides missing left / right / bottom drawn, top absent

🤖 Generated with Claude Code

https://claude.ai/code/session_01SCkGi9MbYao9rnHMLiGq1D

…l scales

Fixes DioxusLabs#837. Two independent bugs each erased `border: 1px solid;
border-top-width: 0; border-radius: 8px` entirely:

blitz-paint: `start_angle()` splits a corner arc between its two adjacent
sides from the ratio of their widths. A zero-width side made that
`inf / inf = NaN`, and since all same-coloured edges are filled as one
path the NaN corner dropped the whole outline. Return the limiting angle
for a zero-width side instead (the arc belongs entirely to the other side).

blitz-dom: `taffy::round_layout` snaps edges to whole CSS pixels, but stylo
already snaps lengths to device pixels, so at a fractional scale a `1px`
border is `1 / scale` CSS px wide and its two edges could round to the same
value, leaving a zero-width border to paint. Replace it with a rounding pass
on the device pixel grid (`round(v * scale) / scale`), identical to taffy's
at scale 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCkGi9MbYao9rnHMLiGq1D

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1px borders sometimes not rendering

1 participant