From e378cef4f0a5a689510ee93ecb4e6f0d5dffac0e Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 21 Sep 2022 21:24:52 +0800 Subject: [PATCH 1/2] fix: resize canvas should clear the context --- src/lib.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e2089a7..9fcb7c68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,15 +72,18 @@ pub struct CanvasRenderingContext2DAttributes { #[napi] pub struct CanvasElement { - pub width: u32, - pub height: u32, + pub(crate) width: u32, + pub(crate) height: u32, pub(crate) ctx: ClassInstance, } #[napi] impl CanvasElement { - #[napi(constructor)] - pub fn new(mut env: Env, mut this: This, width: u32, height: u32) -> Result { + fn create_context( + mut env: Env, + width: u32, + height: u32, + ) -> Result> { let ctx = CanvasRenderingContext2D::into_instance( CanvasRenderingContext2D { context: Context::new(width, height, ColorSpace::default())?, @@ -96,12 +99,52 @@ impl CanvasElement { .with_property_attributes(PropertyAttributes::Writable | PropertyAttributes::Configurable), ])?; env.adjust_external_memory((width * height * 4) as i64)?; + Ok(ctx) + } + + #[napi(constructor)] + pub fn new(env: Env, mut this: This, width: u32, height: u32) -> Result { + let ctx = Self::create_context(env, width, height)?; this.define_properties(&[Property::new("ctx")? .with_value(&ctx) .with_property_attributes(PropertyAttributes::Default)])?; Ok(Self { width, height, ctx }) } + #[napi(setter)] + pub fn set_width(&mut self, mut env: Env, width: u32) -> Result<()> { + self.width = width; + let height = self.height; + let old_ctx = mem::replace( + &mut self.ctx.context, + Context::new(width, height, ColorSpace::default())?, + ); + env.adjust_external_memory((width as i64 - old_ctx.width as i64) * 4)?; + Ok(()) + } + + #[napi(getter)] + pub fn get_width(&self) -> u32 { + self.width + } + + #[napi(setter)] + pub fn set_height(&mut self, mut env: Env, height: u32) -> Result<()> { + self.height = height; + let width = self.width; + let old_ctx = mem::replace( + &mut self.ctx.context, + Context::new(width, height, ColorSpace::default())?, + ); + env.adjust_external_memory((height as i64 - old_ctx.height as i64) * 4)?; + Ok(()) + } + + #[napi(getter)] + pub fn get_height(&self) -> u32 { + self.height + } + #[napi] pub fn get_context( &mut self, From 3f339ecdac9db839cfe7021dbf9b45596423b63b Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 22 Sep 2022 16:48:25 +0800 Subject: [PATCH 2/2] fix: add Mutex guard to GlobalFont --- src/ctx.rs | 12 ++++++---- src/error.rs | 6 +++++ src/global_fonts.rs | 55 +++++++++++++++++++++++++++++---------------- src/svg.rs | 28 +++++++++++------------ 4 files changed, 63 insertions(+), 38 deletions(-) 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 {