Draw a keycap emoji as one keycap glyph #32

Merged
natsukium merged 1 commit from fix/keycap-vs16 into main 2026-10-06 16:03:56 +09:00
Owner

Closes #30.

A keycap emoji (#️⃣, 1️⃣) drew as a tiny # beside an empty keycap. swash 0.2.10 drops VS15/VS16 only after an emoji base; after # it shapes the VS16 as a .notdef with a full advance, which sits between # and U+20E3 during GSUB and splits the font's keycap ligature into three glyphs. The cluster fit then shrank all three into two cells.

Shaper::shape_cluster now shapes the cluster without the variation selectors (U+FE00..FE0F, U+E0100..E01EF) that the chosen face does not map; the base is always kept, and a selector the face maps directly stays.

  • The presentation selectors have already done their job by picking the face.
  • swash never consults the cmap's variation-sequence subtable, so an ideographic variation selector cannot pick its variant today either; dropping it changes no glyph. The code comment and text-shaping.md name the revisit trigger (swash gaining variant mapping).

Tests: the keycap shapes to the same single glyph as #⃣ on the emoji face (three glyphs [4, 0, 21] without the fix); a keycap cell needs no fit (fit_px 5 without the fix, 0 with it); a unit test covers the filter (IVS, FE00, mapped selector, lone selector).

Before (top) / after (bottom), offscreen render with Moralerspace Neon HW: the keycaps reach full size; ❤️ ⭐︎ 👍🏻 and 葛󠄀 ≩︀ are unchanged.

before and after

Closes #30. A keycap emoji (`#️⃣`, `1️⃣`) drew as a tiny `#` beside an empty keycap. swash 0.2.10 drops VS15/VS16 only after an emoji base; after `#` it shapes the VS16 as a `.notdef` with a full advance, which sits between `#` and U+20E3 during GSUB and splits the font's keycap ligature into three glyphs. The cluster fit then shrank all three into two cells. `Shaper::shape_cluster` now shapes the cluster without the variation selectors (U+FE00..FE0F, U+E0100..E01EF) that the chosen face does not map; the base is always kept, and a selector the face maps directly stays. - The presentation selectors have already done their job by picking the face. - swash never consults the cmap's variation-sequence subtable, so an ideographic variation selector cannot pick its variant today either; dropping it changes no glyph. The code comment and `text-shaping.md` name the revisit trigger (swash gaining variant mapping). Tests: the keycap shapes to the same single glyph as `#⃣` on the emoji face (three glyphs `[4, 0, 21]` without the fix); a keycap cell needs no fit (`fit_px` 5 without the fix, 0 with it); a unit test covers the filter (IVS, FE00, mapped selector, lone selector). Before (top) / after (bottom), offscreen render with Moralerspace Neon HW: the keycaps reach full size; ❤️ ⭐︎ 👍🏻 and 葛󠄀 ≩︀ are unchanged. ![before and after](https://git.natsukium.com/attachments/ebab2f1a-97f0-49d5-87f5-34551d2fbb8f)
shaping: drop variation selectors the cluster's face does not map
All checks were successful
darwin / detect relevant changes (pull_request) Successful in 8s
windows / detect relevant changes (pull_request) Successful in 11s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / windows docs-only no-op (pull_request) Has been skipped
pr / nix flake check (pull_request) Successful in 36s
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m3s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m48s
pr / wire schema is compatible with the base (pull_request) Successful in 5s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 45s
windows / cargo nextest (Windows) (pull_request) Successful in 4m13s
windows / cargo clippy (Windows cross) (pull_request) Successful in 15s
windows / frontend smoke (Windows) (pull_request) Successful in 1m15s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
041cfbd4c0
A keycap emoji (#️⃣, 1️⃣) drew as a tiny `#` beside an empty keycap.
swash 0.2.10 keeps a variation selector that follows a non-emoji base as
a shaping character, maps it through the cmap to glyph 0, and leaves
that .notdef between `#` and U+20E3 during GSUB, which splits the font's
keycap ligature into three glyphs; the cluster fit then shrank all three
into two cells.

shape_cluster now shapes the cluster without the selectors (FE00..FE0F,
E0100..E01EF) the chosen face does not map. The presentation selectors
have already done their job by picking the face. swash never consults
the cmap's variation-sequence subtable, so an ideographic variation
selector cannot select its variant either, and dropping it changes no
glyph while keeping it out of ligature lookups. A selector the face maps
directly is kept, so the rule follows the face's coverage, not the text.

Closes #30

Assisted-by: Claude Code
natsukium deleted branch fix/keycap-vs16 2026-10-06 16:03:57 +09:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
felis-terminal/felis!32
No description provided.