Skip to content

feat(🔤): expose SkFontStyleSet on SkFontMgr#3820

Open
IsaacInsoll wants to merge 1 commit intoShopify:mainfrom
IsaacInsoll:feat/font-style-set
Open

feat(🔤): expose SkFontStyleSet on SkFontMgr#3820
IsaacInsoll wants to merge 1 commit intoShopify:mainfrom
IsaacInsoll:feat/font-style-set

Conversation

@IsaacInsoll
Copy link
Copy Markdown
Contributor

feat(🔤): expose SkFontStyleSet on SkFontMgr

Summary

Adds JSI bindings for Skia's SkFontStyleSet, letting JS enumerate the exact
variants a font family ships with. Two new methods on SkFontMgr:

  • matchFamily(name: string): SkFontStyleSet | null
  • createStyleSet(index: number): SkFontStyleSet | null

And a new host object, SkFontStyleSet, exposing:

  • count()
  • getStyle(index)FontStyleEntry ({ weight, width, slant, name })
  • createTypeface(index)
  • matchStyle(style)
  • dispose()

Motivation

matchFamilyStyle is the only JS-side entry point for family lookup today, and
it has two limitations:

  1. It silently falls back across families when the requested family doesn't
    exist — there is no way to distinguish "Helvetica Bold" from "some other
    font's Bold that Skia picked for you".
  2. It cannot enumerate what a family actually ships. Building a variant picker
    (weight/width/slant) currently requires bundling the font metadata yourself.

SkFontStyleSet is the native Skia primitive that answers both questions. It's
already returned by SkFontMgr::matchFamily / SkFontMgr::createStyleSet on
both platforms — this PR just lifts it into JS.

API

interface SkFontMgr {
  // ...existing
  createStyleSet(index: number): SkFontStyleSet | null;
  matchFamily(name: string): SkFontStyleSet | null;
}

interface FontStyleEntry extends Required<FontStyle> {
  name: string; // PostScript name, e.g. "Helvetica-BoldOblique"
}

interface SkFontStyleSet extends SkJSIInstance<'FontStyleSet'> {
  count(): number;
  getStyle(index: number): FontStyleEntry;
  createTypeface(index: number): SkTypeface | null;
  matchStyle(style: FontStyle): SkTypeface | null;
}

FontStyleEntry reuses the existing FontWeight / FontWidth / FontSlant
enums via Required<FontStyle> for type consistency with the rest of the font
API.

matchFamily resolves family aliases ("System" on iOS, etc.) via
RNSkPlatformContext::resolveFontFamily — same behavior as matchFamilyStyle
after #3603.

Implementation notes

  • New C++ host object: cpp/api/JsiSkFontStyleSet.h. Follows the
    JsiSkTypeface pattern: EXPORT_JSI_API_TYPENAME, static toValue,
    getMemoryPressure, getObjectType, dispose inherited from the base.
  • JsiSkFontMgr.h gains createStyleSet / matchFamily as thin wrappers and
    re-uses resolveFontFamily for alias resolution.
  • Nullable returns match Skia's C++ contract: an unknown family / out-of-range
    index returns jsi::Value::null() rather than throwing.
  • Web: stub JsiSkFontStyleSet that throws "Not implemented on React Native
    Web", parallel to how matchFamilyStyle is stubbed today. CanvasKit has no
    FontStyleSet equivalent, so web callers should guard with Platform.OS.

Testing

  • yarn lint — clean
  • yarn tsc — clean
  • yarn test — existing suite passes. No new unit tests yet — see the
    "Follow-ups" note below.
  • Manually verified against the system font manager on iOS and Android by
    enumerating a known-multi-variant family (Helvetica on iOS, sans-serif on
    Android) and confirming count(), getStyle(i).name, and
    createTypeface(i) return sensible values.

No yarn e2e run yet either — e2e requires a rendering assertion, and
SkFontStyleSet doesn't draw. Open to suggestions on the right harness for
this; happy to add tests in whatever form you prefer before merge.

Docs

New page: apps/docs/docs/text/font-style-set.md (included in this PR) with
usage examples for matchFamily, createStyleSet, and a "pick the closest
variant" worked example. Linked from the sidebar under Text.

Checklist

  • Types updated (src/skia/types/Font/FontMgr.ts + generated .d.ts)
  • Native binding added and compiles on iOS and Android
  • Web stub implemented and throws with a clear message
  • Unit tests — not yet, happy to add once we agree on the surface
  • E2E test — not yet, see Testing notes
  • Docs page
  • No breaking changes — purely additive

Notes for reviewers

  • Tests: I'd like guidance before writing them. Unit tests in
    packages/skia/src/skia/__tests__/ seem to target pure JS; I'm not sure
    what the preferred way is to cover a JSI host object that wraps native font
    data. Pointers welcome.
  • Should SkFontStyleSet expose a [Symbol.iterator] / convenience helpers in
    JS (e.g. getStyles(): FontStyleEntry[])? I kept the surface minimal and
    Skia-faithful; happy to add an idiomatic JS helper in a follow-up.
  • getMemoryPressure returns 2048 to match JsiSkFontMgr. A style set is
    tiny; open to tuning this if you have a preferred heuristic.

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