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:
dskuza
2024-11-27 16:50:58 +00:00
parent e3e0556f64
commit a629422a34
16 changed files with 113 additions and 48 deletions

View File

@@ -1 +1 @@
fb8ecf3552aeece0b1d242804580a016b6297ad4
7986d64d8371531716ea3f038dcbec5da187e6cd

View File

@@ -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

View File

@@ -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',

View File

@@ -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',

View File

@@ -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:

View File

@@ -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) {}

View File

@@ -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 ..
}

View File

@@ -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.

Binary file not shown.

Binary file not shown.

View File

@@ -30,6 +30,7 @@ do
'../../decoders/include',
'../../renderer/include',
'../../renderer/src',
harfbuzz .. '/src',
miniaudio,
yoga,
})

View File

@@ -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");

View File

@@ -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)
{