Skip to content

Commit b629d01

Browse files
authored
feat: add registerFont API for custom TTF font loading (#422)
* feat: add registerFont API for custom TTF font loading Adds new `registerFont` API to load custom TTF fonts at runtime, enabling proper text rendering with non-standard fonts in Lottie animations. Changes: - Core API: * Add `register_font` to Renderer trait as static method * Implement font loading in TvgRenderer with ThorVG's font API * Always copy font data (copy=true) for memory safety * Hardcode "ttf" mimetype for minimal binary size - Fallback Font (tvg-ttf feature): * Add compressed default font (9.5KB, decompresses to 14.8KB at runtime) * Auto-load fallback font on first renderer initialization * Use LZSS compression for optimal size/performance balance - FFI Layer: * Add `register_font([ByRef] string, [ByRef] bytes)` to UDL * Expose through DotLottiePlayer, DotLottiePlayerContainer, DotLottieRuntime * Add proper forwarding through all player abstraction layers - WASM Bindings: * Register `VectorChar` type for Emscripten binding * Expose as `registerFont(fontName: string, fontData: Uint8Array)` * Fix: Remove unnecessary `load_dotlottie_data` wrapper (use direct binding) * Fix: Bind `loadDotLottieData` directly to C++ method - Web Example: * Add `registerSystemFont()` helper to load fonts from Local Font Access API * Demonstrate loading system fonts (Arial, Impact) into player * Clean up font loading examples Benefits: - Enables custom font rendering in text layers - Minimal binary size impact (~100 bytes for API forwarding) - Type-safe Uint8Array → std::vector<char> conversion via registered types - Automatic fallback font ensures text always renders feat: add C FFI binding for registerFont API Adds the `dotlottie_register_font` C FFI function to enable custom font loading from C/C++ applications. The function accepts a font name (C string) and font data (byte pointer + size), following the same pattern as `dotlottie_load_dotlottie_data` for handling binary data safely across FFI boundaries. Changes: - Added dotlottie_register_font() in src/ffi/mod.rs - Updated bindings.h with the new C function declaration feat: add native features to cargo build command in Makefile * fix: lint warnings * fix: update pointer type for font data in register_font method * refactor: registerFont api from an intsnace method to a standalone function
1 parent ae60e55 commit b629d01

File tree

16 files changed

+219
-131
lines changed

16 files changed

+219
-131
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ RELEASE = release
115115
RUNTIME_FFI = dotlottie-ffi
116116
DOTLOTTIE_PLAYER = dotlottie-player
117117
RUNTIME_FFI_HEADER = dotlottie_player.h
118+
NATIVE_FEATURES = ffi,tvg,tvg-sw,tvg-webp,tvg-png,tvg-jpg,tvg-ttf,tvg-threads,tvg-lottie-expressions
118119

119120
DOTLOTTIE_PLAYER_NATIVE_RELEASE_DIR = $(RELEASE)/$(NATIVE)/$(DOTLOTTIE_PLAYER)
120121
DOTLOTTIE_PLAYER_NATIVE_RELEASE_INCLUDE_DIR = $(DOTLOTTIE_PLAYER_NATIVE_RELEASE_DIR)/include
@@ -132,7 +133,7 @@ endef
132133
# Build native libraries for the current platform
133134
native:
134135
@echo "Building native libraries for current platform..."
135-
cargo build --manifest-path $(RUNTIME_FFI)/Cargo.toml --release
136+
cargo build --manifest-path $(RUNTIME_FFI)/Cargo.toml --features $(NATIVE_FEATURES) --release
136137
$(NATIVE_RELEASE)
137138
@echo "✓ Native build complete. Artifacts available in $(RELEASE)/$(NATIVE)/"
138139

dotlottie-ffi/bindings.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,10 @@ int32_t dotlottie_pause(struct DotLottiePlayer *ptr);
314314

315315
int32_t dotlottie_play(struct DotLottiePlayer *ptr);
316316

317+
int32_t dotlottie_register_font(const char *font_name,
318+
const char *font_data,
319+
size_t font_data_size);
320+
317321
int32_t dotlottie_render(struct DotLottiePlayer *ptr);
318322

319323
int32_t dotlottie_request_frame(struct DotLottiePlayer *ptr, float *result);

dotlottie-ffi/emscripten_bindings.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ val buffer(DotLottiePlayer &player)
1414
return val(typed_memory_view(buffer_len, reinterpret_cast<uint8_t *>(buffer_ptr)));
1515
}
1616

17-
bool load_dotlottie_data(DotLottiePlayer &player, std::string data, uint32_t width, uint32_t height)
18-
{
19-
std::vector<char> data_vector(data.begin(), data.end());
20-
21-
return player.load_dotlottie_data(data_vector, width, height);
22-
}
23-
2417
struct ObserverCallbacks
2518
{
2619
std::function<void()> on_complete;
@@ -409,6 +402,7 @@ EMSCRIPTEN_BINDINGS(DotLottiePlayer)
409402
register_vector<float>("VectorFloat");
410403
register_vector<std::string>("VectorString");
411404
register_vector<Marker>("VectorMarker");
405+
register_vector<char>("VectorChar");
412406

413407
register_optional<std::vector<float>>();
414408
register_optional<std::string>();
@@ -466,6 +460,7 @@ EMSCRIPTEN_BINDINGS(DotLottiePlayer)
466460

467461
function("createDefaultConfig", &create_default_config);
468462
function("transformThemeToLottieSlots", &transform_theme_to_lottie_slots);
463+
function("registerFont", &register_font);
469464

470465
class_<Observer>("Observer")
471466
.smart_ptr<std::shared_ptr<Observer>>("Observer")
@@ -511,7 +506,7 @@ EMSCRIPTEN_BINDINGS(DotLottiePlayer)
511506
.function("isStopped", &DotLottiePlayer::is_stopped)
512507
.function("loadAnimationData", &DotLottiePlayer::load_animation_data)
513508
.function("loadAnimationPath", &DotLottiePlayer::load_animation_path)
514-
.function("loadDotLottieData", &load_dotlottie_data)
509+
.function("loadDotLottieData", &DotLottiePlayer::load_dotlottie_data)
515510
.function("loadAnimation", &DotLottiePlayer::load_animation)
516511
// .function("manifest", &DotLottiePlayer::manifest)
517512
.function("manifestString", &DotLottiePlayer::manifest_string)

dotlottie-ffi/src/dotlottie_player.udl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace dotlottie_player {
33
Config create_default_config();
44
OpenUrlPolicy create_default_open_url_policy();
55
string transform_theme_to_lottie_slots([ByRef] string theme_data, [ByRef] string animation_id);
6+
boolean register_font([ByRef] string font_name, [ByRef] bytes font_data);
67
};
78

89
[Trait, WithForeign]

dotlottie-ffi/src/ffi/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,3 +880,17 @@ pub unsafe extern "C" fn dotlottie_state_machine_internal_unsubscribe(
880880
}
881881
})
882882
}
883+
884+
#[no_mangle]
885+
pub unsafe extern "C" fn dotlottie_register_font(
886+
font_name: *const c_char,
887+
font_data: *const c_char,
888+
font_data_size: usize,
889+
) -> i32 {
890+
if let Ok(font_name) = DotLottieString::read(font_name) {
891+
let font_data_slice = slice::from_raw_parts(font_data as *const u8, font_data_size);
892+
to_exit_status(dotlottie_rs::register_font(&font_name, font_data_slice))
893+
} else {
894+
DOTLOTTIE_INVALID_PARAMETER
895+
}
896+
}

dotlottie-ffi/src/uniffi_impl.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ pub fn create_default_config() -> Config {
2020
pub fn transform_theme_to_lottie_slots(theme_data: &str, animation_id: &str) -> String {
2121
dotlottie_rs::transform_theme_to_lottie_slots(theme_data, animation_id).unwrap_or_default()
2222
}
23+
24+
pub fn register_font(font_name: &str, font_data: &[u8]) -> bool {
25+
dotlottie_rs::register_font(font_name, font_data)
26+
}

dotlottie-rs/src/dotlottie_player.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ impl DotLottieRuntime {
650650
// directly updating fields that don't require special handling
651651
self.config.use_frame_interpolation = new_config.use_frame_interpolation;
652652

653-
if Self::is_valid_segment(&new_config.segment) {
653+
if Self::is_valid_segment(&new_config.segment) {
654654
self.config.segment = new_config.segment;
655655
}
656656
self.config.autoplay = new_config.autoplay;

dotlottie-rs/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ pub use state_machine_engine::events::*;
1616
pub use state_machine_engine::security::*;
1717
pub use state_machine_engine::*;
1818
pub use theming::*;
19+
20+
#[cfg(feature = "tvg")]
21+
pub fn register_font(font_name: &str, font_data: &[u8]) -> bool {
22+
use lottie_renderer::Renderer;
23+
crate::TvgRenderer::register_font(font_name, font_data).is_ok()
24+
}
9.49 KB
Binary file not shown.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// The font is embedded as compressed data and decompressed at runtime using LZSS algorithm.
2+
3+
#[cfg(feature = "tvg-ttf")]
4+
const COMPRESSED_FONT_SIZE: usize = 9721;
5+
#[cfg(feature = "tvg-ttf")]
6+
const DEFAULT_FONT_SIZE: usize = 14852;
7+
#[cfg(feature = "tvg-ttf")]
8+
const COMPRESSED_FONT: &[u8] = include_bytes!("fallback_font.bin");
9+
#[cfg(feature = "tvg-ttf")]
10+
const DEFAULT_FONT_NAME: &str = "default";
11+
12+
#[cfg(feature = "tvg-ttf")]
13+
pub fn font() -> (&'static str, Vec<u8>) {
14+
let mut output = vec![0u8; DEFAULT_FONT_SIZE];
15+
let mut input_pos = 0;
16+
let mut output_pos = 0;
17+
18+
while input_pos < COMPRESSED_FONT_SIZE && output_pos < DEFAULT_FONT_SIZE {
19+
let flags = COMPRESSED_FONT[input_pos];
20+
input_pos += 1;
21+
22+
for bit in 0..8 {
23+
if output_pos >= DEFAULT_FONT_SIZE {
24+
break;
25+
}
26+
27+
if (flags & (1 << bit)) != 0 {
28+
// Literal byte
29+
output[output_pos] = COMPRESSED_FONT[input_pos];
30+
output_pos += 1;
31+
input_pos += 1;
32+
} else {
33+
// Back reference
34+
let offset_high = (COMPRESSED_FONT[input_pos] as u16) << 4;
35+
input_pos += 1;
36+
let length_offset = COMPRESSED_FONT[input_pos];
37+
input_pos += 1;
38+
39+
let offset = offset_high | ((length_offset >> 4) as u16);
40+
let length = ((length_offset & 0x0F) + 3) as usize;
41+
42+
let copy_pos = output_pos - offset as usize;
43+
for i in 0..length {
44+
if output_pos >= DEFAULT_FONT_SIZE {
45+
break;
46+
}
47+
output[output_pos] = output[copy_pos + i];
48+
output_pos += 1;
49+
}
50+
}
51+
}
52+
}
53+
54+
(DEFAULT_FONT_NAME, output)
55+
}

0 commit comments

Comments
 (0)