Skip to content

fix(text): colour alpha, rich_text whitespace and baseline, gradient_text angle and stops - #392

Merged
LeadcodeDev merged 2 commits into
mainfrom
fix/text-components
Sep 27, 2026
Merged

LeadcodeDev merged 2 commits into
mainfrom
fix/text-components

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Closes #366. Closes #367. Closes #368. Refs #388.

⚠️ One behaviour change to review before merging

#368 changes what an existing gradient_text angle renders as. The angle was applied as (cos θ, sin θ); it now follows the CSS convention (sin θ, −cos θ), so 0° is to top and 90° is to right. The default stays 90, and now genuinely renders left-to-right.

A scenario that set an explicit angle to compensate for the old axis will render differently. That is the fix, not a side effect — but it is worth a look at any file that tuned an angle by eye.

#366 — text ignored the alpha of style.color

paint.set_alpha_f(1.0) immediately after paint_from_hex(color) threw the parsed alpha away.

style.color's alpha channel (0x12 = 18/255, about 7%) must survive to the
painted pixels instead of being forced fully opaque; got max alpha 255

hex-colors.md already documented #RRGGBBAA generically — that claim is now true for text too.

#367 — rich_text: two independent bugs

Leading spaces. Tokenization always used split_whitespace(), collapsing runs. A white-space: pre|nowrap branch now keeps each span verbatim and disables wrapping, mirroring what text already does for those values.

white-space: pre must keep the leading spaces verbatim
  left:  "AB"
  right: "    AB"

The 9 px offset. baseline_offset was (line_height + ascent) / 2, missing the descent; text uses (line_height + ascent − descent) / 2.

rich_text's baseline must be computed as (line_height + ascent - descent) / 2
… got top 22, expected 13

A 9 px gap — exactly the issue's own measurement.

#368 — gradient_text: angle, line length, and stops

Beyond the angle convention above, the gradient line length was the box diagonal regardless of angle. It is now the CSS box projection (|w·sinθ| + |h·cosθ|) / 2, so the first and last colour land on the first and last glyph edge at any angle. The pre-fix symptom is the issue's own: the left glyph edge read (152, 0, 103) — muddy purple — instead of the first colour.

New optional stops (color + position), falling back to even spacing when omitted. Pinned as backward-compatible: even=(50, 205, 0), skewed=(50, 205, 0) was the pre-fix failure, identical colours proving stops was ignored.

Verification

Seven tests, each proven red before the fix by hand-editing the code back rather than by git stash (worktree safety). cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (1601) all clean. Comment count still 0 — the only /// added are on GradientTextStop and the new stops field, both JsonSchema-derived.

Also found

The gradient_text early-return guard would have skipped rendering for a scenario using stops with an explicit colors: []. Widened, since it is the same code path.

Written comment-free, per the codebase-wide rule from #345.

…adient angle

Three text components silently diverged from their own documented model:

- text forced style.color's alpha to 1.0 after parsing it, so a translucent
  color rendered fully opaque. rich_text, backgrounds and borders already
  respected alpha; text was the outlier. Just stop clobbering it.

- rich_text tokenized every span with split_whitespace(), which collapses
  runs of spaces to a single one — so white-space: pre (or nowrap) lost
  leading/internal spaces that the plain text component preserves. It also
  computed its baseline as (line_height + ascent) / 2, dropping descent,
  which text's formula includes; that alone put rich_text ~9px lower than
  an identical text at the same font size, so swapping one for the other
  visibly jumped. Pre/nowrap now keeps each span's text verbatim as a single
  token with no re-wrapping, and the baseline formula now matches text's.

- gradient_text's default angle (90) rendered vertical because the angle was
  applied as (cos, sin) — a top/bottom axis — while CSS defines 90deg as
  "to right". It also always drew the gradient across the box diagonal
  regardless of angle, so a wide/short text sampled only the unsaturated
  middle of the ramp. angle now follows the CSS convention (0 = to top,
  90 = to right — same meaning as view.background's linear-gradient) and
  the gradient line is the CSS box projection (|w*sin(a)| + |h*cos(a)|),
  so the first and last colors land exactly on the first and last glyph
  edges at any angle. This changes what the same angle number renders as;
  scenarios that set an explicit angle to work around the old axis need
  a look. A new optional `stops` field (color + position) lets a scenario
  place colors explicitly instead of the always-even default spacing,
  which stays the fallback when stops is omitted.

Each fix has a pixel-level test that fails on the pre-fix code:
- text::tests::a_translucent_color_alpha_channel_survives_to_the_painted_pixels
- rich_text::tests::white_space_pre_keeps_leading_spaces_as_a_single_literal_token
- rich_text::tests::white_space_pre_preserves_leading_spaces_at_the_pixel_level
- rich_text::tests::baseline_offset_matches_the_text_components_formula
- gradient_text::tests::default_angle_ramps_left_to_right_across_the_glyphs
- gradient_text::tests::gradient_line_length_is_the_box_projection_not_the_diagonal
- gradient_text::tests::explicit_stops_override_even_color_spacing
@LeadcodeDev LeadcodeDev added the bug Something isn't working label Sep 27, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 27, 2026
@LeadcodeDev
LeadcodeDev merged commit 8f3523f into main Sep 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

1 participant