From c5a9e26960db21fda3f6df1d2daa40198225adad Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 17 Apr 2024 17:26:13 +0100 Subject: [PATCH] Typeface: Add support for querying the default system UI typeface --- modules/juce_graphics/fonts/juce_Font.cpp | 38 +++++++++- modules/juce_graphics/fonts/juce_Font.h | 9 +++ modules/juce_graphics/fonts/juce_Typeface.h | 18 +++++ .../juce_DirectWriteTypeface_windows.cpp | 50 ++++++++++--- .../native/juce_Fonts_android.cpp | 74 ++++++++++++++----- .../native/juce_Fonts_freetype.cpp | 69 ++++++++++++----- .../juce_graphics/native/juce_Fonts_linux.cpp | 2 +- .../juce_graphics/native/juce_Fonts_mac.mm | 27 ++++++- 8 files changed, 233 insertions(+), 54 deletions(-) diff --git a/modules/juce_graphics/fonts/juce_Font.cpp b/modules/juce_graphics/fonts/juce_Font.cpp index 2f92f6c3df..05dbe44baf 100644 --- a/modules/juce_graphics/fonts/juce_Font.cpp +++ b/modules/juce_graphics/fonts/juce_Font.cpp @@ -39,6 +39,8 @@ class Font::Native { public: HbFont font{}; + + static Typeface::Ptr getDefaultPlatformTypefaceForFont (const Font&); }; using GetTypefaceForFont = Typeface::Ptr (*)(const Font&); @@ -426,10 +428,11 @@ void Font::dupeInternalIfShared() //============================================================================== struct FontPlaceholderNames { - String sans { "" }, - serif { "" }, - mono { "" }, - regular { "" }; + String sans = "", + serif = "", + mono = "", + regular = "", + systemUi = "system-ui"; }; static const FontPlaceholderNames& getFontPlaceholderNames() @@ -447,6 +450,7 @@ static FontNamePreloader fnp; #endif const String& Font::getDefaultSansSerifFontName() { return getFontPlaceholderNames().sans; } +const String& Font::getSystemUIFontName() { return getFontPlaceholderNames().systemUi; } const String& Font::getDefaultSerifFontName() { return getFontPlaceholderNames().serif; } const String& Font::getDefaultMonospacedFontName() { return getFontPlaceholderNames().mono; } const String& Font::getDefaultStyle() { return getFontPlaceholderNames().regular; } @@ -869,4 +873,30 @@ Font::Native Font::getNativeDetails() const return { font->getFontPtr (*this) }; } +Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +{ + const auto systemTypeface = [&]() -> Typeface::Ptr + { + if (font.getTypefaceName() != getSystemUIFontName()) + return {}; + + const auto systemTypeface = Typeface::findSystemTypeface(); + + if (systemTypeface == nullptr) + return {}; + + if (systemTypeface->getStyle() == font.getTypefaceStyle()) + return systemTypeface; + + auto copy = font; + copy.setTypefaceName (systemTypeface->getName()); + return getDefaultTypefaceForFont (copy); + }(); + + if (systemTypeface != nullptr) + return systemTypeface; + + return Native::getDefaultPlatformTypefaceForFont (font); +} + } // namespace juce diff --git a/modules/juce_graphics/fonts/juce_Font.h b/modules/juce_graphics/fonts/juce_Font.h index 9a28fcfdd8..c116e5a619 100644 --- a/modules/juce_graphics/fonts/juce_Font.h +++ b/modules/juce_graphics/fonts/juce_Font.h @@ -212,6 +212,15 @@ public: */ static const String& getDefaultSansSerifFontName(); + /** Returns a typeface font family that represents the system UI font. + + Note that this method just returns a generic placeholder string that means "the default + UI font" - it's not the actual font family of this font. + + @see getDefaultSansSerifFontName, setTypefaceName + */ + static const String& getSystemUIFontName(); + /** Returns a typeface font family that represents the default serif font. Note that this method just returns a generic placeholder string that means "the default diff --git a/modules/juce_graphics/fonts/juce_Typeface.h b/modules/juce_graphics/fonts/juce_Typeface.h index f7fae451bc..5fc94dc403 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.h +++ b/modules/juce_graphics/fonts/juce_Typeface.h @@ -346,6 +346,24 @@ public: */ virtual Typeface::Ptr createSystemFallback (const String& text, const String& language) const = 0; + /** Returns the system's default UI font. + + This will differ depending on the platform. + + On Linux/fontconfig, this returns the typeface mapped to the name "system-ui", + or nullptr if no such font exists. + + On Windows, this queries SystemParametersInfo with the key SPI_GETNONCLIENTMETRICS, + and returns the lfMessageFont that is returned, or nullptr if the font cannot be found. + + On macOS and iOS, this returns the result of CTFontCreateUIFontForLanguage() for + the kCTFontUIFontSystem typeface. + + On Android 29+, this will use AFontMatcher to return the "system-ui" font. On earlier + Android versions, this will attempt to return the Roboto font. + */ + static Typeface::Ptr findSystemTypeface(); + private: //============================================================================== String name; diff --git a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp index 8546c65331..280f8612d4 100644 --- a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp +++ b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp @@ -566,24 +566,45 @@ public: ComSmartPtr getIDWriteFontFace() const { return dwFontFace; } -private: - float getKerning (int glyph1, int glyph2) const + static Typeface::Ptr findSystemTypeface() { - const auto face = dwFontFace.getInterface(); + NONCLIENTMETRICS nonClientMetrics{}; + nonClientMetrics.cbSize = sizeof (NONCLIENTMETRICS); - const UINT16 glyphs[] { (UINT16) glyph1, (UINT16) glyph2 }; - INT32 advances [std::size (glyphs)]{}; - - if (FAILED (face->GetDesignGlyphAdvances ((UINT32) std::size (glyphs), std::data (glyphs), std::data (advances)))) + if (! SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICS), &nonClientMetrics, sizeof (NONCLIENTMETRICS))) return {}; - DWRITE_FONT_METRICS metrics{}; - face->GetMetrics (&metrics); + SharedResourcePointer factories; - // TODO(reuk) incorrect - return (float) advances[0] / (float) metrics.designUnitsPerEm; + ComSmartPtr interop; + if (FAILED (factories->getDWriteFactory()->GetGdiInterop (interop.resetAndGetPointerAddress())) || interop == nullptr) + return {}; + + ComSmartPtr dwFont; + if (FAILED (interop->CreateFontFromLOGFONT (&nonClientMetrics.lfMessageFont, dwFont.resetAndGetPointerAddress())) || dwFont == nullptr) + return {}; + + ComSmartPtr dwFontFace; + if (FAILED (dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress())) || dwFontFace == nullptr) + return {}; + + const auto name = getLocalisedFamilyName (*dwFont); + const auto style = getLocalisedStyle (*dwFont); + + const HbFace hbFace { hb_directwrite_face_create (dwFontFace) }; + HbFont font { hb_font_create (hbFace.get()) }; + const auto metrics = getGdiMetrics (font.get()).value_or (getDwriteMetrics (dwFontFace)); + + return new WindowsDirectWriteTypeface (name, + style, + dwFont, + dwFontFace, + std::move (font), + metrics, + {}); } +private: static UINT32 numUtf16Words (const CharPointer_UTF16& str) { return (UINT32) (str.findTerminatingNull().getAddress() - str.getAddress()); @@ -795,7 +816,7 @@ struct DefaultFontNames String defaultSans, defaultSerif, defaultFixed, defaultFallback; }; -Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font) { static DefaultFontNames defaultNames; @@ -822,6 +843,11 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (Span data) return WindowsDirectWriteTypeface::from (data); } +Typeface::Ptr Typeface::findSystemTypeface() +{ + return WindowsDirectWriteTypeface::findSystemTypeface(); +} + void Typeface::scanFolderForFonts (const File&) { // TODO(reuk) diff --git a/modules/juce_graphics/native/juce_Fonts_android.cpp b/modules/juce_graphics/native/juce_Fonts_android.cpp index 35143b19f1..0050c9a2c6 100644 --- a/modules/juce_graphics/native/juce_Fonts_android.cpp +++ b/modules/juce_graphics/native/juce_Fonts_android.cpp @@ -35,7 +35,7 @@ namespace juce { -Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font) { Font f (font); f.setTypefaceName ([&]() -> String @@ -285,10 +285,58 @@ public: c->remove ({ getName(), getStyle() }); } + static Typeface::Ptr findSystemTypeface() + { + if (__builtin_available (android 29, *)) + return findSystemTypefaceWithMatcher(); + + return from (FontOptions{}.withName ("Roboto")); + } + private: + static __INTRODUCED_IN (29) Typeface::Ptr fromMatchedFont (AFont* matched) + { + if (matched == nullptr) + { + // Unable to find any matching fonts. This should never happen - in the worst case, + // we should at least get a font with the tofu character. + jassertfalse; + return {}; + } + + const File matchedFile { AFont_getFontFilePath (matched) }; + const auto matchedIndex = AFont_getCollectionIndex (matched); + + auto* cache = TypefaceFileCache::getInstance(); + + if (cache == nullptr) + return {}; // Perhaps we're shutting down + + return cache->get ({ matchedFile, (int) matchedIndex }, &loadCompatibleFont); + } + + static __INTRODUCED_IN (29) Typeface::Ptr findSystemTypefaceWithMatcher() + { + using AFontMatcherPtr = std::unique_ptr>; + using AFontPtr = std::unique_ptr>; + + constexpr uint16_t testString[] { 't', 'e', 's', 't' }; + + const AFontMatcherPtr matcher { AFontMatcher_create() }; + const AFontPtr matched { AFontMatcher_match (matcher.get(), + "system-ui", + testString, + std::size (testString), + nullptr) }; + + return fromMatchedFont (matched.get()); + } + __INTRODUCED_IN (29) Typeface::Ptr matchWithAFontMatcher (const String& text, const String& language) const { using AFontMatcherPtr = std::unique_ptr>; + using AFontPtr = std::unique_ptr>; + const AFontMatcherPtr matcher { AFontMatcher_create() }; const auto weight = hb_style_get_value (hbFont.get(), HB_STYLE_TAG_WEIGHT); @@ -299,7 +347,6 @@ private: const auto utf16 = text.toUTF16(); - using AFontPtr = std::unique_ptr>; const AFontPtr matched { AFontMatcher_match (matcher.get(), readFontName (hb_font_get_face (hbFont.get()), HB_OT_NAME_ID_FONT_FAMILY, @@ -308,23 +355,7 @@ private: (uint32_t) (utf16.findTerminatingNull().getAddress() - utf16.getAddress()), nullptr) }; - if (matched == nullptr) - { - // Unable to find any matching fonts. This should never happen - in the worst case, - // we should at least get a font with the tofu character. - jassertfalse; - return {}; - } - - const File matchedFile { AFont_getFontFilePath (matched.get()) }; - const auto matchedIndex = AFont_getCollectionIndex (matched.get()); - - auto* cache = TypefaceFileCache::getInstance(); - - if (cache == nullptr) - return {}; // Perhaps we're shutting down - - return cache->get ({ matchedFile, (int) matchedIndex }, &loadCompatibleFont); + return fromMatchedFont (matched.get()); } static Typeface::Ptr loadCompatibleFont (const TypefaceFileAndIndex& info) @@ -592,6 +623,11 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (Span data) return AndroidTypeface::from (data); } +Typeface::Ptr Typeface::findSystemTypeface() +{ + return AndroidTypeface::findSystemTypeface(); +} + void Typeface::scanFolderForFonts (const File&) { jassertfalse; // not currently available diff --git a/modules/juce_graphics/native/juce_Fonts_freetype.cpp b/modules/juce_graphics/native/juce_Fonts_freetype.cpp index c9da674348..93e2dab322 100644 --- a/modules/juce_graphics/native/juce_Fonts_freetype.cpp +++ b/modules/juce_graphics/native/juce_Fonts_freetype.cpp @@ -39,10 +39,12 @@ namespace juce { +#if JUCE_USE_FONTCONFIG using FcConfigPtr = std::unique_ptr>; using FcPatternPtr = std::unique_ptr>; using FcCharSetPtr = std::unique_ptr>; using FcLangSetPtr = std::unique_ptr>; +#endif struct FTLibWrapper final : public ReferenceCountedObject { @@ -61,7 +63,10 @@ struct FTLibWrapper final : public ReferenceCountedObject FT_Done_FreeType (library); } + #if JUCE_USE_FONTCONFIG const FcConfigPtr fcConfig { FcInitLoadConfigAndFonts() }; + #endif + FT_Library library = {}; using Ptr = ReferenceCountedObjectPtr; @@ -317,6 +322,8 @@ public: JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (FTTypefaceList) + FTLibWrapper::Ptr getLibrary() const { return library; } + private: FTLibWrapper::Ptr library = new FTLibWrapper; std::vector> faces; @@ -437,7 +444,6 @@ public: if (cache == nullptr) return {}; - auto* config = ftFace->library->fcConfig.get(); FcPatternPtr pattern { FcPatternCreate() }; { @@ -468,11 +474,47 @@ public: FcPatternAddLangSet (pattern.get(), FC_LANG, langset.get()); } - FcConfigSubstitute (config, pattern.get(), FcMatchPattern); - FcDefaultSubstitute (pattern.get()); + return fromPattern (pattern.get()); + #else + // Font substitution will not work unless fontconfig is enabled. + jassertfalse; + return nullptr; + #endif + } + + ~FreeTypeTypeface() override + { + if (doCache == DoCache::yes) + if (auto* list = FTTypefaceList::getInstance()) + list->removeMemoryFace (ftFace); + } + + static Typeface::Ptr findSystemTypeface() + { + #if JUCE_USE_FONTCONFIG + FcPatternPtr pattern { FcNameParse (unalignedPointerCast ("system-ui")) }; + return fromPattern (pattern.get()); + #else + return nullptr; + #endif + } + +private: + #if JUCE_USE_FONTCONFIG + static Typeface::Ptr fromPattern (FcPattern* pattern) + { + auto* cache = TypefaceFileCache::getInstance(); + + if (cache == nullptr) + return {}; + + const auto library = FTTypefaceList::getInstance()->getLibrary(); + + FcConfigSubstitute (library->fcConfig.get(), pattern, FcMatchPattern); + FcDefaultSubstitute (pattern); FcResult result{}; - const FcPatternPtr matched { FcFontMatch (config, pattern.get(), &result) }; + const FcPatternPtr matched { FcFontMatch (library->fcConfig.get(), pattern, &result) }; if (result != FcResultMatch) return {}; @@ -502,21 +544,9 @@ public: return new FreeTypeTypeface (DoCache::no, face, std::move (cachedFont), face->face->family_name, face->face->style_name); }); - #else - // Font substitution will not work unless fontconfig is enabled. - jassertfalse; - return nullptr; - #endif } + #endif - ~FreeTypeTypeface() override - { - if (doCache == DoCache::yes) - if (auto* list = FTTypefaceList::getInstance()) - list->removeMemoryFace (ftFace); - } - -private: FreeTypeTypeface (DoCache cache, FTFaceWrapper::Ptr ftFaceIn, HbFont hbIn, @@ -551,4 +581,9 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (Span data) return FreeTypeTypeface::from (data); } +Typeface::Ptr Typeface::findSystemTypeface() +{ + return FreeTypeTypeface::findSystemTypeface(); +} + } // namespace juce diff --git a/modules/juce_graphics/native/juce_Fonts_linux.cpp b/modules/juce_graphics/native/juce_Fonts_linux.cpp index 01703d3a59..f785daabca 100644 --- a/modules/juce_graphics/native/juce_Fonts_linux.cpp +++ b/modules/juce_graphics/native/juce_Fonts_linux.cpp @@ -197,7 +197,7 @@ private: JUCE_DECLARE_NON_COPYABLE (DefaultFontInfo) }; -Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font) { static const DefaultFontInfo defaultInfo; diff --git a/modules/juce_graphics/native/juce_Fonts_mac.mm b/modules/juce_graphics/native/juce_Fonts_mac.mm index 236a054b18..7acf49dbbb 100644 --- a/modules/juce_graphics/native/juce_Fonts_mac.mm +++ b/modules/juce_graphics/native/juce_Fonts_mac.mm @@ -667,6 +667,26 @@ public: return ctFont.get(); } + static Typeface::Ptr findSystemTypeface() + { + CFUniquePtr defaultCtFont (CTFontCreateUIFontForLanguage (kCTFontUIFontSystem, 0.0, nullptr)); + const CFUniquePtr newName { CTFontCopyFamilyName (defaultCtFont.get()) }; + const CFUniquePtr descriptor { CTFontCopyFontDescriptor (defaultCtFont.get()) }; + const CFUniquePtr newStyle { (CFStringRef) CTFontDescriptorCopyAttribute (descriptor.get(), + kCTFontStyleNameAttribute) }; + + HbFont result { hb_coretext_font_create (defaultCtFont.get()) }; + + if (result == nullptr) + return {}; + + return new CoreTextTypeface { std::move (defaultCtFont), + std::move (result), + String::fromCFString (newName.get()), + String::fromCFString (newStyle.get()), + {} }; + } + private: CoreTextTypeface (CFUniquePtr nativeFont, HbFont fontIn, @@ -737,6 +757,11 @@ void Typeface::scanFolderForFonts (const File& folder) CTFontManagerRegisterFontsForURL (urlref.get(), kCTFontManagerScopeProcess, nullptr); } +Typeface::Ptr Typeface::findSystemTypeface() +{ + return CoreTextTypeface::findSystemTypeface(); +} + StringArray Font::findAllTypefaceNames() { StringArray names; @@ -807,7 +832,7 @@ struct DefaultFontNames #endif }; -Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font) { static DefaultFontNames defaultNames;