mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 13:11:19 +01:00
Support supplying mobile fallback fonts by style with caching
This pull request builds on top of fallback font support on iOS by including the ability to provide fallback fonts based on the styling of the missing character. Currently, the style information only contains weight. This weight is grabbed by calling `getAxisValue`. According to Luigi, this is a linear search, so perhaps there's room for a performance optimization later on. There is one lower-level C++ change: `gFallbackProc` returns the font for the missing character, in addition _to_ the missing character (as a second parameter). This font will be used to generate the requested styling within the iOS runtime. This adds a new class property to `RiveFont`: `fallbackFontCallback` (whose name I'm open to changing). This is a block (i.e closure) that will be called when a fallback font is requested. It supplies the styling of the missing character so that, for example, different fonts can be used based on the weight of the missing character. For example usage, see `SwiftFallbackFonts.swift`. This provider is what's used under-the-hood, and utilizes the pre-existing `fallbackFonts` class property The "trickiest" bit here is the caching. NSDictionary requires equatable / hashable types as keys, and we want to minimize additional generation of a Rive font, so we cache any used fonts in a wrapper type, used as the value. When new fallback fonts are provided, either directly or when a new provider block is set, the cache will be reset. Once the weight is determined, generating the right key is as simple as calling the right initializer, and when set, generating the right value is simple as calling the right initializer with the created Rive font. Finally, `RiveFactory` was getting a little bloated, so I did a little file cleanup. This pull requests also includes Android support from #8621 Diffs= 7986d64d83 Support supplying mobile fallback fonts by style with caching (#8396) Co-authored-by: David Skuza <david@rive.app> Co-authored-by: Umberto <usonnino@gmail.com> Co-authored-by: Umberto Sonnino <umberto@rive.app>
This commit is contained in:
@@ -1 +1 @@
|
||||
fb8ecf3552aeece0b1d242804580a016b6297ad4
|
||||
7986d64d8371531716ea3f038dcbec5da187e6cd
|
||||
|
||||
15
.vscode/c_cpp_properties.json
vendored
15
.vscode/c_cpp_properties.json
vendored
@@ -4,15 +4,18 @@
|
||||
"name": "Mac",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/dev/test/include",
|
||||
"${workspaceFolder}/include"
|
||||
],
|
||||
"defines": [],
|
||||
"defines": [
|
||||
"WITH_RIVE_TEXT",
|
||||
"WITH_RIVE_AUDIO",
|
||||
"WITH_RIVE_LAYOUT"
|
||||
],
|
||||
"macFrameworkPath": [],
|
||||
"compilerPath": "/usr/local/bin/arm-none-eabi-gcc",
|
||||
"cStandard": "gnu11",
|
||||
"cppStandard": "gnu++14",
|
||||
"intelliSenseMode": "clang-x64"
|
||||
"compilerPath": "/usr/bin/clang",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "clang-arm64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
||||
3
dependencies/premake5_harfbuzz.lua
vendored
3
dependencies/premake5_harfbuzz.lua
vendored
@@ -221,6 +221,8 @@ do
|
||||
harfbuzz .. '/src/hb-paint.cc',
|
||||
harfbuzz .. '/src/hb-paint-extents.cc',
|
||||
harfbuzz .. '/src/hb-outline.cc',
|
||||
harfbuzz .. '/src/hb-style.h',
|
||||
harfbuzz .. '/src/hb-style.cc',
|
||||
})
|
||||
|
||||
warnings('Off')
|
||||
@@ -235,7 +237,6 @@ do
|
||||
'HB_NO_BITMAP',
|
||||
'HB_NO_BUFFER_SERIALIZE',
|
||||
'HB_NO_SETLOCALE',
|
||||
'HB_NO_STYLE',
|
||||
'HB_NO_VERTICAL',
|
||||
'HB_NO_LAYOUT_COLLECT_GLYPHS',
|
||||
'HB_NO_LAYOUT_RARELY_USED',
|
||||
|
||||
3
dependencies/premake5_harfbuzz_v2.lua
vendored
3
dependencies/premake5_harfbuzz_v2.lua
vendored
@@ -220,6 +220,8 @@ do
|
||||
harfbuzz .. '/src/hb-paint.cc',
|
||||
harfbuzz .. '/src/hb-paint-extents.cc',
|
||||
harfbuzz .. '/src/hb-outline.cc',
|
||||
harfbuzz .. '/src/hb-style.h',
|
||||
harfbuzz .. '/src/hb-style.cc',
|
||||
})
|
||||
|
||||
warnings('Off')
|
||||
@@ -237,7 +239,6 @@ do
|
||||
'HB_NO_BUFFER_VERIFY',
|
||||
'HB_NO_BUFFER_MESSAGE',
|
||||
'HB_NO_SETLOCALE',
|
||||
'HB_NO_STYLE',
|
||||
'HB_NO_VERTICAL',
|
||||
'HB_NO_LAYOUT_COLLECT_GLYPHS',
|
||||
'HB_NO_LAYOUT_RARELY_USED',
|
||||
|
||||
@@ -21,6 +21,8 @@ public:
|
||||
uint16_t getAxisCount() const override;
|
||||
float getAxisValue(uint32_t axisTag) const override;
|
||||
uint32_t getFeatureValue(uint32_t featureTag) const override;
|
||||
uint16_t getWeight() const override;
|
||||
bool isItalic() const override;
|
||||
|
||||
rive::RawPath getPath(rive::GlyphID) const override;
|
||||
rive::SimpleArray<rive::Paragraph> onShapeText(
|
||||
@@ -39,7 +41,7 @@ public:
|
||||
bool useSystemShaper,
|
||||
uint16_t weight,
|
||||
uint8_t width);
|
||||
|
||||
static float GetStyle(hb_font_t*, uint32_t);
|
||||
hb_font_t* font() const { return m_font; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -157,6 +157,12 @@ public:
|
||||
// will be used. Otherwise the default value for the axis will be returned.
|
||||
virtual float getAxisValue(uint32_t axisTag) const = 0;
|
||||
|
||||
// Returns the current font value as a numeric value [1, 1000]
|
||||
virtual uint16_t getWeight() const = 0;
|
||||
|
||||
// Whether this font is italic or not.
|
||||
virtual bool isItalic() const = 0;
|
||||
|
||||
// Font feature.
|
||||
struct Feature
|
||||
{
|
||||
@@ -201,11 +207,12 @@ public:
|
||||
// return a font that can draw (at least some of) them. If no font is
|
||||
// available just return nullptr.
|
||||
|
||||
using FallbackProc =
|
||||
rive::rcp<rive::Font> (*)(const rive::Unichar,
|
||||
const uint32_t fallbackIndex);
|
||||
using FallbackProc = rive::rcp<rive::Font> (*)(const rive::Unichar missing,
|
||||
const uint32_t fallbackIndex,
|
||||
const rive::Font*);
|
||||
static FallbackProc gFallbackProc;
|
||||
static bool gFallbackProcEnabled;
|
||||
static constexpr unsigned kRegularWeight = 400;
|
||||
|
||||
protected:
|
||||
Font(const LineMetrics& lm) : m_lineMetrics(lm) {}
|
||||
|
||||
@@ -28,7 +28,7 @@ getSkia() {
|
||||
# switch to a stable branch
|
||||
echo "Checking out stable branch $SKIA_STABLE_BRANCH"
|
||||
git checkout $SKIA_STABLE_BRANCH
|
||||
python tools/git-sync-deps
|
||||
python3 tools/git-sync-deps
|
||||
cd ..
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,11 @@ rive::rcp<rive::Font> HBFont::FromSystem(void* systemFont,
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////
|
||||
float HBFont::GetStyle(hb_font_t* font, uint32_t styleTag)
|
||||
{
|
||||
return hb_style_get_value(font, (hb_style_tag_t)styleTag);
|
||||
}
|
||||
//////////////
|
||||
|
||||
constexpr int kStdScale = 2048;
|
||||
constexpr float gInvScale = 1.0f / kStdScale;
|
||||
@@ -321,6 +325,20 @@ uint32_t HBFont::getFeatureValue(uint32_t featureTag) const
|
||||
return (uint32_t)-1;
|
||||
}
|
||||
|
||||
uint16_t HBFont::getWeight() const
|
||||
{
|
||||
uint32_t tag = HB_TAG('w', 'g', 'h', 't');
|
||||
float res = HBFont::GetStyle(m_font, tag);
|
||||
return static_cast<uint16_t>(res);
|
||||
}
|
||||
|
||||
bool HBFont::isItalic() const
|
||||
{
|
||||
uint32_t tag = HB_TAG('i', 't', 'a', 'l');
|
||||
float res = HBFont::GetStyle(m_font, tag);
|
||||
return res != 0.0;
|
||||
}
|
||||
|
||||
rive::rcp<rive::Font> HBFont::withOptions(
|
||||
rive::Span<const Coord> coords,
|
||||
rive::Span<const Feature> features) const
|
||||
@@ -514,7 +532,7 @@ void HBFont::shapeFallbackRun(rive::SimpleArrayBuilder<rive::GlyphRun>& gruns,
|
||||
// font-fallback
|
||||
size_t index = iter - gr.glyphs.begin();
|
||||
rive::Unichar missing = text[gr.textIndices[index]];
|
||||
auto fallback = HBFont::gFallbackProc(missing, fallbackIndex);
|
||||
auto fallback = HBFont::gFallbackProc(missing, fallbackIndex, this);
|
||||
if (fallback && fallback.get() != this)
|
||||
{
|
||||
perform_fallback(fallback,
|
||||
@@ -694,9 +712,8 @@ rive::SimpleArray<rive::Paragraph> HBFont::onShapeText(
|
||||
// font-fallback
|
||||
size_t index = iter - gr.glyphs.begin();
|
||||
rive::Unichar missing = text[gr.textIndices[index]];
|
||||
// todo: consider sending more chars if that helps choose a
|
||||
// font
|
||||
auto fallback = gFallbackProc(missing, 0);
|
||||
// todo: consider sending more chars if that helps choose a font
|
||||
auto fallback = gFallbackProc(missing, 0, this);
|
||||
if (fallback)
|
||||
{
|
||||
perform_fallback(fallback, gruns, text.data(), gr, tr, 1);
|
||||
|
||||
Binary file not shown.
BIN
tests/unit_tests/assets/fonts/Inter_18pt-Regular.ttf
Normal file
BIN
tests/unit_tests/assets/fonts/Inter_18pt-Regular.ttf
Normal file
Binary file not shown.
BIN
tests/unit_tests/assets/fonts/Inter_28pt-Bold.ttf
Normal file
BIN
tests/unit_tests/assets/fonts/Inter_28pt-Bold.ttf
Normal file
Binary file not shown.
BIN
tests/unit_tests/assets/fonts/OpenSans-ExtraBoldItalic.ttf
Normal file
BIN
tests/unit_tests/assets/fonts/OpenSans-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
tests/unit_tests/assets/fonts/OpenSans-Italic.ttf
Normal file
BIN
tests/unit_tests/assets/fonts/OpenSans-Italic.ttf
Normal file
Binary file not shown.
@@ -30,6 +30,7 @@ do
|
||||
'../../decoders/include',
|
||||
'../../renderer/include',
|
||||
'../../renderer/src',
|
||||
harfbuzz .. '/src',
|
||||
miniaudio,
|
||||
yoga,
|
||||
})
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "rive/text_engine.hpp"
|
||||
#include "rive/text/font_hb.hpp"
|
||||
#include "rive/text/utf.hpp"
|
||||
#include "hb.h"
|
||||
#include "hb-ot.h"
|
||||
#include <string>
|
||||
|
||||
using namespace rive;
|
||||
@@ -39,7 +41,8 @@ static rcp<Font> loadFont(const char* filename)
|
||||
|
||||
static std::vector<rive::rcp<rive::Font>> fallbackFonts;
|
||||
static rive::rcp<rive::Font> pickFallbackFont(const rive::Unichar missing,
|
||||
const uint32_t fallbackIndex)
|
||||
const uint32_t fallbackIndex,
|
||||
const rive::Font*)
|
||||
{
|
||||
if (fallbackIndex > 0)
|
||||
{
|
||||
@@ -57,7 +60,37 @@ static rive::rcp<rive::Font> pickFallbackFont(const rive::Unichar missing,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TEST_CASE("fallback glyphs are found", "[text]")
|
||||
TEST_CASE("Inspect Font Styles", "[text_styles]")
|
||||
{
|
||||
struct TestCaseData
|
||||
{
|
||||
const char* fontPath;
|
||||
uint16_t expectedWeight;
|
||||
bool expectedItalic;
|
||||
};
|
||||
|
||||
std::vector<TestCaseData> testCases = {
|
||||
{"assets/fonts/AdventPro-VariableFont_wdth,wght.ttf", 400, false},
|
||||
{"assets/fonts/Inter_18pt-Regular.ttf", 400, false},
|
||||
{"assets/fonts/Inter_28pt-Bold.ttf", 700, false},
|
||||
{"assets/fonts/OpenSans-Italic.ttf", 400, true},
|
||||
{"assets/fonts/OpenSans-ExtraBoldItalic.ttf", 800, true},
|
||||
};
|
||||
|
||||
for (const auto& testCase : testCases)
|
||||
{
|
||||
SECTION(testCase.fontPath)
|
||||
{
|
||||
rive::rcp<Font> font = loadFont(testCase.fontPath);
|
||||
HBFont* hbFont = static_cast<HBFont*>(font.get());
|
||||
|
||||
REQUIRE(hbFont->getWeight() == testCase.expectedWeight);
|
||||
REQUIRE(hbFont->isItalic() == testCase.expectedItalic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("fallback glyphs are found", "[text_fallback]")
|
||||
{
|
||||
REQUIRE(fallbackFonts.empty());
|
||||
auto font = loadFont("assets/RobotoFlex.ttf");
|
||||
|
||||
@@ -100,10 +100,10 @@ static float drawpara(rive::Factory* factory,
|
||||
return origin.y + lines.back().bottom;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
std::vector<rive::rcp<rive::Font>> fallbackFonts;
|
||||
static rive::rcp<rive::Font> pickFallbackFont(const rive::Unichar missing,
|
||||
const uint32_t fallbackIndex)
|
||||
const uint32_t fallbackIndex,
|
||||
const rive::Font*)
|
||||
{
|
||||
if (fallbackIndex > 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user