Repository navigation
Conversation
A cluster carrying `U+FE0F` (VS16) explicitly requests the emoji presentation per UTS linebender#51, but font selection ignored the request: the emoji family is appended *after* the requested families, so a text font that happens to cover the base codepoint wins and the cluster renders as a monochrome text glyph. `❤️` (`U+2764 U+FE0F`) is the motivating case. `U+2764` has a dingbat glyph in common UI text fonts (DejaVu Sans, Inter, …), so the text font matches first and the emoji family is never reached — the selector that asks for emoji presentation has no effect. Emoji with no text-font glyph (`🎉`, `🇯🇵`) are unaffected, which is why this is easy to miss. The emoji family cannot simply be tried first in general: `is_emoji` is the raw Emoji/Extended_Pictographic property, which is also true for `5`, `#` and `▶` — codepoints whose default presentation is text and which would then wrongly render as emoji. The request has to be honoured only where it is actually made. So track which presentation, if either, a cluster explicitly asks for, and put the emoji family first only for VS16: - Add `Presentation` (`Unspecified` / `Text` / `Emoji`) and record it on `CharCluster` while filling it. This is deliberately finer-grained than the existing `is_variation_selector()`, which cannot distinguish the two emoji variation selectors — they request *opposite* presentations. - In `select_font`, order the emoji family before the requested families when the cluster requests `Presentation::Emoji`. `U+FE0E` (VS15) requests the text presentation, so it keeps the existing ordering, which already prefers the text font whenever one covers the codepoint. Verified against a bundled CBDT emoji font with Inter as the primary family: `❤️` now resolves to the emoji font while `❤` (no selector), `5`, `#` and Latin text all still resolve to Inter. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
|
Apparently that macOS failure does point towards a real bug with the approach that might be worth further consideration. In particular it now prioritizes an emoji font over an explicitly configured font family that happens to also have emoji versions of the character. So what we would need to know to solve this properly is how a given font renders it so we can decide if that font is a valid candidate, that seems related to the #695 issue I opened too where the selection logic also doesn't have all the information to make an informed decision. |
Contributor
Collaborator
|
We may well also want to support font-variant-emoji as a style. |
Member
|
(#693 (comment) also applies here). I do suspect that the approach in #620 is more complete; I'm reviewing that PR now. |
nicoburns
pushed a commit
to nicoburns/parley
that referenced
this pull request
Sep 16, 2026
…er#811) LLM Contributions: Unknown for linebender#620, c-emoji. I used them for review, bootstrapping the new crate to follow https://linebender.org/wiki/formatting-scheme/, and getting/implementing the technique of sticking the properties into our existing property infrastructure, plus rebasing/reordering the series. Adaptation of linebender#620 by @fundon. This uses a state machine based on @chansen's [c-emoji](https://github.com/chansen/c-emoji) to implement detection of Emoji sequences, as described in [tr-51](https://www.unicode.org/reports/tr51/tr51-29.html). This is Unicode 17 Emoji, as opposed to Unicode 18 (releasing tomorrow...), but I don't think the difference has a huge impact for us. The state machine lives in a new crate `parley-emoji`, so as to follow c-emoji's license. We were unable to get its code relicensed under our MIT/Apache 2.0 license. With this, we are *not* completely compliant with Unicode Emoji, but this gives us a significantly better jumping off point. Things I've chosen to defer: - Better testing. I have local changes which generate a suitable subset of Noto Color Emoji, but I think end-to-end testing needs more care. - Also not yet tested using the upstream Unicode data. - Splitting a single grapheme cluster into its 'text presentation/emoji presentation' preferred sections. This can happen in three cases that I'm aware of: - Prepend character then Emoji, all within one grapheme cluster - Non-Emoji modifier base, then Fitzgerald skin tone modifier - ZWJ sequence containing VS15 text variation selectors. - Having the Emoji font in the fallback chain, even if text presentation is preferred. - Thinking through how the 'reject' case should be handled - Doing the proper rendering of malformed tag sequences, which is required by spec, but which as far as I know, no system implements. - [#font-tech > Appearance of unsupported emoji sequences](https://xi.zulipchat.com/#narrow/channel/296640-font-tech/topic/Appearance.20of.20unsupported.20emoji.20sequences/with/623471132). - Verifying that the handling of RI sequences is correct - in particular, the way that three regional indicators in a row merge into grapheme clusters can lead to jank. - Fix the editing behaviour to use this properly (see also linebender#693) - Documenting our level of compliance with the Unicode Emoji spec. - Handling legacy modifier sequences with an intervening VS16. There's a TODO in the code for this. - The 'emoji presentation locale' thing. I don't believe any of those are blocking - this is still a better state than before. Fixes linebender#744. Supersedes linebender#620, linebender#788, linebender#692. cc @kane50613 (wrt linebender#745). **Changelog** > ### Fixed > > - Parley's detection of Emoji is now more robust. --------- Co-authored-by: Fangdun Tsai <fundon@pindash.io> Co-authored-by: Tom Churchman <thomas@churchman.nl>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A cluster carrying
U+FE0F(VS16) explicitly requests the emoji presentation per UTS #51, but font selection ignored the request: the emoji family is appended after the requested families, so a text font that happens to cover the base codepoint wins and the cluster renders as a monochrome text glyph.❤️(U+2764 U+FE0F) is the motivating case.U+2764has a dingbat glyph in common UI text fonts (DejaVu Sans, Inter, …), so the text font matches first and the emoji family is never reached — the selector that asks for emoji presentation has no effect. Emoji with no text-font glyph (🎉,🇯🇵) are unaffected, which is why this is easy to miss.The emoji family cannot simply be tried first in general:
is_emojiis the raw Emoji/Extended_Pictographic property, which is also true for5,#and▶— codepoints whose default presentation is text and which would then wrongly render as emoji. The request has to be honoured only where it is actually made.So track which presentation, if either, a cluster explicitly asks for, and put the emoji family first only for VS16:
Presentation(Unspecified/Text/Emoji) and record it onCharClusterwhile filling it. This is deliberately finer-grained than the existingis_variation_selector(), which cannot distinguish the two emoji variation selectors — they request opposite presentations.select_font, order the emoji family before the requested families when the cluster requestsPresentation::Emoji.U+FE0E(VS15) requests the text presentation, so it keeps the existing ordering, which already prefers the text font whenever one covers the codepoint.Verified against a bundled CBDT emoji font with Inter as the primary family:
❤️now resolves to the emoji font while❤(no selector),5,#and Latin text all still resolve to Inter.