diff options
author | 2023-11-29 22:28:31 +0100 | |
---|---|---|
committer | 2023-11-29 22:28:31 +0100 | |
commit | e09b4e24dda51b8212d8ece52431dacaa3922a7b (patch) | |
tree | 7005e181528134ebdde5bbbe5909273db9f30174 /tiny_skia | |
parent | 83c7870c569a2976923ee6243a19813094d44673 (diff) | |
parent | 7f8b17604a31e00becc43130ec516c1a53552c88 (diff) | |
download | iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.tar.gz iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.tar.bz2 iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.zip |
Merge branch 'master' into feat/multi-window-support
Diffstat (limited to 'tiny_skia')
-rw-r--r-- | tiny_skia/Cargo.toml | 48 | ||||
-rw-r--r-- | tiny_skia/fonts/Iced-Icons.ttf | bin | 5108 -> 0 bytes | |||
-rw-r--r-- | tiny_skia/src/backend.rs | 153 | ||||
-rw-r--r-- | tiny_skia/src/geometry.rs | 30 | ||||
-rw-r--r-- | tiny_skia/src/lib.rs | 3 | ||||
-rw-r--r-- | tiny_skia/src/raster.rs | 16 | ||||
-rw-r--r-- | tiny_skia/src/settings.rs | 6 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 484 | ||||
-rw-r--r-- | tiny_skia/src/vector.rs | 28 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 18 |
10 files changed, 348 insertions, 438 deletions
diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index d9276ea5..df4c6143 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -1,7 +1,14 @@ [package] name = "iced_tiny_skia" -version = "0.1.0" -edition = "2021" +description = "A software renderer for iced on top of tiny-skia" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true [features] image = ["iced_graphics/image"] @@ -9,30 +16,17 @@ svg = ["resvg"] geometry = ["iced_graphics/geometry"] [dependencies] -raw-window-handle = "0.5" -softbuffer = "0.2" -tiny-skia = "0.10" -bytemuck = "1" -rustc-hash = "1.1" -kurbo = "0.9" -log = "0.4" +iced_graphics.workspace = true -[dependencies.iced_graphics] -version = "0.8" -path = "../graphics" +bytemuck.workspace = true +cosmic-text.workspace = true +kurbo.workspace = true +log.workspace = true +raw-window-handle.workspace = true +rustc-hash.workspace = true +softbuffer.workspace = true +tiny-skia.workspace = true +xxhash-rust.workspace = true -[dependencies.cosmic-text] -git = "https://github.com/hecrj/cosmic-text.git" -rev = "c3cd24dc972bb8fd55d016c81ac9fa637e0a4ada" - -[dependencies.twox-hash] -version = "1.6" -default-features = false - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] -version = "1.6.1" -features = ["std"] - -[dependencies.resvg] -version = "0.35" -optional = true +resvg.workspace = true +resvg.optional = true diff --git a/tiny_skia/fonts/Iced-Icons.ttf b/tiny_skia/fonts/Iced-Icons.ttf Binary files differdeleted file mode 100644 index e3273141..00000000 --- a/tiny_skia/fonts/Iced-Icons.ttf +++ /dev/null diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index a8add70b..f2905b00 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,16 +1,11 @@ -use crate::core::text; -use crate::core::Gradient; -use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::core::{Background, Color, Gradient, Rectangle, Vector}; use crate::graphics::backend; use crate::graphics::{Damage, Viewport}; use crate::primitive::{self, Primitive}; -use crate::Settings; use std::borrow::Cow; pub struct Backend { - default_font: Font, - default_text_size: f32, text_pipeline: crate::text::Pipeline, #[cfg(feature = "image")] @@ -21,10 +16,8 @@ pub struct Backend { } impl Backend { - pub fn new(settings: Settings) -> Self { + pub fn new() -> Self { Self { - default_font: settings.default_font, - default_text_size: settings.default_text_size, text_pipeline: crate::text::Pipeline::new(), #[cfg(feature = "image")] @@ -364,6 +357,57 @@ impl Backend { } } } + Primitive::Paragraph { + paragraph, + position, + color, + } => { + let physical_bounds = + (Rectangle::new(*position, paragraph.min_bounds) + + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_paragraph( + paragraph, + *position + translation, + *color, + scale_factor, + pixels, + clip_mask, + ); + } + Primitive::Editor { + editor, + position, + color, + } => { + let physical_bounds = + (Rectangle::new(*position, editor.bounds) + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + + self.text_pipeline.draw_editor( + editor, + *position + translation, + *color, + scale_factor, + pixels, + clip_mask, + ); + } Primitive::Text { content, bounds, @@ -385,7 +429,7 @@ impl Backend { let clip_mask = (!physical_bounds.is_within(&clip_bounds)) .then_some(clip_mask as &_); - self.text_pipeline.draw( + self.text_pipeline.draw_cached( content, *bounds + translation, *color, @@ -401,7 +445,11 @@ impl Backend { ); } #[cfg(feature = "image")] - Primitive::Image { handle, bounds } => { + Primitive::Image { + handle, + filter_method, + bounds, + } => { let physical_bounds = (*bounds + translation) * scale_factor; if !clip_bounds.intersects(&physical_bounds) { @@ -417,14 +465,19 @@ impl Backend { ) .post_scale(scale_factor, scale_factor); - self.raster_pipeline - .draw(handle, *bounds, pixels, transform, clip_mask); + self.raster_pipeline.draw( + handle, + *filter_method, + *bounds, + pixels, + transform, + clip_mask, + ); } #[cfg(not(feature = "image"))] Primitive::Image { .. } => { log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {:?}", - primitive + "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", ); } #[cfg(feature = "svg")] @@ -453,8 +506,7 @@ impl Backend { #[cfg(not(feature = "svg"))] Primitive::Svg { .. } => { log::warn!( - "Unsupported primitive in `iced_tiny_skia`: {:?}", - primitive + "Unsupported primitive in `iced_tiny_skia`: {primitive:?}", ); } Primitive::Custom(primitive::Custom::Fill { @@ -599,6 +651,12 @@ impl Backend { } } +impl Default for Backend { + fn default() -> Self { + Self::new() + } +} + fn into_color(color: Color) -> tiny_skia::Color { tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) .expect("Convert color from iced to tiny_skia") @@ -779,60 +837,6 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = Font::with_name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{f00c}'; - const ARROW_DOWN_ICON: char = '\u{e800}'; - - fn default_font(&self) -> Font { - self.default_font - } - - fn default_size(&self) -> f32 { - self.default_text_size - } - - fn measure( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - ) -> Size { - self.text_pipeline.measure( - contents, - size, - line_height, - font, - bounds, - shaping, - ) - } - - fn hit_test( - &self, - contents: &str, - size: f32, - line_height: text::LineHeight, - font: Font, - bounds: Size, - shaping: text::Shaping, - point: Point, - nearest_only: bool, - ) -> Option<text::Hit> { - self.text_pipeline.hit_test( - contents, - size, - line_height, - font, - bounds, - shaping, - point, - nearest_only, - ) - } - fn load_font(&mut self, font: Cow<'static, [u8]>) { self.text_pipeline.load_font(font); } @@ -840,7 +844,10 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> { + fn dimensions( + &self, + handle: &crate::core::image::Handle, + ) -> crate::core::Size<u32> { self.raster_pipeline.dimensions(handle) } } @@ -850,7 +857,7 @@ impl backend::Svg for Backend { fn viewport_dimensions( &self, handle: &crate::core::svg::Handle, - ) -> Size<u32> { + ) -> crate::core::Size<u32> { self.vector_pipeline.viewport_dimensions(handle) } } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 9bd47556..1d14aa03 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -39,7 +39,9 @@ impl Frame { } pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { - let Some(path) = convert_path(path) else { return }; + let Some(path) = convert_path(path) else { + return; + }; let fill = fill.into(); self.primitives @@ -57,7 +59,9 @@ impl Frame { size: Size, fill: impl Into<Fill>, ) { - let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { return }; + let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { + return; + }; let fill = fill.into(); self.primitives @@ -73,7 +77,9 @@ impl Frame { } pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { - let Some(path) = convert_path(path) else { return }; + let Some(path) = convert_path(path) else { + return; + }; let stroke = stroke.into(); let skia_stroke = into_stroke(&stroke); @@ -148,8 +154,16 @@ impl Frame { .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); } - pub fn scale(&mut self, scale: f32) { - self.transform = self.transform.pre_scale(scale, scale); + pub fn scale(&mut self, scale: impl Into<f32>) { + let scale = scale.into(); + + self.scale_nonuniform(Vector { x: scale, y: scale }); + } + + pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + let scale = scale.into(); + + self.transform = self.transform.pre_scale(scale.x, scale.y); } pub fn into_primitive(self) -> Primitive { @@ -166,9 +180,9 @@ fn convert_path(path: &Path) -> Option<tiny_skia::Path> { use iced_graphics::geometry::path::lyon_path; let mut builder = tiny_skia::PathBuilder::new(); - let mut last_point = Default::default(); + let mut last_point = lyon_path::math::Point::default(); - for event in path.raw().iter() { + for event in path.raw() { match event { lyon_path::Event::Begin { at } => { builder.move_to(at.x, at.y); @@ -289,7 +303,7 @@ pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { } } -pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { +pub fn into_stroke(stroke: &Stroke<'_>) -> tiny_skia::Stroke { tiny_skia::Stroke { width: stroke.width, line_cap: match stroke.line_cap { diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 15de6ce2..ec8012be 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -1,3 +1,6 @@ +#![forbid(rust_2018_idioms)] +#![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod window; mod backend; diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index dedb127c..5f17ae60 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -28,6 +28,7 @@ impl Pipeline { pub fn draw( &mut self, handle: &raster::Handle, + filter_method: raster::FilterMethod, bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, transform: tiny_skia::Transform, @@ -39,12 +40,21 @@ impl Pipeline { let transform = transform.pre_scale(width_scale, height_scale); + let quality = match filter_method { + raster::FilterMethod::Linear => { + tiny_skia::FilterQuality::Bilinear + } + raster::FilterMethod::Nearest => { + tiny_skia::FilterQuality::Nearest + } + }; + pixels.draw_pixmap( (bounds.x / width_scale) as i32, (bounds.y / height_scale) as i32, image, &tiny_skia::PixmapPaint { - quality: tiny_skia::FilterQuality::Bilinear, + quality, ..Default::default() }, transform, @@ -85,14 +95,14 @@ impl Cache { ); } - entry.insert(Some(Entry { + let _ = entry.insert(Some(Entry { width: image.width(), height: image.height(), pixels: buffer, })); } - self.hits.insert(id); + let _ = self.hits.insert(id); self.entries.get(&id).unwrap().as_ref().map(|entry| { tiny_skia::PixmapRef::from_bytes( bytemuck::cast_slice(&entry.pixels), diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index abffbfe6..ec27b218 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,4 +1,4 @@ -use crate::core::Font; +use crate::core::{Font, Pixels}; /// The settings of a [`Backend`]. /// @@ -11,14 +11,14 @@ pub struct Settings { /// The default size of text. /// /// By default, it will be set to `16.0`. - pub default_text_size: f32, + pub default_text_size: Pixels, } impl Default for Settings { fn default() -> Settings { Settings { default_font: Font::default(), - default_text_size: 16.0, + default_text_size: Pixels(16.0), } } } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 15f25740..70e95d01 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,18 +1,19 @@ use crate::core::alignment; -use crate::core::font::{self, Font}; -use crate::core::text::{Hit, LineHeight, Shaping}; -use crate::core::{Color, Pixels, Point, Rectangle, Size}; +use crate::core::text::{LineHeight, Shaping}; +use crate::core::{Color, Font, Pixels, Point, Rectangle}; +use crate::graphics::color; +use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::editor; +use crate::graphics::text::font_system; +use crate::graphics::text::paragraph; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; use std::collections::hash_map; -use std::hash::{BuildHasher, Hash, Hasher}; -use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { - font_system: RefCell<cosmic_text::FontSystem>, glyph_cache: GlyphCache, cache: RefCell<Cache>, } @@ -20,31 +21,88 @@ pub struct Pipeline { impl Pipeline { pub fn new() -> Self { Pipeline { - font_system: RefCell::new(cosmic_text::FontSystem::new_with_fonts( - [cosmic_text::fontdb::Source::Binary(Arc::new( - include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), - ))] - .into_iter(), - )), glyph_cache: GlyphCache::new(), cache: RefCell::new(Cache::new()), } } pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.font_system.get_mut().db_mut().load_font_source( - cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())), - ); + font_system() + .write() + .expect("Write font system") + .load_font(bytes); self.cache = RefCell::new(Cache::new()); } - pub fn draw( + pub fn draw_paragraph( + &mut self, + paragraph: ¶graph::Weak, + position: Point, + color: Color, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, + ) { + use crate::core::text::Paragraph as _; + + let Some(paragraph) = paragraph.upgrade() else { + return; + }; + + let mut font_system = font_system().write().expect("Write font system"); + + draw( + font_system.raw(), + &mut self.glyph_cache, + paragraph.buffer(), + Rectangle::new(position, paragraph.min_bounds()), + color, + paragraph.horizontal_alignment(), + paragraph.vertical_alignment(), + scale_factor, + pixels, + clip_mask, + ); + } + + pub fn draw_editor( + &mut self, + editor: &editor::Weak, + position: Point, + color: Color, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, + ) { + use crate::core::text::Editor as _; + + let Some(editor) = editor.upgrade() else { + return; + }; + + let mut font_system = font_system().write().expect("Write font system"); + + draw( + font_system.raw(), + &mut self.glyph_cache, + editor.buffer(), + Rectangle::new(position, editor.bounds()), + color, + alignment::Horizontal::Left, + alignment::Vertical::Top, + scale_factor, + pixels, + clip_mask, + ); + } + + pub fn draw_cached( &mut self, content: &str, bounds: Rectangle, color: Color, - size: f32, + size: Pixels, line_height: LineHeight, font: Font, horizontal_alignment: alignment::Horizontal, @@ -54,189 +112,122 @@ impl Pipeline { pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::Mask>, ) { - let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let line_height = f32::from(line_height.to_absolute(size)); - let font_system = self.font_system.get_mut(); - let key = Key { + let mut font_system = font_system().write().expect("Write font system"); + let font_system = font_system.raw(); + + let key = cache::Key { bounds: bounds.size(), content, font, - size, + size: size.into(), line_height, shaping, }; let (_, entry) = self.cache.get_mut().allocate(font_system, key); - let max_width = entry.bounds.width * scale_factor; - let total_height = entry.bounds.height * scale_factor; - - let bounds = bounds * scale_factor; - - let x = match horizontal_alignment { - alignment::Horizontal::Left => bounds.x, - alignment::Horizontal::Center => bounds.x - max_width / 2.0, - alignment::Horizontal::Right => bounds.x - max_width, - }; - - let y = match vertical_alignment { - alignment::Vertical::Top => bounds.y, - alignment::Vertical::Center => bounds.y - total_height / 2.0, - alignment::Vertical::Bottom => bounds.y - total_height, - }; - - let mut swash = cosmic_text::SwashCache::new(); - - for run in entry.buffer.layout_runs() { - for glyph in run.glyphs { - let physical_glyph = glyph.physical((x, y), scale_factor); - - if let Some((buffer, placement)) = self.glyph_cache.allocate( - physical_glyph.cache_key, - color, - font_system, - &mut swash, - ) { - let pixmap = tiny_skia::PixmapRef::from_bytes( - buffer, - placement.width, - placement.height, - ) - .expect("Create glyph pixel map"); - - pixels.draw_pixmap( - physical_glyph.x + placement.left, - physical_glyph.y - placement.top - + (run.line_y * scale_factor).round() as i32, - pixmap, - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::identity(), - clip_mask, - ); - } - } - } + let width = entry.min_bounds.width; + let height = entry.min_bounds.height; + + draw( + font_system, + &mut self.glyph_cache, + &entry.buffer, + Rectangle { + width, + height, + ..bounds + }, + color, + horizontal_alignment, + vertical_alignment, + scale_factor, + pixels, + clip_mask, + ); } pub fn trim_cache(&mut self) { self.cache.get_mut().trim(); self.glyph_cache.trim(); } - - pub fn measure( - &self, - content: &str, - size: f32, - line_height: LineHeight, - font: Font, - bounds: Size, - shaping: Shaping, - ) -> Size { - let mut measurement_cache = self.cache.borrow_mut(); - - let line_height = f32::from(line_height.to_absolute(Pixels(size))); - - let (_, entry) = measurement_cache.allocate( - &mut self.font_system.borrow_mut(), - Key { - content, - size, - line_height, - font, - bounds, - shaping, - }, - ); - - entry.bounds - } - - pub fn hit_test( - &self, - content: &str, - size: f32, - line_height: LineHeight, - font: Font, - bounds: Size, - shaping: Shaping, - point: Point, - _nearest_only: bool, - ) -> Option<Hit> { - let mut measurement_cache = self.cache.borrow_mut(); - - let line_height = f32::from(line_height.to_absolute(Pixels(size))); - - let (_, entry) = measurement_cache.allocate( - &mut self.font_system.borrow_mut(), - Key { - content, - size, - line_height, - font, - bounds, - shaping, - }, - ); - - let cursor = entry.buffer.hit(point.x, point.y)?; - - Some(Hit::CharOffset(cursor.index)) - } -} - -fn measure(buffer: &cosmic_text::Buffer) -> Size { - let (width, total_lines) = buffer - .layout_runs() - .fold((0.0, 0usize), |(width, total_lines), run| { - (run.line_w.max(width), total_lines + 1) - }); - - Size::new(width, total_lines as f32 * buffer.metrics().line_height) } -fn to_family(family: font::Family) -> cosmic_text::Family<'static> { - match family { - font::Family::Name(name) => cosmic_text::Family::Name(name), - font::Family::SansSerif => cosmic_text::Family::SansSerif, - font::Family::Serif => cosmic_text::Family::Serif, - font::Family::Cursive => cosmic_text::Family::Cursive, - font::Family::Fantasy => cosmic_text::Family::Fantasy, - font::Family::Monospace => cosmic_text::Family::Monospace, - } -} - -fn to_weight(weight: font::Weight) -> cosmic_text::Weight { - match weight { - font::Weight::Thin => cosmic_text::Weight::THIN, - font::Weight::ExtraLight => cosmic_text::Weight::EXTRA_LIGHT, - font::Weight::Light => cosmic_text::Weight::LIGHT, - font::Weight::Normal => cosmic_text::Weight::NORMAL, - font::Weight::Medium => cosmic_text::Weight::MEDIUM, - font::Weight::Semibold => cosmic_text::Weight::SEMIBOLD, - font::Weight::Bold => cosmic_text::Weight::BOLD, - font::Weight::ExtraBold => cosmic_text::Weight::EXTRA_BOLD, - font::Weight::Black => cosmic_text::Weight::BLACK, - } -} - -fn to_stretch(stretch: font::Stretch) -> cosmic_text::Stretch { - match stretch { - font::Stretch::UltraCondensed => cosmic_text::Stretch::UltraCondensed, - font::Stretch::ExtraCondensed => cosmic_text::Stretch::ExtraCondensed, - font::Stretch::Condensed => cosmic_text::Stretch::Condensed, - font::Stretch::SemiCondensed => cosmic_text::Stretch::SemiCondensed, - font::Stretch::Normal => cosmic_text::Stretch::Normal, - font::Stretch::SemiExpanded => cosmic_text::Stretch::SemiExpanded, - font::Stretch::Expanded => cosmic_text::Stretch::Expanded, - font::Stretch::ExtraExpanded => cosmic_text::Stretch::ExtraExpanded, - font::Stretch::UltraExpanded => cosmic_text::Stretch::UltraExpanded, +fn draw( + font_system: &mut cosmic_text::FontSystem, + glyph_cache: &mut GlyphCache, + buffer: &cosmic_text::Buffer, + bounds: Rectangle, + color: Color, + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, + scale_factor: f32, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::Mask>, +) { + let bounds = bounds * scale_factor; + + let x = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => bounds.x - bounds.width / 2.0, + alignment::Horizontal::Right => bounds.x - bounds.width, + }; + + let y = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => bounds.y - bounds.height / 2.0, + alignment::Vertical::Bottom => bounds.y - bounds.height, + }; + + let mut swash = cosmic_text::SwashCache::new(); + + for run in buffer.layout_runs() { + for glyph in run.glyphs { + let physical_glyph = glyph.physical((x, y), scale_factor); + + if let Some((buffer, placement)) = glyph_cache.allocate( + physical_glyph.cache_key, + glyph.color_opt.map(from_color).unwrap_or(color), + font_system, + &mut swash, + ) { + let pixmap = tiny_skia::PixmapRef::from_bytes( + buffer, + placement.width, + placement.height, + ) + .expect("Create glyph pixel map"); + + pixels.draw_pixmap( + physical_glyph.x + placement.left, + physical_glyph.y - placement.top + + (run.line_y * scale_factor).round() as i32, + pixmap, + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::identity(), + clip_mask, + ); + } + } } } -fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { - match shaping { - Shaping::Basic => cosmic_text::Shaping::Basic, - Shaping::Advanced => cosmic_text::Shaping::Advanced, +fn from_color(color: cosmic_text::Color) -> Color { + let [r, g, b, a] = color.as_rgba(); + + if color::GAMMA_CORRECTION { + // `cosmic_text::Color` is linear RGB in this case, so we + // need to convert back to sRGB + Color::from_linear_rgba( + r as f32 / 255.0, + g as f32 / 255.0, + b as f32 / 255.0, + a as f32 / 255.0, + ) + } else { + Color::from_rgba8(r, g, b, a as f32 / 255.0) } } @@ -327,10 +318,10 @@ impl GlyphCache { } } - entry.insert((buffer, image.placement)); + let _ = entry.insert((buffer, image.placement)); } - self.recently_used.insert(key); + let _ = self.recently_used.insert(key); self.entries.get(&key).map(|(buffer, placement)| { (bytemuck::cast_slice(buffer.as_slice()), *placement) @@ -350,134 +341,3 @@ impl GlyphCache { } } } - -struct Cache { - entries: FxHashMap<KeyHash, Entry>, - measurements: FxHashMap<KeyHash, KeyHash>, - recently_used: FxHashSet<KeyHash>, - hasher: HashBuilder, - trim_count: usize, -} - -struct Entry { - buffer: cosmic_text::Buffer, - bounds: Size, -} - -#[cfg(not(target_arch = "wasm32"))] -type HashBuilder = twox_hash::RandomXxHashBuilder64; - -#[cfg(target_arch = "wasm32")] -type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; - -impl Cache { - const TRIM_INTERVAL: usize = 300; - - fn new() -> Self { - Self { - entries: FxHashMap::default(), - measurements: FxHashMap::default(), - recently_used: FxHashSet::default(), - hasher: HashBuilder::default(), - trim_count: 0, - } - } - - fn allocate( - &mut self, - font_system: &mut cosmic_text::FontSystem, - key: Key<'_>, - ) -> (KeyHash, &mut Entry) { - let hash = key.hash(self.hasher.build_hasher()); - - if let Some(hash) = self.measurements.get(&hash) { - let _ = self.recently_used.insert(*hash); - - return (*hash, self.entries.get_mut(hash).unwrap()); - } - - if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { - let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2); - let mut buffer = cosmic_text::Buffer::new(font_system, metrics); - - buffer.set_size( - font_system, - key.bounds.width, - key.bounds.height.max(key.size * 1.2), - ); - buffer.set_text( - font_system, - key.content, - cosmic_text::Attrs::new() - .family(to_family(key.font.family)) - .weight(to_weight(key.font.weight)) - .stretch(to_stretch(key.font.stretch)), - to_shaping(key.shaping), - ); - - let bounds = measure(&buffer); - - let _ = entry.insert(Entry { buffer, bounds }); - - for bounds in [ - bounds, - Size { - width: key.bounds.width, - ..bounds - }, - ] { - if key.bounds != bounds { - let _ = self.measurements.insert( - Key { bounds, ..key }.hash(self.hasher.build_hasher()), - hash, - ); - } - } - } - - let _ = self.recently_used.insert(hash); - - (hash, self.entries.get_mut(&hash).unwrap()) - } - - fn trim(&mut self) { - if self.trim_count > Self::TRIM_INTERVAL { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - self.measurements - .retain(|_, value| self.recently_used.contains(value)); - - self.recently_used.clear(); - - self.trim_count = 0; - } else { - self.trim_count += 1; - } - } -} - -#[derive(Debug, Clone, Copy)] -struct Key<'a> { - content: &'a str, - size: f32, - line_height: f32, - font: Font, - bounds: Size, - shaping: Shaping, -} - -impl Key<'_> { - fn hash<H: Hasher>(self, mut hasher: H) -> KeyHash { - self.content.hash(&mut hasher); - self.size.to_bits().hash(&mut hasher); - self.line_height.to_bits().hash(&mut hasher); - self.font.hash(&mut hasher); - self.bounds.width.to_bits().hash(&mut hasher); - self.bounds.height.to_bits().hash(&mut hasher); - self.shaping.hash(&mut hasher); - - hasher.finish() - } -} - -type KeyHash = u64; diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 433ca0f5..9c2893a2 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -1,7 +1,8 @@ use crate::core::svg::{Data, Handle}; use crate::core::{Color, Rectangle, Size}; +use crate::graphics::text; -use resvg::usvg; +use resvg::usvg::{self, TreeTextToPath}; use rustc_hash::{FxHashMap, FxHashSet}; use std::cell::RefCell; @@ -77,7 +78,7 @@ impl Cache { let id = handle.id(); if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { - let svg = match handle.data() { + let mut svg = match handle.data() { Data::Path(path) => { fs::read_to_string(path).ok().and_then(|contents| { usvg::Tree::from_str( @@ -92,10 +93,19 @@ impl Cache { } }; - entry.insert(svg); + if let Some(svg) = &mut svg { + if svg.has_text_nodes() { + let mut font_system = + text::font_system().write().expect("Read font system"); + + svg.convert_text(font_system.raw().db_mut()); + } + } + + let _ = entry.insert(svg); } - self.tree_hits.insert(id); + let _ = self.tree_hits.insert(id); self.trees.get(&id).unwrap().as_ref() } @@ -172,16 +182,16 @@ impl Cache { for pixel in bytemuck::cast_slice_mut::<u8, u32>(image.data_mut()) { - *pixel = *pixel & 0xFF00FF00 - | ((0x000000FF & *pixel) << 16) - | ((0x00FF0000 & *pixel) >> 16); + *pixel = *pixel & 0xFF00_FF00 + | ((0x0000_00FF & *pixel) << 16) + | ((0x00FF_0000 & *pixel) >> 16); } } - self.rasters.insert(key, image); + let _ = self.rasters.insert(key, image); } - self.raster_hits.insert(key); + let _ = self.raster_hits.insert(key); self.rasters.get(&key).map(tiny_skia::Pixmap::as_ref) } diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 1aaba2c9..32095e23 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -31,11 +31,22 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { ) -> Result<(Self, Self::Renderer), Error> { let (compositor, backend) = new(settings); - Ok((compositor, Renderer::new(backend))) + Ok(( + compositor, + Renderer::new( + backend, + settings.default_font, + settings.default_text_size, + ), + )) } fn renderer(&self) -> Self::Renderer { - Renderer::new(Backend::new(self.settings)) + Renderer::new( + Backend::new(), + self.settings.default_font, + self.settings.default_text_size, + ) } fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( @@ -44,6 +55,7 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { width: u32, height: u32, ) -> Surface { + #[allow(unsafe_code)] let window = unsafe { softbuffer::GraphicsContext::new(window, window) } .expect("Create softbuffer for window"); @@ -124,7 +136,7 @@ pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { settings, _theme: PhantomData, }, - Backend::new(settings), + Backend::new(), ) } |