From 10ea022df8dcc69ebc76ed624c8058a0e28ef133 Mon Sep 17 00:00:00 2001 From: "zhuoxian.dzx" Date: Fri, 22 Nov 2024 14:08:02 +0800 Subject: [PATCH] feat: support lru cache to limit registry memory usage --- package.json | 2 +- src/bindings.rs | 128 +++++++++++++++++++++----------------- src/font.rs | 24 +++++++- src/lib.rs | 159 +++++++++++++++++++++++++++++++----------------- src/wit.rs | 7 ++- tests/basic.rs | 18 ++++++ wit/world.wit | 2 + 7 files changed, 226 insertions(+), 114 deletions(-) diff --git a/package.json b/package.json index 543236f..5c1b0b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fontkit-rs", - "version": "0.0.14-beta.1", + "version": "0.0.14", "description": "Toolkit used to load, match, measure, and render texts", "main": "index.js", "directories": { diff --git a/src/bindings.rs b/src/bindings.rs index 7bdf57e..bf6126d 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -1426,6 +1426,16 @@ pub mod exports { } #[doc(hidden)] #[allow(non_snake_case)] + pub unsafe fn _export_method_font_kit_set_lru_limit_cabi( + arg0: *mut u8, + arg1: i32, + ) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + T::set_lru_limit(FontKitBorrow::lift(arg0 as u32 as usize).get(), arg1 as u32); + } + #[doc(hidden)] + #[allow(non_snake_case)] pub unsafe fn _export_method_font_kit_add_font_from_buffer_cabi( arg0: *mut u8, arg1: *mut u8, @@ -2727,6 +2737,9 @@ pub mod exports { } fn new() -> Self; + /// add an LRU limit for font buffer registry, `limit`'s + /// unit is KB, 0 means caching is disabled + fn set_lru_limit(&self, limit: u32); /// Register a font (or several fonts in case of ttc), /// return the keys of added fonts. /// The file type is extracted from the buffer by checking @@ -2949,6 +2962,10 @@ pub mod exports { unsafe extern "C" fn export_constructor_font_kit() -> i32 { $($path_to_types)*::_export_constructor_font_kit_cabi::<<$ty as $($path_to_types)*::Guest>::FontKit>() } + #[export_name = "alibaba:fontkit/fontkit-interface#[method]font-kit.set-lru-limit"] + unsafe extern "C" fn export_method_font_kit_set_lru_limit(arg0: *mut u8,arg1: i32,) { + $($path_to_types)*::_export_method_font_kit_set_lru_limit_cabi::<<$ty as $($path_to_types)*::Guest>::FontKit>(arg0, arg1) + } #[export_name = "alibaba:fontkit/fontkit-interface#[method]font-kit.add-font-from-buffer"] unsafe extern "C" fn export_method_font_kit_add_font_from_buffer(arg0: *mut u8,arg1: *mut u8,arg2: usize,) { $($path_to_types)*::_export_method_font_kit_add_font_from_buffer_cabi::<<$ty as $($path_to_types)*::Guest>::FontKit>(arg0, arg1, arg2) @@ -3345,63 +3362,64 @@ pub(crate) use __export_fontkit_impl as export; #[cfg(target_arch = "wasm32")] #[link_section = "component-type:wit-bindgen:0.25.0:fontkit:encoded world"] #[doc(hidden)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 2844] = *b"\ -\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x9e\x15\x01A\x02\x01\ +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 2900] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xd6\x15\x01A\x02\x01\ A\x05\x01B\x06\x01k{\x01k\x7f\x01o\x02sv\x01p\x02\x01r\x05\x06weight\0\x06italic\ \x01\x07stretch\0\x06familys\x0avariations\x03\x04\0\x08font-key\x03\0\x04\x03\x01\ -\x17alibaba:fontkit/commons\x05\0\x02\x03\0\0\x08font-key\x01B~\x02\x03\x02\x01\x01\ -\x04\0\x08font-key\x03\0\0\x01r\x03\x02id{\x04names\x0blanguage-id{\x04\0\x04nam\ -e\x03\0\x02\x01p\x03\x01ks\x01r\x04\x0bstyle-names\x04\x05names\x04\x04path\x05\x03\ -key\x01\x04\0\x09font-info\x03\0\x06\x01r\x02\x08position|\x09thickness|\x04\0\x0c\ -line-metrics\x03\0\x08\x04\0\x0ctext-metrics\x03\x01\x04\0\x0cglyph-bitmap\x03\x01\ -\x04\0\x04font\x03\x01\x04\0\x08font-kit\x03\x01\x01i\x0a\x01@\x01\x05values\0\x0e\ -\x04\0\x19[constructor]text-metrics\x01\x0f\x01h\x0a\x01@\x01\x04self\x10\0\x0e\x04\ -\0\x1e[method]text-metrics.duplicate\x01\x11\x01@\x03\x04self\x10\x09font-sizev\x0e\ -letter-spacingv\0v\x04\0\x1a[method]text-metrics.width\x01\x12\x01kv\x01@\x03\x04\ -self\x10\x09font-sizev\x0bline-height\x13\0v\x04\0\x1b[method]text-metrics.heigh\ -t\x01\x14\x01@\x02\x04self\x10\x09font-sizev\0v\x04\0\x1d[method]text-metrics.as\ -cender\x01\x15\x01@\x01\x04self\x10\0v\x04\0\x1d[method]text-metrics.line-gap\x01\ -\x16\x04\0\x1a[method]text-metrics.units\x01\x16\x01@\x03\x04self\x10\x05starty\x05\ -county\0\x0e\x04\0\x1a[method]text-metrics.slice\x01\x17\x01@\x01\x04self\x10\0s\ -\x04\0\x1a[method]text-metrics.value\x01\x18\x01@\x01\x04self\x10\0\x7f\x04\0\x1b\ -[method]text-metrics.is-rtl\x01\x19\x01@\x02\x04self\x10\x05other\x0e\x01\0\x04\0\ -\x1b[method]text-metrics.append\x01\x1a\x01@\x01\x04self\x10\0y\x04\0\x1a[method\ -]text-metrics.count\x01\x1b\x01@\x03\x04self\x10\x05other\x0e\x08fallback\x7f\x01\ -\0\x04\0\x1c[method]text-metrics.replace\x01\x1c\x01@\x04\x04self\x10\x09font-si\ -zev\x0eletter-spacingv\x05widthv\0\x0e\x04\0#[method]text-metrics.split-by-width\ -\x01\x1d\x01pt\x01@\x01\x04self\x10\0\x1e\x04\0\x1a[method]text-metrics.chars\x01\ -\x1f\x01h\x0b\x01@\x01\x04self\x20\0y\x04\0\x1a[method]glyph-bitmap.width\x01!\x04\ -\0\x1b[method]glyph-bitmap.height\x01!\x01p}\x01@\x01\x04self\x20\0\"\x04\0\x1b[\ -method]glyph-bitmap.bitmap\x01#\x01@\x01\x04self\x20\0v\x04\0\x1a[method]glyph-b\ -itmap.x-min\x01$\x04\0\x1a[method]glyph-bitmap.y-max\x01$\x04\0\x1d[method]glyph\ --bitmap.stroke-x\x01$\x04\0\x1d[method]glyph-bitmap.stroke-y\x01$\x01o\x02\"y\x01\ -k%\x01@\x01\x04self\x20\0&\x04\0\"[method]glyph-bitmap.stroke-bitmap\x01'\x04\0\x1f\ -[method]glyph-bitmap.advanced-x\x01$\x04\0\x1d[method]glyph-bitmap.ascender\x01$\ -\x04\0\x1e[method]glyph-bitmap.descender\x01$\x01h\x0c\x01@\x02\x04self(\x01ct\0\ -\x7f\x04\0\x16[method]font.has-glyph\x01)\x01@\x02\x04self(\x01ct\0\x05\x04\0\x1e\ -[method]font.glyph-path-string\x01*\x01@\x01\x04self(\0\"\x04\0\x13[method]font.\ -buffer\x01+\x01@\x01\x04self(\0s\x04\0\x11[method]font.path\x01,\x01@\x01\x04sel\ -f(\0\x01\x04\0\x10[method]font.key\x01-\x01j\x01\x0e\x01s\x01@\x02\x04self(\x04t\ -exts\0.\x04\0\x14[method]font.measure\x01/\x01@\x01\x04self(\0|\x04\0\x15[method\ -]font.ascender\x010\x04\0\x16[method]font.descender\x010\x01@\x01\x04self(\0{\x04\ -\0\x19[method]font.units-per-em\x011\x01i\x0b\x01k2\x01@\x04\x04self(\x01ct\x09f\ -ont-sizev\x0cstroke-widthv\03\x04\0\x13[method]font.bitmap\x014\x01k\x09\x01@\x01\ -\x04self(\05\x04\0\x1e[method]font.underline-metrics\x016\x01i\x0d\x01@\0\07\x04\ -\0\x15[constructor]font-kit\x018\x01h\x0d\x01@\x02\x04self9\x06buffer\"\x01\0\x04\ -\0%[method]font-kit.add-font-from-buffer\x01:\x01@\x02\x04self9\x04paths\x01\0\x04\ -\0\x20[method]font-kit.add-search-path\x01;\x01i\x0c\x01k<\x01@\x02\x04self9\x03\ -key\x01\0=\x04\0\x16[method]font-kit.query\x01>\x01p\x07\x01k?\x01@\x02\x04self9\ -\x03key\x01\0\xc0\0\x04\0\x20[method]font-kit.query-font-info\x01A\x04\0\x1c[met\ -hod]font-kit.exact-match\x01>\x01@\x01\x04self9\0?\x04\0\x1b[method]font-kit.fon\ -ts-info\x01B\x01@\x01\x04self9\0y\x04\0\x14[method]font-kit.len\x01C\x01@\x02\x04\ -self9\x03key\x01\x01\0\x04\0\x17[method]font-kit.remove\x01D\x01k\x0e\x01@\x03\x04\ -self9\x03key\x01\x04texts\0\xc5\0\x04\0\x18[method]font-kit.measure\x01F\x01@\x01\ -\x04self9\0s\x04\0\x1b[method]font-kit.write-data\x01G\x01@\x02\x04self9\x04data\ -s\x01\0\x04\0\x1a[method]font-kit.read-data\x01H\x01@\x01\x05widths\0{\x04\0\x13\ -str-width-to-number\x01I\x01@\x01\x05width{\0s\x04\0\x13number-width-to-str\x01J\ -\x04\x01!alibaba:fontkit/fontkit-interface\x05\x02\x04\x01\x17alibaba:fontkit/fo\ -ntkit\x04\0\x0b\x0d\x01\0\x07fontkit\x03\0\0\0G\x09producers\x01\x0cprocessed-by\ -\x02\x0dwit-component\x070.208.1\x10wit-bindgen-rust\x060.25.0"; +\x17alibaba:fontkit/commons\x05\0\x02\x03\0\0\x08font-key\x01B\x80\x01\x02\x03\x02\ +\x01\x01\x04\0\x08font-key\x03\0\0\x01r\x03\x02id{\x04names\x0blanguage-id{\x04\0\ +\x04name\x03\0\x02\x01p\x03\x01ks\x01r\x04\x0bstyle-names\x04\x05names\x04\x04pa\ +th\x05\x03key\x01\x04\0\x09font-info\x03\0\x06\x01r\x02\x08position|\x09thicknes\ +s|\x04\0\x0cline-metrics\x03\0\x08\x04\0\x0ctext-metrics\x03\x01\x04\0\x0cglyph-\ +bitmap\x03\x01\x04\0\x04font\x03\x01\x04\0\x08font-kit\x03\x01\x01i\x0a\x01@\x01\ +\x05values\0\x0e\x04\0\x19[constructor]text-metrics\x01\x0f\x01h\x0a\x01@\x01\x04\ +self\x10\0\x0e\x04\0\x1e[method]text-metrics.duplicate\x01\x11\x01@\x03\x04self\x10\ +\x09font-sizev\x0eletter-spacingv\0v\x04\0\x1a[method]text-metrics.width\x01\x12\ +\x01kv\x01@\x03\x04self\x10\x09font-sizev\x0bline-height\x13\0v\x04\0\x1b[method\ +]text-metrics.height\x01\x14\x01@\x02\x04self\x10\x09font-sizev\0v\x04\0\x1d[met\ +hod]text-metrics.ascender\x01\x15\x01@\x01\x04self\x10\0v\x04\0\x1d[method]text-\ +metrics.line-gap\x01\x16\x04\0\x1a[method]text-metrics.units\x01\x16\x01@\x03\x04\ +self\x10\x05starty\x05county\0\x0e\x04\0\x1a[method]text-metrics.slice\x01\x17\x01\ +@\x01\x04self\x10\0s\x04\0\x1a[method]text-metrics.value\x01\x18\x01@\x01\x04sel\ +f\x10\0\x7f\x04\0\x1b[method]text-metrics.is-rtl\x01\x19\x01@\x02\x04self\x10\x05\ +other\x0e\x01\0\x04\0\x1b[method]text-metrics.append\x01\x1a\x01@\x01\x04self\x10\ +\0y\x04\0\x1a[method]text-metrics.count\x01\x1b\x01@\x03\x04self\x10\x05other\x0e\ +\x08fallback\x7f\x01\0\x04\0\x1c[method]text-metrics.replace\x01\x1c\x01@\x04\x04\ +self\x10\x09font-sizev\x0eletter-spacingv\x05widthv\0\x0e\x04\0#[method]text-met\ +rics.split-by-width\x01\x1d\x01pt\x01@\x01\x04self\x10\0\x1e\x04\0\x1a[method]te\ +xt-metrics.chars\x01\x1f\x01h\x0b\x01@\x01\x04self\x20\0y\x04\0\x1a[method]glyph\ +-bitmap.width\x01!\x04\0\x1b[method]glyph-bitmap.height\x01!\x01p}\x01@\x01\x04s\ +elf\x20\0\"\x04\0\x1b[method]glyph-bitmap.bitmap\x01#\x01@\x01\x04self\x20\0v\x04\ +\0\x1a[method]glyph-bitmap.x-min\x01$\x04\0\x1a[method]glyph-bitmap.y-max\x01$\x04\ +\0\x1d[method]glyph-bitmap.stroke-x\x01$\x04\0\x1d[method]glyph-bitmap.stroke-y\x01\ +$\x01o\x02\"y\x01k%\x01@\x01\x04self\x20\0&\x04\0\"[method]glyph-bitmap.stroke-b\ +itmap\x01'\x04\0\x1f[method]glyph-bitmap.advanced-x\x01$\x04\0\x1d[method]glyph-\ +bitmap.ascender\x01$\x04\0\x1e[method]glyph-bitmap.descender\x01$\x01h\x0c\x01@\x02\ +\x04self(\x01ct\0\x7f\x04\0\x16[method]font.has-glyph\x01)\x01@\x02\x04self(\x01\ +ct\0\x05\x04\0\x1e[method]font.glyph-path-string\x01*\x01@\x01\x04self(\0\"\x04\0\ +\x13[method]font.buffer\x01+\x01@\x01\x04self(\0s\x04\0\x11[method]font.path\x01\ +,\x01@\x01\x04self(\0\x01\x04\0\x10[method]font.key\x01-\x01j\x01\x0e\x01s\x01@\x02\ +\x04self(\x04texts\0.\x04\0\x14[method]font.measure\x01/\x01@\x01\x04self(\0|\x04\ +\0\x15[method]font.ascender\x010\x04\0\x16[method]font.descender\x010\x01@\x01\x04\ +self(\0{\x04\0\x19[method]font.units-per-em\x011\x01i\x0b\x01k2\x01@\x04\x04self\ +(\x01ct\x09font-sizev\x0cstroke-widthv\03\x04\0\x13[method]font.bitmap\x014\x01k\ +\x09\x01@\x01\x04self(\05\x04\0\x1e[method]font.underline-metrics\x016\x01i\x0d\x01\ +@\0\07\x04\0\x15[constructor]font-kit\x018\x01h\x0d\x01@\x02\x04self9\x05limity\x01\ +\0\x04\0\x1e[method]font-kit.set-lru-limit\x01:\x01@\x02\x04self9\x06buffer\"\x01\ +\0\x04\0%[method]font-kit.add-font-from-buffer\x01;\x01@\x02\x04self9\x04paths\x01\ +\0\x04\0\x20[method]font-kit.add-search-path\x01<\x01i\x0c\x01k=\x01@\x02\x04sel\ +f9\x03key\x01\0>\x04\0\x16[method]font-kit.query\x01?\x01p\x07\x01k\xc0\0\x01@\x02\ +\x04self9\x03key\x01\0\xc1\0\x04\0\x20[method]font-kit.query-font-info\x01B\x04\0\ +\x1c[method]font-kit.exact-match\x01?\x01@\x01\x04self9\0\xc0\0\x04\0\x1b[method\ +]font-kit.fonts-info\x01C\x01@\x01\x04self9\0y\x04\0\x14[method]font-kit.len\x01\ +D\x01@\x02\x04self9\x03key\x01\x01\0\x04\0\x17[method]font-kit.remove\x01E\x01k\x0e\ +\x01@\x03\x04self9\x03key\x01\x04texts\0\xc6\0\x04\0\x18[method]font-kit.measure\ +\x01G\x01@\x01\x04self9\0s\x04\0\x1b[method]font-kit.write-data\x01H\x01@\x02\x04\ +self9\x04datas\x01\0\x04\0\x1a[method]font-kit.read-data\x01I\x01@\x01\x05widths\ +\0{\x04\0\x13str-width-to-number\x01J\x01@\x01\x05width{\0s\x04\0\x13number-widt\ +h-to-str\x01K\x04\x01!alibaba:fontkit/fontkit-interface\x05\x02\x04\x01\x17aliba\ +ba:fontkit/fontkit\x04\0\x0b\x0d\x01\0\x07fontkit\x03\0\0\0G\x09producers\x01\x0c\ +processed-by\x02\x0dwit-component\x070.208.1\x10wit-bindgen-rust\x060.25.0"; #[inline(never)] #[doc(hidden)] diff --git a/src/font.rs b/src/font.rs index 775dc66..e8c9f8a 100644 --- a/src/font.rs +++ b/src/font.rs @@ -10,6 +10,7 @@ use std::hash::Hash; #[cfg(feature = "parse")] use std::io::Read; use std::path::PathBuf; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; pub use ttf_parser::LineMetrics; use ttf_parser::{Face, Fixed, Tag, VariationAxis, Width as ParserWidth}; @@ -400,6 +401,8 @@ pub(crate) struct Font { buffer: ArcSwap>, /// [Font variation](https://learn.microsoft.com/en-us/typography/opentype/spec/fvar) and font collection data variants: Vec, + hit_counter: Arc, + pub(crate) hit_index: AtomicU32, } impl Font { @@ -416,7 +419,10 @@ impl Font { } #[cfg(feature = "parse")] - pub(super) fn from_buffer(mut buffer: Vec) -> Result { + pub(super) fn from_buffer( + mut buffer: Vec, + hit_counter: Arc, + ) -> Result { let mut variants = vec![0]; if is_otf(&buffer) { variants = (0..ttf_parser::fonts_in_collection(&buffer).unwrap_or(1)).collect(); @@ -448,6 +454,8 @@ impl Font { path: None, buffer: ArcSwap::new(Arc::new(buffer)), variants, + hit_index: AtomicU32::default(), + hit_counter, }) } @@ -459,6 +467,8 @@ impl Font { if !self.buffer.load().is_empty() { return Ok(()); } + let hit_index = self.hit_counter.fetch_add(1, Ordering::SeqCst); + self.hit_index.store(hit_index, Ordering::SeqCst); #[cfg(feature = "parse")] if let Some(path) = self.path.as_ref() { let mut buffer = Vec::new(); @@ -525,13 +535,23 @@ impl Font { &self.variants } - pub(super) fn new(path: Option, variants: Vec) -> Self { + pub(super) fn new( + path: Option, + variants: Vec, + hit_counter: Arc, + ) -> Self { Font { path, variants, buffer: ArcSwap::default(), + hit_index: AtomicU32::default(), + hit_counter, } } + + pub(super) fn buffer_size(&self) -> usize { + self.buffer.load().len() + } } #[self_referencing] diff --git a/src/lib.rs b/src/lib.rs index 2e83725..6f2f1f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ use std::collections::HashSet; #[cfg(feature = "parse")] use std::path::Path; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; pub use ttf_parser::LineMetrics; #[cfg(all(target_arch = "wasm32", feature = "wit"))] @@ -30,6 +32,8 @@ pub struct FontKit { fonts: dashmap::DashMap, fallback_font_key: Option font::FontKey + Send + Sync>>, emoji_font_key: Option, + pub(crate) lru_limit: AtomicU32, + hit_counter: Arc, } impl FontKit { @@ -39,6 +43,8 @@ impl FontKit { fonts: dashmap::DashMap::new(), fallback_font_key: None, emoji_font_key: None, + lru_limit: AtomicU32::default(), + hit_counter: Arc::default(), } } @@ -105,17 +111,65 @@ impl FontKit { self.fonts.remove(&key); } + pub fn set_lru_limit(&self, limit: u32) { + self.lru_limit.store(limit, Ordering::SeqCst); + } + + pub fn buffer_size(&self) -> usize { + self.fonts + .iter() + .map(|font| font.buffer_size()) + .sum::() + } + + pub fn check_lru(&self) { + let limit = self.lru_limit.load(Ordering::SeqCst) as usize * 4 * 1024; + if limit == 0 { + return; + } + let mut current_size = self.buffer_size(); + let mut loaded_fonts = self.fonts.iter().filter(|f| f.buffer_size() > 0).count(); + while current_size > limit && loaded_fonts > 1 { + let font = self + .fonts + .iter() + .filter(|f| f.buffer_size() > 0) + .min_by(|a, b| { + a.hit_index + .load(Ordering::SeqCst) + .cmp(&b.hit_index.load(Ordering::SeqCst)) + }); + + let hit_index = font + .as_ref() + .map(|f| f.hit_index.load(Ordering::SeqCst)) + .unwrap_or(0); + if let Some(f) = font { + current_size -= f.buffer_size(); + f.unload(); + } + self.hit_counter.fetch_sub(hit_index, Ordering::SeqCst); + for f in self.fonts.iter() { + f.hit_index.fetch_sub(hit_index, Ordering::SeqCst); + } + loaded_fonts = self.fonts.iter().filter(|f| f.buffer_size() > 0).count(); + } + } + /// Add fonts from a buffer. This will load the fonts and store the buffer /// in FontKit. Type information is inferred from the magic number using /// `infer` crate. - /// - /// If the given buffer is a font collection (ttc), multiple keys will be - /// returned. #[cfg(feature = "parse")] pub fn add_font_from_buffer(&self, buffer: Vec) -> Result<(), Error> { - let font = Font::from_buffer(buffer)?; + let mut font = Font::from_buffer(buffer, self.hit_counter.clone())?; let key = font.first_key(); + if let Some(v) = self.fonts.get(&key) { + if let Some(path) = v.path().cloned() { + font.set_path(path); + } + } self.fonts.insert(key, font); + self.check_lru(); Ok(()) } @@ -123,8 +177,49 @@ impl FontKit { /// font buffer to reduce memory consumption #[cfg(feature = "parse")] pub fn search_fonts_from_path(&self, path: impl AsRef) -> Result<(), Error> { - if let Some(font) = load_font_from_path(&path) { - self.fonts.insert(font.first_key(), font); + use std::io::Read; + // if path.as_ref().is_dir() { + // let mut result = vec![]; + // if let Ok(data) = fs::read_dir(path) { + // for entry in data { + // if let Ok(entry) = entry { + // + // result.extend(load_font_from_path(&entry.path()).into_iter().flatten()); + // } + // } + // } + // return Some(result); + // } + + let mut buffer = Vec::new(); + let path = path.as_ref(); + let ext = path + .extension() + .and_then(|s| s.to_str()) + .map(|s| s.to_lowercase()); + let ext = ext.as_deref(); + let ext = match ext { + Some(e) => e, + None => return Ok(()), + }; + match ext { + "ttf" | "otf" | "ttc" | "woff2" | "woff" => { + let mut file = std::fs::File::open(&path).unwrap(); + buffer.clear(); + file.read_to_end(&mut buffer).unwrap(); + let mut font = match Font::from_buffer(buffer, self.hit_counter.clone()) { + Ok(f) => f, + Err(e) => { + log::warn!("Failed loading font {:?}: {:?}", path, e); + return Ok(()); + } + }; + font.set_path(path.to_path_buf()); + font.unload(); + self.fonts.insert(font.first_key(), font); + self.check_lru(); + } + _ => {} } Ok(()) } @@ -149,7 +244,9 @@ impl FontKit { } pub fn query(&self, key: &font::FontKey) -> Option { - self.fonts.get(&self.query_font(key)?)?.face(key).ok() + let result = self.fonts.get(&self.query_font(key)?)?.face(key).ok(); + self.check_lru(); + result } pub(crate) fn query_font(&self, key: &font::FontKey) -> Option { @@ -194,54 +291,6 @@ impl FontKit { } } -#[cfg(feature = "parse")] -fn load_font_from_path(path: impl AsRef) -> Option { - use std::io::Read; - - // if path.as_ref().is_dir() { - // let mut result = vec![]; - // if let Ok(data) = fs::read_dir(path) { - // for entry in data { - // if let Ok(entry) = entry { - // - // result.extend(load_font_from_path(&entry.path()).into_iter().flatten()); - // } - // } - // } - // return Some(result); - // } - - let mut buffer = Vec::new(); - let path = path.as_ref(); - let ext = path - .extension() - .and_then(|s| s.to_str()) - .map(|s| s.to_lowercase()); - let ext = ext.as_deref(); - let ext = match ext { - Some(e) => e, - None => return None, - }; - match ext { - "ttf" | "otf" | "ttc" | "woff2" | "woff" => { - let mut file = std::fs::File::open(&path).unwrap(); - buffer.clear(); - file.read_to_end(&mut buffer).unwrap(); - let mut font = match Font::from_buffer(buffer) { - Ok(f) => f, - Err(e) => { - log::warn!("Failed loading font {:?}: {:?}", path, e); - return None; - } - }; - font.set_path(path.to_path_buf()); - font.unload(); - Some(font) - } - _ => None, - } -} - enum Filter<'a> { Family(&'a str), Italic(bool), diff --git a/src/wit.rs b/src/wit.rs index b9bce3d..481bea6 100644 --- a/src/wit.rs +++ b/src/wit.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use std::sync::atomic::Ordering; use crate::bindings::exports::alibaba::fontkit::fontkit_interface as fi; use crate::font::FontKey; @@ -168,7 +169,7 @@ impl fi::GuestFontKit for FontKit { .get("variants") .and_then(|v| serde_json::from_value::>(v.clone()).ok()); if let (Some(path), Some(variants)) = (path, variants) { - let font = Font::new(path, variants); + let font = Font::new(path, variants, self.hit_counter.clone()); let key = font.first_key(); self.fonts.insert(key, font); } @@ -198,6 +199,10 @@ impl fi::GuestFontKit for FontKit { let font = self.fonts.get(&self.query_font(&key)?)?; Some(font_info(&*font)) } + + fn set_lru_limit(&self, limit: u32) { + self.lru_limit.store(limit, Ordering::SeqCst); + } } impl GuestTextMetrics for TextMetrics { diff --git a/tests/basic.rs b/tests/basic.rs index 6f643e0..2c910ff 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -104,3 +104,21 @@ pub fn test_complex_text_wrap() -> Result<(), Error> { assert_eq!(area.value_string(), "商家\n热卖\n123\n456\n78"); Ok(()) } + +#[test] +pub fn test_lru_cache() -> Result<(), Error> { + let fontkit = FontKit::new(); + fontkit.set_lru_limit(1); + fontkit.search_fonts_from_path("examples/AlimamaFangYuanTiVF.ttf")?; + assert_eq!(fontkit.buffer_size(), 0); + let key = fontkit.keys().pop().unwrap(); + assert!(fontkit.query(&key).is_some()); + assert_eq!(fontkit.buffer_size(), 7412388); + fontkit.search_fonts_from_path("examples/OpenSans-Italic.ttf")?; + let key2 = FontKey::new_with_family("Open Sans".to_string()); + assert!(fontkit.query(&key2).is_some()); + assert_eq!(fontkit.buffer_size(), 212896); + fontkit.query(&key); + assert_eq!(fontkit.buffer_size(), 7412388); + Ok(()) +} diff --git a/wit/world.wit b/wit/world.wit index ce0581e..1929ad5 100644 --- a/wit/world.wit +++ b/wit/world.wit @@ -82,6 +82,8 @@ interface fontkit-interface { /// Stores font buffer and provides font-querying APIs resource font-kit { constructor(); + /// add an LRU limit for font buffer registry, `limit`'s unit is KB, 0 means caching is disabled + set-lru-limit: func(limit: u32); /// Register a font (or several fonts in case of ttc), return the keys of added fonts. /// The file type is extracted from the buffer by checking magic numbers add-font-from-buffer: func(buffer: list) ;