Skip to content

🐛 [BUG] - CanvasTextRenderer tints color-emoji glyphs with node.color, crushing native emoji colors #1

@rwaltenberg

Description

@rwaltenberg

Description

CanvasTextRenderer rasterizes all text with a hardcoded context.fillStyle = 'white' (CanvasTextRenderer.ts:57 equivalent). Emoji glyphs bypass fillStyle and render with their browser-native color fonts, producing a canvas texture with white text pixels + natively colored emoji pixels. The text node is then drawn through the standard GPU shader, which multiplies the texture by the node's color prop (propagated to colorTl/Tr/Bl/Br) as a tint.

That works fine when node.color is near-white — white × near-white = near-white for text glyphs, and emoji × near-white barely changes. It breaks visibly when node.color is dark: text glyphs tint to the desired color, but emoji pixels get multiplied down to near-black silhouettes, losing all their native color information.

The practical trigger is any multi-theme app — in dark theme text color is typically near-white and emoji look correct, but in light theme text color is near-black and every color emoji flattens to a black blob.

Reproduction URL

Minimal scenario (no playground; <Text> with color + emoji suffices):

// Dark-theme path — looks correct, near-native emoji color
<Text color={0xf1f5f9ff}>🔥 hello</Text>

// Light-theme path — emoji renders as a near-black silhouette
<Text color={0x0f172aff}>🔥 hello</Text>

Reproduction steps

1. In any @solidtv/solid app using CanvasTextRenderer, render a <Text color={...}> containing a color emoji
2. Set color to a near-white value (e.g. 0xf1f5f9ff) — emoji appears roughly natural (slight dimming)
3. Set color to a near-black value (e.g. 0x0f172aff) — emoji flattens to a near-black silhouette

Screenshots

Image

Browsers

  • Chromium

Browser version

Chrome 140+ (dev). Also reproducible on WebOS Chromium builds.

OS

  • Linux (dev)

Additional context (root cause + options we considered)

Two moving parts:

1. CanvasTextRenderer hardcodes fillStyle = 'white'. Since emoji ignore fillStyle, the canvas texture ends up mixing "white glyphs for text" with "native-color pixels for emoji". All text coloring then has to come from the shader tint.

2. The shader tint for a text node is the same color prop users set to color their text. There's no way to say "tint text to X but leave emoji alone".

Architectural footnote we hit while patching locally: CoreNode's constructor does this.props = {} and field-copies the incoming props. CoreTextNode.textProps keeps a reference to the original incoming props object, so after the first color setter runs, this.textProps.color and this.props.color diverge — CanvasTextRenderer reads the stale one. That's a separate pre-existing issue but any fix to the emoji problem needs to deal with it (or the rasterizer reads the wrong color).

We ruled out a client-side workaround (splitting emoji runs out of Text into separate untinted nodes) because it breaks inline flow and requires threading through every <Text> site.

Possible fix directions — deferring to maintainer's preference on API shape before proposing a PR:

  • (a) Bake node.color into CanvasTextRenderer's fillStyle and default the shader tint to white for canvas text nodes. Fixes emoji cleanly; text rendering is mathematically equivalent (white × color vs. color × white). Behavior change: gradient-tinted text (non-uniform colorTl/Tr/Bl/Br) would lose the gradient, since corners are forced white.
  • (b) Add a separate opt-in prop (e.g. textColor, or a preserveEmojiColor flag) that switches to (a)'s behavior per-node. Opt-in, no existing behavior changes.
  • (c) Leave as-is; document the tradeoff and let apps handle emoji text separately.

Option (a) matches what apps almost always want; option (b) is the safe incremental path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions