Skip to content

[ports] CoreText: honor SkFontArguments COLR/CPAL palette selection#270

Draft
mattleibow wants to merge 1 commit into
skiasharpfrom
dev/coretext-colr-palette
Draft

[ports] CoreText: honor SkFontArguments COLR/CPAL palette selection#270
mattleibow wants to merge 1 commit into
skiasharpfrom
dev/coretext-colr-palette

Conversation

@mattleibow

Copy link
Copy Markdown
Collaborator

Summary

CoreText exposes no public API to select a COLR/CPAL palette and always renders the font's default palette (index 0); the only color input it honors is the CGContext fill color, which it maps to the COLR foreground entry. As a result SkTypeface::makeClone() with a non-default SkFontArguments::Palette (selected index and/or per-entry overrides) had no visual effect on macOS/iOS, while FreeType (Linux) and DirectWrite (Windows) honor it.

This is the CoreText half of SkiaSharp's SKTypeface.Clone(paletteIndex) / SKFontArguments.PaletteOverrides being silently ignored on Apple platforms.

Fix

Honor the requested palette using public API only. At clone/load time (onMakeClone and MakeFromStream):

  1. Open the font's sfnt bytes (already reconstructed for any CTFont, incl. system fonts).
  2. Find the CPAL table and bake the resolved palette (selected index + per-entry overrides, BGRA, out-of-range index falls back to palette 0) into palette 0's color records of an in-memory copy.
  3. Rebuild the CTFont from those bytes via CTFontManagerCreateFontDescriptorFromData.

CoreText then renders the requested colors because it still performs all of the drawing. The overwrite is in place (same size, no sfnt relayout); only the CPAL bytes and that table's directory checksum change. When there is no usable CPAL table or the request is the identity, the original font is kept.

Scope / known limitation (COLRv1)

CoreText renders COLRv0 (layered solid colors) and honors the edited palette, but does not paint COLRv1 (gradients) at all on current macOS — it falls back to a monochrome outline regardless of palette. So this fix makes palette selection work for COLRv0 fonts; full COLRv1 palette support on Apple platforms would require Skia's own COLR engine (out of scope here). Verified empirically with standalone CoreText probes (no Skia): Apple Color Emoji paints; a COLRv0 font paints and follows edited palette-0 bytes; the official test_glyphs-COLRv1.ttf always renders black.

Why not the private SPI

A previous upstream attempt used the private kCTFontPaletteAttribute SPI (CL 867836) and was reverted (CL 918416) because it is not App Store safe (see flutter/flutter#158423). This approach uses only public CoreText API and carries no App Store risk.

Testing

Verified on macOS arm64 (macOS 26.5) via SkiaSharp's managed test suite (SKColorFontPaletteRenderingTest): Clone(0/1/2) of a 3-palette COLRv0 font now render distinct, correct colors and per-entry overrides recolor the targeted entry; typeface/font regression slice clean (162 tests). The consuming SkiaSharp PR links here.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

CoreText exposes no public API to select a COLR/CPAL palette and always
renders the font's default palette (index 0); the only color input it
honors is the CGContext fill color, which it maps to the COLR foreground
entry. As a result SkTypeface::makeClone() with a non-default
SkFontArguments::Palette (selected index and/or per-entry overrides) had
no visual effect on macOS/iOS, while FreeType and DirectWrite honor it.

Honor the requested palette using public API only: at clone/load time,
bake the resolved palette (selected index + per-entry overrides) into
palette 0 of an in-memory copy of the font's CPAL table and build the
CTFont from those bytes. CoreText then renders the requested colors
because it still performs all of the drawing. Out-of-range indices fall
back to palette 0, matching CSS Fonts 4 / FreeType semantics.

Scope: CoreText renders COLRv0 (layered solid colors) and honors the
edited palette, but does not paint COLRv1 (gradients) at all -- it falls
back to a monochrome outline regardless of palette. Full COLRv1 palette
support on Apple platforms would require Skia's own COLR engine.

A previous attempt to use the private kCTFontPaletteAttribute SPI was
reverted upstream because it is not App Store safe; this approach uses
only public CoreText API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow

Copy link
Copy Markdown
Collaborator Author

Consumed by mono/SkiaSharp#4265, which bumps the externals/skia submodule to this branch and adds SKColorFontPaletteRenderingTest proving COLRv0 palette selection renders distinct colors per palette index on CoreText.

@mattleibow mattleibow marked this pull request as draft June 29, 2026 15:25
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.

1 participant