diff --git a/apps/example/src/Examples/API/FontMgr.tsx b/apps/example/src/Examples/API/FontMgr.tsx index 3e937c7e97..0ff0c9d9bf 100644 --- a/apps/example/src/Examples/API/FontMgr.tsx +++ b/apps/example/src/Examples/API/FontMgr.tsx @@ -2,8 +2,8 @@ import React from "react"; import { ScrollView, useWindowDimensions } from "react-native"; import { Canvas, - Skia, Text, + listFontFamilies, matchFont, useFonts, } from "@shopify/react-native-skia"; @@ -16,11 +16,7 @@ const titleText = "Fonts from the System"; const titleY = titleFontSize + PADDING; const subtitleY = titleY + 14 + PADDING; -const fontMgr = Skia.FontMgr.System(); -const familyNames = new Array(fontMgr.countFamilies()) - .fill(0) - .map((_, i) => fontMgr.getFamilyName(i)); - +const familyNames = listFontFamilies(); const title2Y = subtitleY + 16 * familyNames.length + PADDING + titleFontSize; export const FontMgr = () => { diff --git a/packages/skia/android/cpp/jni/JniPlatformContext.cpp b/packages/skia/android/cpp/jni/JniPlatformContext.cpp index 3557955654..87d1b1e449 100644 --- a/packages/skia/android/cpp/jni/JniPlatformContext.cpp +++ b/packages/skia/android/cpp/jni/JniPlatformContext.cpp @@ -207,7 +207,7 @@ void JniPlatformContext::notifyTaskReadyExternal() { jni::ThreadScope ts; static auto method = - javaPart_->getClass()->getMethod("notifyTaskReady"); + javaPart_->getClass()->getMethod("notifyTaskReady"); method(javaPart_.get()); } diff --git a/packages/skia/apple/RNSkApplePlatformContext.h b/packages/skia/apple/RNSkApplePlatformContext.h index f1c6f5a83c..0d5d5b8f92 100644 --- a/packages/skia/apple/RNSkApplePlatformContext.h +++ b/packages/skia/apple/RNSkApplePlatformContext.h @@ -73,6 +73,8 @@ class RNSkApplePlatformContext : public RNSkPlatformContext { sk_sp createFontMgr() override; + std::vector getSystemFontFamilies() override; + private: ViewScreenshotService *_screenshotService; diff --git a/packages/skia/apple/RNSkApplePlatformContext.mm b/packages/skia/apple/RNSkApplePlatformContext.mm index c61cca9a1c..8effaafc1a 100644 --- a/packages/skia/apple/RNSkApplePlatformContext.mm +++ b/packages/skia/apple/RNSkApplePlatformContext.mm @@ -3,6 +3,7 @@ #import #include #import +#include #include #include @@ -300,6 +301,51 @@ return SkFontMgr_New_CoreText(nullptr); } +std::vector RNSkApplePlatformContext::getSystemFontFamilies() { + std::vector families; + + // System UI fonts (e.g., .AppleSystemUIFont) are not enumerated by Skia's + // font manager. We retrieve them via Core Text's CTFontUIFontType constants. + // This list covers common system font types as of iOS 17 / macOS 14. + // Apple may add new CTFontUIFontType values in future OS versions, + // so this list may need to be updated periodically. + CTFontUIFontType fontTypes[] = { + kCTFontUIFontUser, kCTFontUIFontUserFixedPitch, + kCTFontUIFontSystem, kCTFontUIFontEmphasizedSystem, + kCTFontUIFontSmallSystem, kCTFontUIFontSmallEmphasizedSystem, + kCTFontUIFontMiniSystem, kCTFontUIFontMiniEmphasizedSystem, + kCTFontUIFontLabel, kCTFontUIFontMessage, + kCTFontUIFontToolTip, + }; + + std::set uniqueFamilies; + + for (CTFontUIFontType fontType : fontTypes) { + CTFontRef font = CTFontCreateUIFontForLanguage(fontType, 12.0, nullptr); + if (font) { + CFStringRef familyName = CTFontCopyFamilyName(font); + if (familyName) { + const char *cstr = + CFStringGetCStringPtr(familyName, kCFStringEncodingUTF8); + if (cstr) { + uniqueFamilies.insert(std::string(cstr)); + } else { + char buffer[256]; + if (CFStringGetCString(familyName, buffer, sizeof(buffer), + kCFStringEncodingUTF8)) { + uniqueFamilies.insert(std::string(buffer)); + } + } + CFRelease(familyName); + } + CFRelease(font); + } + } + + families.assign(uniqueFamilies.begin(), uniqueFamilies.end()); + return families; +} + void RNSkApplePlatformContext::runOnMainThread(std::function func) { dispatch_async(dispatch_get_main_queue(), ^{ func(); diff --git a/packages/skia/apple/SkiaCVPixelBufferUtils.mm b/packages/skia/apple/SkiaCVPixelBufferUtils.mm index d477acdf1b..1cc8e108a5 100644 --- a/packages/skia/apple/SkiaCVPixelBufferUtils.mm +++ b/packages/skia/apple/SkiaCVPixelBufferUtils.mm @@ -30,12 +30,16 @@ #include #if TARGET_RT_BIG_ENDIAN #define FourCC2Str(fourcc) \ - (const char[]){*((char *)&fourcc), *(((char *)&fourcc) + 1), \ - *(((char *)&fourcc) + 2), *(((char *)&fourcc) + 3), 0} + (const char[]) { \ + *((char *)&fourcc), *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 2), \ + *(((char *)&fourcc) + 3), 0 \ + } #else #define FourCC2Str(fourcc) \ - (const char[]){*(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \ - *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0} + (const char[]) { \ + *(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \ + *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0 \ + } #endif // pragma MARK: TextureHolder diff --git a/packages/skia/cpp/api/JsiSkFontMgr.h b/packages/skia/cpp/api/JsiSkFontMgr.h index 40ce60655a..6d910f4948 100644 --- a/packages/skia/cpp/api/JsiSkFontMgr.h +++ b/packages/skia/cpp/api/JsiSkFontMgr.h @@ -27,15 +27,32 @@ class JsiSkFontMgr : public JsiSkWrappingSkPtrHostObject { JsiSkFontMgr(std::shared_ptr context, sk_sp fontMgr) - : JsiSkWrappingSkPtrHostObject(std::move(context), fontMgr) {} + : JsiSkWrappingSkPtrHostObject(context, fontMgr), + _systemFontFamilies(context->getSystemFontFamilies()) {} - JSI_HOST_FUNCTION(countFamilies) { return getObject()->countFamilies(); } + JSI_HOST_FUNCTION(countFamilies) { + return static_cast(getObject()->countFamilies() + + _systemFontFamilies.size()); + } JSI_HOST_FUNCTION(getFamilyName) { auto i = static_cast(arguments[0].asNumber()); - SkString name; - getObject()->getFamilyName(i, &name); - return jsi::String::createFromUtf8(runtime, name.c_str()); + auto baseFamilyCount = getObject()->countFamilies(); + if (i < baseFamilyCount) { + SkString name; + getObject()->getFamilyName(i, &name); + return jsi::String::createFromUtf8(runtime, name.c_str()); + } + auto systemIndex = i - baseFamilyCount; + if (systemIndex < static_cast(_systemFontFamilies.size())) { + return jsi::String::createFromUtf8(runtime, + _systemFontFamilies[systemIndex]); + } + throw jsi::JSError(runtime, "Font family index out of bounds: " + + std::to_string(i) + " (total families: " + + std::to_string(baseFamilyCount + + _systemFontFamilies.size()) + + ")"); } JSI_HOST_FUNCTION(matchFamilyStyle) { @@ -55,6 +72,9 @@ class JsiSkFontMgr : public JsiSkWrappingSkPtrHostObject { JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkFontMgr, countFamilies), JSI_EXPORT_FUNC(JsiSkFontMgr, getFamilyName), JSI_EXPORT_FUNC(JsiSkFontMgr, matchFamilyStyle)) + +private: + std::vector _systemFontFamilies; }; } // namespace RNSkia diff --git a/packages/skia/cpp/api/JsiSkPictureFactory.h b/packages/skia/cpp/api/JsiSkPictureFactory.h index 6fb2e20851..f23047ab39 100644 --- a/packages/skia/cpp/api/JsiSkPictureFactory.h +++ b/packages/skia/cpp/api/JsiSkPictureFactory.h @@ -50,12 +50,15 @@ class JsiSkPictureFactory : public JsiSkHostObject { } } - // Get ArrayBuffer - try buffer property first (Uint8Array, etc.), then direct ArrayBuffer - jsi::ArrayBuffer buffer = obj.hasProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer")) - ? obj.getProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer")) - .asObject(runtime) - .getArrayBuffer(runtime) - : obj.getArrayBuffer(runtime); + // Get ArrayBuffer - try buffer property first (Uint8Array, etc.), then + // direct ArrayBuffer + jsi::ArrayBuffer buffer = + obj.hasProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer")) + ? obj.getProperty(runtime, + jsi::PropNameID::forAscii(runtime, "buffer")) + .asObject(runtime) + .getArrayBuffer(runtime) + : obj.getArrayBuffer(runtime); sk_sp data = SkData::MakeWithCopy(buffer.data(runtime), buffer.size(runtime)); diff --git a/packages/skia/cpp/api/recorder/RNRecorder.h b/packages/skia/cpp/api/recorder/RNRecorder.h index 8eb7bc9993..d96331f088 100644 --- a/packages/skia/cpp/api/recorder/RNRecorder.h +++ b/packages/skia/cpp/api/recorder/RNRecorder.h @@ -78,8 +78,8 @@ class Recorder { for (const auto &child : group->children) { if (child->type == CommandType::Group) { auto *childGroup = static_cast(child.get()); - pending.push_back( - {childGroup, sanitizeZIndex(childGroup), static_cast(pending.size())}); + pending.push_back({childGroup, sanitizeZIndex(childGroup), + static_cast(pending.size())}); } else { flushPendingGroups(ctx, pending); playCommand(ctx, child.get()); @@ -94,9 +94,7 @@ class Recorder { std::shared_ptr _context; Variables variables; - Recorder() { - commandStack.push_back(&commands); - } + Recorder() { commandStack.push_back(&commands); } ~Recorder() { if (!_context || commands.empty()) { return; @@ -128,14 +126,11 @@ class Recorder { pushCommand( std::make_unique(runtime, props, variables)); } else if (nodeType == "skColorShader") { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } else if (nodeType == "skTurbulence") { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } else if (nodeType == "skFractalNoise") { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } else if (nodeType == "skLinearGradient") { pushCommand( std::make_unique(runtime, props, variables)); @@ -146,12 +141,11 @@ class Recorder { pushCommand( std::make_unique(runtime, props, variables)); } else if (nodeType == "skTwoPointConicalGradient") { - pushCommand(std::make_unique( - runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, + variables)); // TODO: should receive skBlendShader here } else if (nodeType == "skBlend") { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } } @@ -216,41 +210,36 @@ class Recorder { pushCommand( std::make_unique(runtime, props, variables)); } else if (nodeType == "skDropShadowImageFilter") { - pushCommand(std::make_unique( - runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, + variables)); } else if (nodeType == "skMorphologyImageFilter") { - pushCommand(std::make_unique( - runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, + variables)); } else if (nodeType == "skBlendImageFilter") { pushCommand( std::make_unique(runtime, props, variables)); } else if (nodeType == "skRuntimeShaderImageFilter") { - pushCommand(std::make_unique( - runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, + variables)); } else if (nodeType == "skImageFilter") { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } } void composePathEffect() { - pushCommand( - std::make_unique(CommandType::ComposePathEffect)); + pushCommand(std::make_unique(CommandType::ComposePathEffect)); } void composeImageFilter() { - pushCommand( - std::make_unique(CommandType::ComposeImageFilter)); + pushCommand(std::make_unique(CommandType::ComposeImageFilter)); } void composeColorFilter() { - pushCommand( - std::make_unique(CommandType::ComposeColorFilter)); + pushCommand(std::make_unique(CommandType::ComposeColorFilter)); } void pushBlurMaskFilter(jsi::Runtime &runtime, const jsi::Object &props) { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } void saveCTM(jsi::Runtime &runtime, const jsi::Object &props) { @@ -278,8 +267,7 @@ class Recorder { } void drawTextPath(jsi::Runtime &runtime, const jsi::Object &props) { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } void drawText(jsi::Runtime &runtime, const jsi::Object &props) { @@ -296,8 +284,7 @@ class Recorder { void drawBox(jsi::Runtime &runtime, const jsi::Object &props, const jsi::Array &shadows) { - pushCommand( - std::make_unique(runtime, props, shadows, variables)); + pushCommand(std::make_unique(runtime, props, shadows, variables)); } void drawImage(jsi::Runtime &runtime, const jsi::Object &props) { @@ -322,18 +309,15 @@ class Recorder { } void drawVertices(jsi::Runtime &runtime, const jsi::Object &props) { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } void drawDiffRect(jsi::Runtime &runtime, const jsi::Object &props) { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } void drawTextBlob(jsi::Runtime &runtime, const jsi::Object &props) { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } void drawGlyphs(jsi::Runtime &runtime, const jsi::Object &props) { @@ -346,13 +330,11 @@ class Recorder { } void drawImageSVG(jsi::Runtime &runtime, const jsi::Object &props) { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } void drawParagraph(jsi::Runtime &runtime, const jsi::Object &props) { - pushCommand( - std::make_unique(runtime, props, variables)); + pushCommand(std::make_unique(runtime, props, variables)); } void drawAtlas(jsi::Runtime &runtime, const jsi::Object &props) { @@ -365,8 +347,7 @@ class Recorder { } void materializePaint() { - pushCommand( - std::make_unique(CommandType::MaterializePaint)); + pushCommand(std::make_unique(CommandType::MaterializePaint)); } void restorePaintDeclaration() { @@ -379,11 +360,11 @@ class Recorder { } void saveBackdropFilter() { - pushCommand( - std::make_unique(CommandType::SaveBackdropFilter)); + pushCommand(std::make_unique(CommandType::SaveBackdropFilter)); } - void saveGroup(jsi::Runtime &runtime, const jsi::Value *propsValue = nullptr) { + void saveGroup(jsi::Runtime &runtime, + const jsi::Value *propsValue = nullptr) { auto group = std::make_unique(); if (propsValue != nullptr && propsValue->isObject()) { auto object = propsValue->asObject(runtime); @@ -400,7 +381,6 @@ class Recorder { } } - void play(DrawingCtx *ctx) { for (const auto &cmd : commands) { playCommand(ctx, cmd.get()); @@ -497,8 +477,7 @@ inline void Recorder::playCommand(DrawingCtx *ctx, Command *cmd) { auto *offsetCmd = static_cast(cmd); offsetCmd->pushImageFilter(ctx); } else if (nodeType == "skDisplacementMapImageFilter") { - auto *displacementCmd = - static_cast(cmd); + auto *displacementCmd = static_cast(cmd); displacementCmd->pushImageFilter(ctx); } else if (nodeType == "skBlurImageFilter") { auto *blurCmd = static_cast(cmd); @@ -513,8 +492,7 @@ inline void Recorder::playCommand(DrawingCtx *ctx, Command *cmd) { auto *blendCmd = static_cast(cmd); blendCmd->pushImageFilter(ctx); } else if (nodeType == "skRuntimeShaderImageFilter") { - auto *runtimeShaderCmd = - static_cast(cmd); + auto *runtimeShaderCmd = static_cast(cmd); runtimeShaderCmd->pushImageFilter(ctx); } else if (nodeType == "skImageFilter") { auto *imageFilterCmd = static_cast(cmd); diff --git a/packages/skia/cpp/rnskia/RNSkPlatformContext.h b/packages/skia/cpp/rnskia/RNSkPlatformContext.h index 1a23181756..6de41e51d9 100644 --- a/packages/skia/cpp/rnskia/RNSkPlatformContext.h +++ b/packages/skia/cpp/rnskia/RNSkPlatformContext.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "RNSkVideo.h" #include "RNWindowContext.h" @@ -145,6 +146,12 @@ class RNSkPlatformContext { */ virtual sk_sp createFontMgr() = 0; + /** + * Return platform-specific system font family names that aren't + * enumerated by the standard font manager (e.g., .AppleSystemUIFont on iOS) + */ + virtual std::vector getSystemFontFamilies() { return {}; } + /** * Creates an skImage containing the screenshot of a native view and its * children. diff --git a/packages/skia/src/skia/core/Font.ts b/packages/skia/src/skia/core/Font.ts index 7fc8296840..e5e1c2a505 100644 --- a/packages/skia/src/skia/core/Font.ts +++ b/packages/skia/src/skia/core/Font.ts @@ -95,10 +95,15 @@ export const matchFont = ( return Skia.Font(typeface, fontStyle.fontSize); }; -export const listFontFamilies = (fontMgr: SkFontMgr = Skia.FontMgr.System()) => - new Array(fontMgr.countFamilies()) - .fill(0) - .map((_, i) => fontMgr.getFamilyName(i)); +export const listFontFamilies = ( + fontMgr: SkFontMgr = Skia.FontMgr.System() +) => { + const families = new Set(); + for (let i = 0; i < fontMgr.countFamilies(); i++) { + families.add(fontMgr.getFamilyName(i)); + } + return Array.from(families); +}; const loadTypefaces = (typefacesToLoad: Record) => { const promises = Object.keys(typefacesToLoad).flatMap((familyName) => {