Skip to content

Commit

Permalink
Support supplying mobile fallback fonts by style with caching
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: Umberto <[email protected]>
Co-authored-by: Umberto Sonnino <[email protected]>
  • Loading branch information
4 people committed Nov 27, 2024
1 parent ba494aa commit 1c87f97
Show file tree
Hide file tree
Showing 14 changed files with 613 additions and 94 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
fb8ecf3552aeece0b1d242804580a016b6297ad4
7986d64d8371531716ea3f038dcbec5da187e6cd
Binary file modified Example-iOS/Assets/fallback_fonts.riv
Binary file not shown.
36 changes: 32 additions & 4 deletions Example-iOS/Source/Examples/SwiftUI/SwiftFallbackFonts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import RiveRuntime
struct SwiftFallbackFonts: View, DismissableView {
var dismiss: () -> Void = {}

@StateObject private var viewModel = RiveViewModel(fileName: "fallback_fonts")
@StateObject private var viewModel = RiveViewModel(fileName: "fallback_fonts", fit: .fill)

private var runBinding: Binding<String> {
Binding {
return self.viewModel.getTextRunValue("text") ?? ""
return self.viewModel.getTextRunValue("ultralight") ?? ""
}
set: { text in
try? self.viewModel.setTextRunValue("text", textValue: text)
try? self.viewModel.setTextRunValue("ultralight", textValue: text)
try? self.viewModel.setTextRunValue("regular", textValue: text)
try? self.viewModel.setTextRunValue("bold", textValue: text)
try? self.viewModel.setTextRunValue("black", textValue: text)
self.viewModel.play()
}
}
Expand All @@ -29,7 +32,7 @@ struct SwiftFallbackFonts: View, DismissableView {
viewModel.view().scaledToFit()

Text(
"The included Rive font only contains characters in the set A...G. Fallback font(s) will be used to draw missing characters."
"The included Rive font only contains characters in the set A...G. Fallback font(s) will be used to draw missing characters with the correct weight."
)
.fixedSize(horizontal: false, vertical: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
.font(.caption)
Expand All @@ -42,6 +45,8 @@ struct SwiftFallbackFonts: View, DismissableView {
Spacer().frame(maxHeight: .infinity)
}
.onAppear {
// Set fallback fonts to be used for all styles.
// If fallbackFontsCallback is set, this is unused.
RiveFont.fallbackFonts = [
// You can use a font descriptor that will generate a system font
RiveFallbackFontDescriptor(design: .default, weight: .regular, width: .standard),
Expand All @@ -50,6 +55,29 @@ struct SwiftFallbackFonts: View, DismissableView {
// ...or a UIFont by name, or any way of initializing a UIFont
UIFont(name: "Times New Roman", size: 12)!
]
// ...or set a callback that returns different fonts based on style.
// If fallbackFonts is set, this is unused.
RiveFont.fallbackFontsCallback = { (style: RiveFontStyle) -> [RiveFallbackFontProvider] in
switch style.weight {
case .ultraLight: return [
RiveFallbackFontDescriptor(weight: .ultraLight),
UIFont.systemFont(ofSize: 20, weight: .ultraLight)
]
case .regular: return [
RiveFallbackFontDescriptor(),
UIFont.systemFont(ofSize: 20, weight: .regular)
]
case .bold: return [
RiveFallbackFontDescriptor(weight: .bold),
UIFont.systemFont(ofSize: 20, weight: .bold)
]
case .black: return [
RiveFallbackFontDescriptor(weight: .black),
UIFont.systemFont(ofSize: 20, weight: .black)
]
default: return [RiveFallbackFontDescriptor()]
}
}
}
}
}
16 changes: 16 additions & 0 deletions RiveRuntime.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
F2D285492C6D469900728340 /* RiveFallbackFontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D285482C6D469900728340 /* RiveFallbackFontProvider.swift */; };
F2ECC2312C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECC2302C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift */; };
F2ECC23A2C66B949008B20E5 /* RiveFontTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECC2382C66B920008B20E5 /* RiveFontTests.swift */; };
F2FD94042CC9492B00C1FC85 /* RiveFont.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FD94032CC9492B00C1FC85 /* RiveFont.m */; };
F2FD94052CC9492B00C1FC85 /* RiveFont.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FD94022CC9492B00C1FC85 /* RiveFont.h */; settings = {ATTRIBUTES = (Public, ); }; };
F2FD94082CC94B1A00C1FC85 /* RiveFallbackFontCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F2FD94072CC94B1A00C1FC85 /* RiveFallbackFontCache.m */; };
F2FD94092CC94B1A00C1FC85 /* RiveFallbackFontCache.h in Headers */ = {isa = PBXBuildFile; fileRef = F2FD94062CC94B1A00C1FC85 /* RiveFallbackFontCache.h */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -224,6 +228,10 @@
F2D285482C6D469900728340 /* RiveFallbackFontProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFallbackFontProvider.swift; sourceTree = "<group>"; };
F2ECC2302C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveFallbackFontDescriptor+Extensions.swift"; sourceTree = "<group>"; };
F2ECC2382C66B920008B20E5 /* RiveFontTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveFontTests.swift; sourceTree = "<group>"; };
F2FD94022CC9492B00C1FC85 /* RiveFont.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveFont.h; sourceTree = "<group>"; };
F2FD94032CC9492B00C1FC85 /* RiveFont.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RiveFont.m; sourceTree = "<group>"; };
F2FD94062CC94B1A00C1FC85 /* RiveFallbackFontCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RiveFallbackFontCache.h; sourceTree = "<group>"; };
F2FD94072CC94B1A00C1FC85 /* RiveFallbackFontCache.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RiveFallbackFontCache.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -430,6 +438,10 @@
F21F08132C66526D00FFA205 /* RiveFallbackFontDescriptor.swift */,
F2ECC2302C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift */,
F2D285482C6D469900728340 /* RiveFallbackFontProvider.swift */,
F2FD94022CC9492B00C1FC85 /* RiveFont.h */,
F2FD94032CC9492B00C1FC85 /* RiveFont.m */,
F2FD94062CC94B1A00C1FC85 /* RiveFallbackFontCache.h */,
F2FD94072CC94B1A00C1FC85 /* RiveFallbackFontCache.m */,
);
path = Fonts;
sourceTree = "<group>";
Expand Down Expand Up @@ -478,8 +490,10 @@
E5964A962A965A9300140479 /* RiveEvent.h in Headers */,
043026022AFB9FCD00320F2E /* RiveFactory.h in Headers */,
C9C741F424FC510200EF9516 /* Rive.h in Headers */,
F2FD94052CC9492B00C1FC85 /* RiveFont.h in Headers */,
04BE5436264D2A7500427B39 /* RivePrivateHeaders.h in Headers */,
C9C73EE224FC478900EF9516 /* RiveRuntime.h in Headers */,
F2FD94092CC94B1A00C1FC85 /* RiveFallbackFontCache.h in Headers */,
83DE4CA72AAAE72100B88B72 /* RenderContext.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -632,6 +646,7 @@
C3E2B580282F242400A8651B /* RiveStateMachineInstance+Extensions.swift in Sources */,
046FB7F5264EAA60000129B1 /* RiveSMIInput.mm in Sources */,
043026002AFA915B00320F2E /* RiveFileAsset.mm in Sources */,
F2FD94042CC9492B00C1FC85 /* RiveFont.m in Sources */,
C3468E5A27ECC7C6008652FD /* RiveViewModel.swift in Sources */,
C3745FD3282BFAB90087F4AF /* FPSCounterView.swift in Sources */,
F2ECC2312C666824008B20E5 /* RiveFallbackFontDescriptor+Extensions.swift in Sources */,
Expand All @@ -640,6 +655,7 @@
046FB7F8264EAA60000129B1 /* RiveStateMachineInstance.mm in Sources */,
C3468E5C27ED4C41008652FD /* RiveModel.swift in Sources */,
046FB7FF264EAA61000129B1 /* RiveFile.mm in Sources */,
F2FD94082CC94B1A00C1FC85 /* RiveFallbackFontCache.m in Sources */,
046FB7F2264EAA60000129B1 /* RiveArtboard.mm in Sources */,
F2610DD22CA5B4C40090D50B /* RiveLogger+StateMachine.swift in Sources */,
046FB7F4264EAA60000129B1 /* RiveLinearAnimationInstance.mm in Sources */,
Expand Down
44 changes: 44 additions & 0 deletions Source/Fonts/RiveFallbackFontCache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// RiveFallbackFontCache.h
// RiveRuntime
//
// Created by David Skuza on 10/23/24.
// Copyright © 2024 Rive. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <rive/text/font_hb.hpp>
#import <RiveRuntime/RiveRuntime-Swift.h>

@class RiveFontStyle;

NS_ASSUME_NONNULL_BEGIN

/// An object that can be used as a dictionary key when caching fallback fonts.
/// - Note: This implements NSCopying and overrides `isEqual` and `hash` to add
/// support for an object of this type to be used as a key in dictionaries.
@interface RiveFallbackFontCacheKey : NSObject <NSCopying>
/// The style of the requested fallback font to be cached.
@property(nonatomic, readonly, nonnull) RiveFontStyle* style;
/// The actual character for which a fallback font is being requested.
@property(nonatomic, readonly) rive::Unichar character;
/// The fallback index used when originally requesting a fallback.
@property(nonatomic, readonly) uint32_t index;
- (instancetype)initWithStyle:(RiveFontStyle*)style
character:(rive::Unichar)character
index:(uint32_t)index;
@end

/// An object that can be used as a dictionary value (typically keyed to
/// `RiveFallbackFontCacheKey`), which contains the cached font types.
@interface RiveFallbackFontCacheValue : NSObject
/// The native font type used as the fallback (passed to the C++
/// runtime). On iOS, this will be UIFont. On macOS, this
/// will be NSFont.
@property(nonatomic, readonly) id font;
/// Whether the font used the system shaper (i.e Core Text over Harfbuzz)
@property(nonatomic, readonly) BOOL usesSystemShaper;
- (instancetype)initWithFont:(id)font usesSystemShaper:(BOOL)usesSystemShaper;
@end

NS_ASSUME_NONNULL_END
83 changes: 83 additions & 0 deletions Source/Fonts/RiveFallbackFontCache.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// RiveFallbackFontCache.m
// RiveRuntime
//
// Created by David Skuza on 10/23/24.
// Copyright © 2024 Rive. All rights reserved.
//

#import "RiveFallbackFontCache.h"
#import "RiveFont.h"

@implementation RiveFallbackFontCacheKey
@synthesize style = _style;
@synthesize character = _character;
@synthesize index = _index;

- (instancetype)initWithStyle:(RiveFontStyle*)style
character:(rive::Unichar)character
index:(uint32_t)index
{
if (self = [super init])
{
_style = style;
_character = character;
_index = index;
}
return self;
}

- (BOOL)isEqual:(id)object
{
if (object == nil)
{
return NO;
}

if (![object isKindOfClass:[RiveFallbackFontCacheKey class]])
{
return NO;
}

if (self == object)
{
return YES;
}

RiveFallbackFontCacheKey* other = (RiveFallbackFontCacheKey*)object;
return [self.style isEqual:other.style] &&
self.character == other.character && self.index == other.index;
}

- (NSUInteger)hash
{
// This is a super basic hash function that may be able to be improved.
// However, I don't imagine many collisions will happen based on the
// simplicity of our current use case. - David
return [self.style hash] ^ self.character ^ self.index;
}

- (id)copyWithZone:(NSZone*)zone
{
return [[RiveFallbackFontCacheKey alloc] initWithStyle:[self.style copy]
character:self.character
index:self.index];
}

@end

@implementation RiveFallbackFontCacheValue
@synthesize font = _font;
@synthesize usesSystemShaper = _usesSystemShaper;

- (instancetype)initWithFont:(id)font usesSystemShaper:(BOOL)usesSystemShaper;
{
if (self = [super init])
{
_font = font;
_usesSystemShaper = usesSystemShaper;
}
return self;
}

@end
4 changes: 2 additions & 2 deletions Source/Fonts/RiveFallbackFontDescriptor+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ extension RiveNativeFont: RiveWeightProvider {
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping
// - David
switch weight {
case RiveNativeFontWeight.ultraLight: return 100
case RiveNativeFontWeight.thin: return 200
case RiveNativeFontWeight.thin: return 100
case RiveNativeFontWeight.ultraLight: return 200
case RiveNativeFontWeight.light: return 300
case RiveNativeFontWeight.regular: return 400
case RiveNativeFontWeight.medium: return 500
Expand Down
59 changes: 59 additions & 0 deletions Source/Fonts/RiveFont.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// RiveFont.h
// RiveRuntime
//
// Created by David Skuza on 10/23/24.
// Copyright © 2024 Rive. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/// An enumeration of possible weight values, mirroring those of
/// UIFont/NSFont.Weight
typedef NS_ENUM(NSInteger, RiveFontStyleWeight) {
RiveFontStyleWeightThin = 100,
RiveFontStyleWeightUltraLight = 200,
RiveFontStyleWeightLight = 300,
RiveFontStyleWeightRegular = 400,
RiveFontStyleWeightMedium = 500,
RiveFontStyleWeightSemibold = 600,
RiveFontStyleWeightBold = 700,
RiveFontStyleWeightHeavy = 800,
RiveFontStyleWeightBlack = 900
};

/// An object that represents the styling of a font.
@interface RiveFontStyle : NSObject <NSCopying>
/// The weight of the font. See `RiveFontStyleWeight` for possible values.
/// This value is computed by rounding `rawWeight` to the nearest hundredth.
@property(nonatomic, readonly) RiveFontStyleWeight weight;
/// The raw weight of the font. This value is used to generate the `weight` of
/// the style.
@property(nonatomic, readonly) CGFloat rawWeight;
- (instancetype)initWithWeight:(RiveFontStyleWeight)weight;
- (instancetype)initWithRawWeight:(CGFloat)rawWeight;
@end

@protocol RiveFallbackFontProvider;

typedef NSArray<id<RiveFallbackFontProvider>>* _Nonnull (
^RiveFallbackFontsCallback)(RiveFontStyle*);

@interface RiveFont : NSObject
/// An array of font descriptors to attempt to use when text being rendererd by
/// Rive uses a font that is missing a glyph. The fonts will be tried in the
/// order in which they are added to the array.
/// - Note: If unset, the default fallback is a default system font, with
/// regular font weight.
@property(class, copy, nonnull)
NSArray<id<RiveFallbackFontProvider>>* fallbackFonts;
/// A block that requests fallback font providers, given a font style.
/// This way, different fallback fonts can be used depending on the styling
/// of the font at draw-time (e.g weight).
@property(class, nonatomic, copy, nonnull)
RiveFallbackFontsCallback fallbackFontsCallback;
@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 1c87f97

Please sign in to comment.