Skip to content

text: project kerning offset onto rotated text-flow axis#1046

Open
benface wants to merge 1 commit into
not-fl3:masterfrom
benface:fix-rotated-text-kerning
Open

text: project kerning offset onto rotated text-flow axis#1046
benface wants to merge 1 commit into
not-fl3:masterfrom
benface:fix-rotated-text-kerning

Conversation

@benface

@benface benface commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #1045.

Summary

draw_text_ex always adds kerning_offset to dest.x, ignoring TextParams.rotation. At rotation = 0 text flows along +X and that's correct; at rotation = π / 2 text flows along +Y so the offset lands perpendicular to the text-flow axis. Multiplying by (rot_cos, rot_sin) routes the offset into the correct axis for any rotation.

Cause

In draw_text_ex:

let rot_cos = rot.cos();
let rot_sin = rot.sin();
let dest_x = (offset_x + total_width) * rot_cos + (glyph_scaled_h + offset_y) * rot_sin;
let dest_y = (offset_x + total_width) * rot_sin + (-glyph_scaled_h - offset_y) * rot_cos;

let dest = Rect::new(
    dest_x / dpi_scaling + x + kerning_offset / dpi_scaling,  // ← always +X
    dest_y / dpi_scaling + y,                                  //   never +Y
    ...
);

(offset_x + total_width) accumulates along the unrotated text-flow axis (+X in glyph-local space) and the rotation matrix correctly routes it into dest_x / dest_y. The kerning offset is an additional text-flow advance but gets added to dest.x without the same routing.

Fix

Project kerning_offset (logical-pixel-converted, same as the existing / dpi_scaling) onto the rotation basis vectors:

let kerning_logical = kerning_offset / dpi_scaling;
let dest = Rect::new(
    dest_x / dpi_scaling + x + kerning_logical * rot_cos,
    dest_y / dpi_scaling + y + kerning_logical * rot_sin,
    ...
);

At rotation = 0: rot_cos = 1, rot_sin = 0 → identical to the previous behaviour.
At rotation = π / 2: rot_cos = 0, rot_sin = 1 → offset routes into dest.y.
Any other angle: offset projects onto the rotated flow direction.

total_width += char_data.advance * font_scale_x + kerning_offset; (line 386) is unchanged — total_width stays in unrotated glyph-local space and is itself routed through the rotation matrix on the next iteration.

Verification

Tested with the originally-reported reproducer: a font with kerning entries (OstrichSans-Heavy) drawn at rotation = π / 2. Before the patch, kerned pairs in strings like RESTART LEVEL show per-character perpendicular shifts in screen space that vary with the surrounding kerning context. With the patch, the shift disappears and the rotated text reads as a clean baseline.

The kerning offset added by not-fl3#1035 is always added to `dest.x`,
ignoring `TextParams.rotation`. At `rot = 0` text flows along +X
and that's correct; at `rot = π/2` text flows along +Y so the
offset lands perpendicular to the text-flow axis instead of along
it. Multiply by `(rot_cos, rot_sin)` to route the offset into the
correct axis for any rotation.
@benface

benface commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

FYI: the failing wasm32-unknown-unknown job is a pre-existing master regression — also red on ed9cdbd, dce417ad, and dd57be6 (this PR's base). Last green CI on master was 44afa9d. Other five jobs pass.

@not-fl3

not-fl3 commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Thanks for PR!

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.

Rotated text: kerning offset added to dest.x regardless of rotation angle (regression in 0.4.15)

2 participants