diff --git a/src/ctx.rs b/src/ctx.rs index a162bf9f..c402987b 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -8,6 +8,7 @@ use cssparser::{Color as CSSColor, Parser, ParserInput, RGBA}; use libavif::AvifData; use napi::{bindgen_prelude::*, JsBuffer, JsString, NapiRaw, NapiValue}; +use crate::global_fonts::get_font; use crate::{ avif::Config, error::SkError, @@ -666,6 +667,7 @@ impl Context { let surface = &mut self.surface; surface.save(); Self::apply_shadow_offset_matrix(surface, state.shadow_offset_x, state.shadow_offset_y)?; + let font = get_font()?; surface.canvas.draw_text( text, x, @@ -675,7 +677,7 @@ impl Context { weight, stretch as i32, slant, - &*crate::global_fonts::GLOBAL_FONT_COLLECTION, + &*font, state.font_style.size, &state.font_style.family, state.text_baseline, @@ -683,9 +685,10 @@ impl Context { state.text_direction, &shadow_paint, )?; + mem::drop(font); surface.restore(); } - + let font = get_font()?; self.surface.canvas.draw_text( text, x, @@ -695,7 +698,7 @@ impl Context { weight, stretch as i32, slant, - &*crate::global_fonts::GLOBAL_FONT_COLLECTION, + &*font, state.font_style.size, &state.font_style.family, state.text_baseline, @@ -712,9 +715,10 @@ impl Context { let weight = state.font_style.weight; let stretch = state.font_style.stretch; let slant = state.font_style.style; + let font = get_font()?; let line_metrics = LineMetrics(self.surface.canvas.get_line_metrics( text, - &*crate::global_fonts::GLOBAL_FONT_COLLECTION, + &*font, state.font_style.size, weight, stretch as i32, diff --git a/src/error.rs b/src/error.rs index dac29b48..d41d2db7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -48,3 +48,9 @@ impl From for SkError { Self::NulError } } + +impl From> for SkError { + fn from(err: std::sync::PoisonError) -> Self { + Self::Generic(format!("PoisonError {}", err)) + } +} diff --git a/src/global_fonts.rs b/src/global_fonts.rs index fc8c7003..0f4b0abf 100644 --- a/src/global_fonts.rs +++ b/src/global_fonts.rs @@ -1,5 +1,6 @@ use std::fs::read_dir; use std::path; +use std::sync::{LockResult, Mutex, MutexGuard, PoisonError}; use once_cell::sync::{Lazy, OnceCell}; @@ -14,60 +15,75 @@ const FONT_PATH: &str = "/usr/share/fonts/"; #[cfg(target_os = "android")] const FONT_PATH: &str = "/system/fonts"; -static FONT_DIR: OnceCell = OnceCell::new(); +static FONT_DIR: OnceCell> = OnceCell::new(); -pub(crate) static GLOBAL_FONT_COLLECTION: Lazy = Lazy::new(FontCollection::new); +pub(crate) static GLOBAL_FONT_COLLECTION: Lazy> = + Lazy::new(|| Mutex::new(FontCollection::new())); + +#[inline] +pub(crate) fn get_font<'a>() -> LockResult> { + GLOBAL_FONT_COLLECTION.lock() +} + +#[inline] +fn into_napi_error(err: PoisonError>) -> napi::Error { + napi::Error::new(napi::Status::GenericFailure, format!("{err}")) +} #[napi] #[allow(non_snake_case)] pub mod GlobalFonts { use napi::bindgen_prelude::*; - use super::{FONT_DIR, FONT_PATH, GLOBAL_FONT_COLLECTION}; + use super::{get_font, into_napi_error, FONT_DIR, FONT_PATH}; #[napi] - pub fn register(font_data: Buffer, name_alias: Option) -> bool { + pub fn register(font_data: Buffer, name_alias: Option) -> Result { let maybe_name_alias = name_alias.and_then(|s| if s.is_empty() { None } else { Some(s) }); - GLOBAL_FONT_COLLECTION.register(font_data.as_ref(), maybe_name_alias) + let font = get_font().map_err(into_napi_error)?; + Ok(font.register(font_data.as_ref(), maybe_name_alias)) } #[napi] - pub fn register_from_path(font_path: String, name_alias: Option) -> bool { + pub fn register_from_path(font_path: String, name_alias: Option) -> Result { let maybe_name_alias = name_alias.and_then(|s| if s.is_empty() { None } else { Some(s) }); - GLOBAL_FONT_COLLECTION.register_from_path(font_path.as_str(), maybe_name_alias) + let font = get_font().map_err(into_napi_error)?; + Ok(font.register_from_path(font_path.as_str(), maybe_name_alias)) } #[napi] pub fn get_families() -> Result { - Ok(serde_json::to_string( - &GLOBAL_FONT_COLLECTION.get_families(), - )?) + let font = get_font().map_err(into_napi_error)?; + Ok(serde_json::to_string(&font.get_families())?) } #[napi] - pub fn load_system_fonts() -> u32 { - *FONT_DIR.get_or_init(move || super::load_fonts_from_dir(FONT_PATH)) + pub fn load_system_fonts() -> Result { + FONT_DIR + .get_or_init(move || super::load_fonts_from_dir(FONT_PATH)) + .clone() } #[napi] - pub fn load_fonts_from_dir(dir: String) -> u32 { + pub fn load_fonts_from_dir(dir: String) -> Result { super::load_fonts_from_dir(dir.as_str()) } #[napi] - pub fn set_alias(font_name: String, alias: String) { - GLOBAL_FONT_COLLECTION.set_alias(font_name.as_str(), alias.as_str()); + pub fn set_alias(font_name: String, alias: String) -> Result<()> { + let font = get_font().map_err(into_napi_error)?; + font.set_alias(font_name.as_str(), alias.as_str()); + Ok(()) } } -fn load_fonts_from_dir>(dir: P) -> u32 { +fn load_fonts_from_dir>(dir: P) -> napi::Result { let mut count = 0u32; - let font_collection = &*GLOBAL_FONT_COLLECTION; if let Ok(dir) = read_dir(dir) { for f in dir.flatten() { if let Ok(meta) = f.metadata() { if meta.is_dir() { - load_fonts_from_dir(f.path()); + load_fonts_from_dir(f.path())?; } else { let p = f.path(); let ext = p.extension().and_then(|s| s.to_str()); @@ -76,6 +92,7 @@ fn load_fonts_from_dir>(dir: P) -> u32 { Some("ttf") | Some("ttc") | Some("otf") | Some("pfb") | Some("woff2") | Some("woff") => { if let Some(p) = p.into_os_string().to_str() { + let font_collection = get_font().map_err(into_napi_error)?; if font_collection.register_from_path::(p, None) { count += 1; } @@ -87,5 +104,5 @@ fn load_fonts_from_dir>(dir: P) -> u32 { } } } - count + Ok(count) } diff --git a/src/svg.rs b/src/svg.rs index 4352507a..d3ac90d0 100644 --- a/src/svg.rs +++ b/src/svg.rs @@ -2,27 +2,25 @@ use std::mem; use napi::{bindgen_prelude::*, JsBuffer}; -use crate::sk::sk_svg_text_to_path; +use crate::{error::SkError, global_fonts::get_font, sk::sk_svg_text_to_path}; #[napi(js_name = "convertSVGTextToPath")] pub fn convert_svg_text_to_path( env: Env, input: Either3, ) -> Result { - sk_svg_text_to_path( - input.as_bytes()?, - &*crate::global_fonts::GLOBAL_FONT_COLLECTION, - ) - .ok_or_else(|| { - Error::new( - Status::InvalidArg, - "Convert svg text to path failed".to_owned(), - ) - }) - .and_then(|v| unsafe { - env.create_buffer_with_borrowed_data(v.0.ptr, v.0.size, v, |d, _| mem::drop(d)) - }) - .map(|b| b.into_raw()) + let font = get_font().map_err(SkError::from)?; + sk_svg_text_to_path(input.as_bytes()?, &*font) + .ok_or_else(|| { + Error::new( + Status::InvalidArg, + "Convert svg text to path failed".to_owned(), + ) + }) + .and_then(|v| unsafe { + env.create_buffer_with_borrowed_data(v.0.ptr, v.0.size, v, |d, _| mem::drop(d)) + }) + .map(|b| b.into_raw()) } trait AsBytes {