mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
468 lines
16 KiB
C++
468 lines
16 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE framework.
|
|
Copyright (c) Raw Material Software Limited
|
|
|
|
JUCE is an open source framework subject to commercial or open source
|
|
licensing.
|
|
|
|
By downloading, installing, or using the JUCE framework, or combining the
|
|
JUCE framework with any other source code, object code, content or any other
|
|
copyrightable work, you agree to the terms of the JUCE End User Licence
|
|
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
|
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
|
do not agree to the terms of these agreements, we will not license the JUCE
|
|
framework to you, and you must discontinue the installation or download
|
|
process and cease use of the JUCE framework.
|
|
|
|
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
|
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
|
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
|
|
|
Or:
|
|
|
|
You may also use this code under the terms of the AGPLv3:
|
|
https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
|
|
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
|
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
|
|
{
|
|
Font f (font);
|
|
f.setTypefaceName ([&]() -> String
|
|
{
|
|
const auto faceName = font.getTypefaceName();
|
|
|
|
if (faceName == Font::getDefaultSansSerifFontName()) return "Roboto";
|
|
if (faceName == Font::getDefaultSerifFontName()) return "Roboto";
|
|
if (faceName == Font::getDefaultMonospacedFontName()) return "Roboto";
|
|
|
|
return faceName;
|
|
}());
|
|
|
|
return Typeface::createSystemTypefaceFor (f);
|
|
}
|
|
|
|
//==============================================================================
|
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
|
STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
|
|
STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
|
|
STATICMETHOD (createFromAsset, "createFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)Landroid/graphics/Typeface;")
|
|
|
|
DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface")
|
|
#undef JNI_CLASS_MEMBERS
|
|
|
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
|
METHOD (constructor, "<init>", "()V") \
|
|
METHOD (computeBounds, "computeBounds", "(Landroid/graphics/RectF;Z)V")
|
|
|
|
DECLARE_JNI_CLASS (AndroidPath, "android/graphics/Path")
|
|
#undef JNI_CLASS_MEMBERS
|
|
|
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
|
METHOD (constructor, "<init>", "()V") \
|
|
FIELD (left, "left", "F") \
|
|
FIELD (right, "right", "F") \
|
|
FIELD (top, "top", "F") \
|
|
FIELD (bottom, "bottom", "F") \
|
|
METHOD (roundOut, "roundOut", "(Landroid/graphics/Rect;)V")
|
|
|
|
DECLARE_JNI_CLASS (AndroidRectF, "android/graphics/RectF")
|
|
#undef JNI_CLASS_MEMBERS
|
|
|
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
|
STATICMETHOD (getInstance, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;") \
|
|
METHOD (update, "update", "([B)V") \
|
|
METHOD (digest, "digest", "()[B")
|
|
DECLARE_JNI_CLASS (JavaMessageDigest, "java/security/MessageDigest")
|
|
#undef JNI_CLASS_MEMBERS
|
|
|
|
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
|
METHOD (open, "open", "(Ljava/lang/String;)Ljava/io/InputStream;") \
|
|
|
|
DECLARE_JNI_CLASS (AndroidAssetManager, "android/content/res/AssetManager")
|
|
#undef JNI_CLASS_MEMBERS
|
|
|
|
// Defined in juce_core
|
|
std::unique_ptr<InputStream> makeAndroidInputStreamWrapper (jobject stream);
|
|
|
|
//==============================================================================
|
|
class MemoryFontCache : public DeletedAtShutdown
|
|
{
|
|
public:
|
|
~MemoryFontCache()
|
|
{
|
|
clearSingletonInstance();
|
|
}
|
|
|
|
struct Key
|
|
{
|
|
String name, style;
|
|
auto tie() const { return std::tuple (name, style); }
|
|
bool operator< (const Key& other) const { return tie() < other.tie(); }
|
|
bool operator== (const Key& other) const { return tie() == other.tie(); }
|
|
};
|
|
|
|
void add (const Key& key, std::shared_ptr<hb_font_t> ptr)
|
|
{
|
|
const std::scoped_lock lock { mutex };
|
|
cache.emplace (key, ptr);
|
|
}
|
|
|
|
void remove (const Key& p)
|
|
{
|
|
const std::scoped_lock lock { mutex };
|
|
cache.erase (p);
|
|
}
|
|
|
|
std::set<String> getAllNames() const
|
|
{
|
|
const std::scoped_lock lock { mutex };
|
|
std::set<String> result;
|
|
|
|
for (const auto& item : cache)
|
|
result.insert (item.first.name);
|
|
|
|
return result;
|
|
}
|
|
|
|
std::set<String> getStylesForFamily (const String& family) const
|
|
{
|
|
const std::scoped_lock lock { mutex };
|
|
|
|
const auto lower = std::lower_bound (cache.begin(), cache.end(), family, [] (const auto& a, const String& b)
|
|
{
|
|
return a.first.name < b;
|
|
});
|
|
const auto upper = std::upper_bound (cache.begin(), cache.end(), family, [] (const String& a, const auto& b)
|
|
{
|
|
return a < b.first.name;
|
|
});
|
|
|
|
std::set<String> result;
|
|
|
|
for (const auto& item : makeRange (lower, upper))
|
|
result.insert (item.first.style);
|
|
|
|
return result;
|
|
}
|
|
|
|
std::shared_ptr<hb_font_t> find (const Key& key)
|
|
{
|
|
const std::scoped_lock lock { mutex };
|
|
|
|
const auto iter = cache.find (key);
|
|
|
|
if (iter != cache.end())
|
|
return iter->second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
JUCE_DECLARE_SINGLETON (MemoryFontCache, true)
|
|
|
|
private:
|
|
std::map<Key, std::shared_ptr<hb_font_t>> cache;
|
|
mutable std::mutex mutex;
|
|
};
|
|
|
|
JUCE_IMPLEMENT_SINGLETON (MemoryFontCache)
|
|
|
|
StringArray Font::findAllTypefaceNames()
|
|
{
|
|
auto results = [&]
|
|
{
|
|
if (auto* cache = MemoryFontCache::getInstance())
|
|
return cache->getAllNames();
|
|
|
|
return std::set<String>{};
|
|
}();
|
|
|
|
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
|
|
results.insert (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false));
|
|
|
|
StringArray s;
|
|
|
|
for (const auto& family : results)
|
|
s.add (family);
|
|
|
|
return s;
|
|
}
|
|
|
|
StringArray Font::findAllTypefaceStyles (const String& family)
|
|
{
|
|
auto results = [&]
|
|
{
|
|
if (auto* cache = MemoryFontCache::getInstance())
|
|
return cache->getStylesForFamily (family);
|
|
|
|
return std::set<String>{};
|
|
}();
|
|
|
|
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf"))
|
|
results.insert (f.getFileNameWithoutExtension().fromLastOccurrenceOf ("-", false, false));
|
|
|
|
StringArray s;
|
|
|
|
for (const auto& style : results)
|
|
s.add (style);
|
|
|
|
return s;
|
|
}
|
|
|
|
//==============================================================================
|
|
class AndroidTypeface final : public Typeface
|
|
{
|
|
public:
|
|
static Typeface::Ptr from (const Font& font)
|
|
{
|
|
if (auto* cache = MemoryFontCache::getInstance())
|
|
if (auto result = cache->find ({ font.getTypefaceName(), font.getTypefaceStyle() }))
|
|
return new AndroidTypeface (DoCache::no, result, font.getTypefaceName(), font.getTypefaceStyle());
|
|
|
|
auto blob = getBlobForFont (font);
|
|
auto face = FontStyleHelpers::getFaceForBlob ({ static_cast<const char*> (blob.getData()), blob.getSize() }, 0);
|
|
|
|
if (face == nullptr)
|
|
{
|
|
jassertfalse;
|
|
return {};
|
|
}
|
|
|
|
HbFont hbFont { hb_font_create (face.get()) };
|
|
FontStyleHelpers::initSynthetics (hbFont.get(), font);
|
|
|
|
return new AndroidTypeface (DoCache::no, std::move (hbFont), font.getTypefaceName(), font.getTypefaceStyle());
|
|
}
|
|
|
|
enum class DoCache
|
|
{
|
|
no,
|
|
yes
|
|
};
|
|
|
|
static Typeface::Ptr from (Span<const std::byte> blob, unsigned int index = 0)
|
|
{
|
|
return fromMemory (DoCache::yes, blob, index);
|
|
}
|
|
|
|
Native getNativeDetails() const override
|
|
{
|
|
return Native { hbFont.get() };
|
|
}
|
|
|
|
Typeface::Ptr createSystemFallback (const String& text, const String& language) const override
|
|
{
|
|
if (__builtin_available (android 29, *))
|
|
return matchWithAFontMatcher (text, language);
|
|
|
|
// The font-fallback API is only available on Android API level 29+
|
|
jassertfalse;
|
|
return {};
|
|
}
|
|
|
|
~AndroidTypeface() override
|
|
{
|
|
if (doCache == DoCache::yes)
|
|
if (auto* c = MemoryFontCache::getInstance())
|
|
c->remove ({ getName(), getStyle() });
|
|
}
|
|
|
|
private:
|
|
__INTRODUCED_IN (29) Typeface::Ptr matchWithAFontMatcher (const String& text, const String& language) const
|
|
{
|
|
using AFontMatcherPtr = std::unique_ptr<AFontMatcher, FunctionPointerDestructor<AFontMatcher_destroy>>;
|
|
const AFontMatcherPtr matcher { AFontMatcher_create() };
|
|
|
|
const auto weight = hb_style_get_value (hbFont.get(), HB_STYLE_TAG_WEIGHT);
|
|
const auto italic = hb_style_get_value (hbFont.get(), HB_STYLE_TAG_ITALIC) != 0.0f;
|
|
AFontMatcher_setStyle (matcher.get(), (uint16_t) weight, italic);
|
|
|
|
AFontMatcher_setLocales (matcher.get(), language.toRawUTF8());
|
|
|
|
const auto utf16 = text.toUTF16();
|
|
|
|
using AFontPtr = std::unique_ptr<AFont, FunctionPointerDestructor<AFont_close>>;
|
|
const AFontPtr matched { AFontMatcher_match (matcher.get(),
|
|
readFontName (hb_font_get_face (hbFont.get()),
|
|
HB_OT_NAME_ID_FONT_FAMILY,
|
|
nullptr).toRawUTF8(),
|
|
unalignedPointerCast<const uint16_t*> (utf16.getAddress()),
|
|
(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 }, [] (const TypefaceFileAndIndex& info) -> Typeface::Ptr
|
|
{
|
|
FileInputStream stream { info.file };
|
|
|
|
if (! stream.openedOk())
|
|
return {};
|
|
|
|
MemoryBlock mb;
|
|
stream.readIntoMemoryBlock (mb);
|
|
|
|
return fromMemory (DoCache::no, { static_cast<const std::byte*> (mb.getData()), mb.getSize() }, (unsigned int) info.index);
|
|
});
|
|
}
|
|
|
|
static Typeface::Ptr fromMemory (DoCache cache, Span<const std::byte> blob, unsigned int index = 0)
|
|
{
|
|
auto face = FontStyleHelpers::getFaceForBlob ({ reinterpret_cast<const char*> (blob.data()), blob.size() }, index);
|
|
|
|
if (face == nullptr)
|
|
return {};
|
|
|
|
return new AndroidTypeface (cache,
|
|
HbFont { hb_font_create (face.get()) },
|
|
readFontName (face.get(), HB_OT_NAME_ID_FONT_FAMILY, nullptr),
|
|
{});
|
|
}
|
|
|
|
static String readFontName (hb_face_t* face, hb_ot_name_id_t nameId, hb_language_t language)
|
|
{
|
|
unsigned int textSize{};
|
|
textSize = hb_ot_name_get_utf8 (face, nameId, language, &textSize, nullptr);
|
|
std::vector<char> nameString (textSize + 1, 0);
|
|
textSize = (unsigned int) nameString.size();
|
|
hb_ot_name_get_utf8 (face, nameId, language, &textSize, nameString.data());
|
|
|
|
return nameString.data();
|
|
}
|
|
|
|
AndroidTypeface (DoCache cache, std::shared_ptr<hb_font_t> fontIn, const String& name, const String& style)
|
|
: Typeface (name, style),
|
|
hbFont (std::move (fontIn)),
|
|
doCache (cache)
|
|
{
|
|
if (doCache == DoCache::yes)
|
|
if (auto* c = MemoryFontCache::getInstance())
|
|
c->add ({ name, style }, hbFont);
|
|
}
|
|
|
|
static MemoryBlock getBlobForFont (const Font& font)
|
|
{
|
|
auto memory = loadFontAsset (font.getTypefaceName());
|
|
|
|
if (! memory.isEmpty())
|
|
return memory;
|
|
|
|
const auto file = findFontFile (font);
|
|
|
|
if (! file.exists())
|
|
{
|
|
// Failed to find file corresponding to this font
|
|
jassertfalse;
|
|
return {};
|
|
}
|
|
|
|
FileInputStream stream { file };
|
|
|
|
MemoryBlock result;
|
|
stream.readIntoMemoryBlock (result);
|
|
|
|
return stream.isExhausted() ? result : MemoryBlock{};
|
|
}
|
|
|
|
static File findFontFile (const Font& font)
|
|
{
|
|
const String styles[] { font.getTypefaceStyle(),
|
|
FontStyleHelpers::getStyleName (font.isBold(), font.isItalic()),
|
|
{} };
|
|
|
|
for (const auto& style : styles)
|
|
if (auto file = getFontFile (font.getTypefaceName(), style); file.exists())
|
|
return file;
|
|
|
|
for (auto& file : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
|
|
if (file.getFileName().startsWith (font.getTypefaceName()))
|
|
return file;
|
|
|
|
return {};
|
|
}
|
|
|
|
static File getFontFile (const String& family, const String& fontStyle)
|
|
{
|
|
return "/system/fonts/" + family + (fontStyle.isNotEmpty() ? ("-" + fontStyle) : String{}) + ".ttf";
|
|
}
|
|
|
|
static MemoryBlock loadFontAsset (const String& typefaceName)
|
|
{
|
|
auto* env = getEnv();
|
|
|
|
const LocalRef<jobject> assetManager { env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets) };
|
|
|
|
if (assetManager == nullptr)
|
|
return {};
|
|
|
|
const LocalRef<jobject> inputStream { env->CallObjectMethod (assetManager,
|
|
AndroidAssetManager.open,
|
|
javaString ("fonts/" + typefaceName).get()) };
|
|
|
|
// Opening an input stream for an asset might throw if the asset isn't found
|
|
jniCheckHasExceptionOccurredAndClear();
|
|
|
|
if (inputStream == nullptr)
|
|
return {};
|
|
|
|
auto streamWrapper = makeAndroidInputStreamWrapper (inputStream);
|
|
|
|
if (streamWrapper == nullptr)
|
|
return {};
|
|
|
|
MemoryBlock result;
|
|
streamWrapper->readIntoMemoryBlock (result);
|
|
|
|
return streamWrapper->isExhausted() ? result : MemoryBlock{};
|
|
}
|
|
|
|
std::shared_ptr<hb_font_t> hbFont;
|
|
DoCache doCache;
|
|
};
|
|
|
|
//==============================================================================
|
|
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
|
|
{
|
|
return AndroidTypeface::from (font);
|
|
}
|
|
|
|
Typeface::Ptr Typeface::createSystemTypefaceFor (Span<const std::byte> data)
|
|
{
|
|
return AndroidTypeface::from (data);
|
|
}
|
|
|
|
void Typeface::scanFolderForFonts (const File&)
|
|
{
|
|
jassertfalse; // not currently available
|
|
}
|
|
|
|
bool TextLayout::createNativeLayout (const AttributedString&)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
} // namespace juce
|