diff options
author | 2023-08-30 04:31:21 +0200 | |
---|---|---|
committer | 2023-08-30 04:31:21 +0200 | |
commit | ed3454301e663a7cb7d73cd56b57b188f4d14a2f (patch) | |
tree | 8118d1305c2eba3a1b45d04634cd0e8d050fc0fa /tiny_skia | |
parent | c9bd48704dd9679c033dd0b8588e2744a3df44a0 (diff) | |
download | iced-ed3454301e663a7cb7d73cd56b57b188f4d14a2f.tar.gz iced-ed3454301e663a7cb7d73cd56b57b188f4d14a2f.tar.bz2 iced-ed3454301e663a7cb7d73cd56b57b188f4d14a2f.zip |
Implement explicit text caching in the widget state tree
Diffstat (limited to 'tiny_skia')
-rw-r--r-- | tiny_skia/fonts/Iced-Icons.ttf | bin | 5108 -> 0 bytes | |||
-rw-r--r-- | tiny_skia/src/backend.rs | 105 | ||||
-rw-r--r-- | tiny_skia/src/settings.rs | 6 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 305 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 15 |
5 files changed, 86 insertions, 345 deletions
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..ef587bac 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,16 +1,12 @@ -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::text; 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 +17,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 +358,32 @@ 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::Text { content, bounds, @@ -599,6 +619,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,58 +805,8 @@ 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 font_system(&self) -> &text::FontSystem { + self.text_pipeline.font_system() } fn load_font(&mut self, font: Cow<'static, [u8]>) { @@ -840,7 +816,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 +829,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/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 08fde4bf..e4c5ad9b 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::text::cache::{self, Cache}; +use crate::graphics::text::paragraph; +use crate::graphics::text::FontSystem; 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>, + font_system: FontSystem, glyph_cache: GlyphCache, cache: RefCell<Cache>, } @@ -20,17 +21,16 @@ 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(), - )), + font_system: FontSystem::new(), glyph_cache: GlyphCache::new(), cache: RefCell::new(Cache::new()), } } + pub fn font_system(&self) -> &FontSystem { + &self.font_system + } + 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())), @@ -39,12 +39,23 @@ impl Pipeline { self.cache = RefCell::new(Cache::new()); } + 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>, + ) { + } + pub fn draw( &mut self, content: &str, bounds: Rectangle, color: Color, - size: f32, + size: Pixels, line_height: LineHeight, font: Font, horizontal_alignment: alignment::Horizontal, @@ -54,22 +65,22 @@ 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 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 (_, buffer) = 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 max_width = bounds.width * scale_factor; + let total_height = bounds.height * scale_factor; let bounds = bounds * scale_factor; @@ -87,7 +98,7 @@ impl Pipeline { let mut swash = cosmic_text::SwashCache::new(); - for run in entry.buffer.layout_runs() { + for run in buffer.layout_runs() { for glyph in run.glyphs { let physical_glyph = glyph.physical((x, y), scale_factor); @@ -122,130 +133,6 @@ impl Pipeline { 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 to_style(style: font::Style) -> cosmic_text::Style { - match style { - font::Style::Normal => cosmic_text::Style::Normal, - font::Style::Italic => cosmic_text::Style::Italic, - font::Style::Oblique => cosmic_text::Style::Oblique, - } -} - -fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { - match shaping { - Shaping::Basic => cosmic_text::Shaping::Basic, - Shaping::Advanced => cosmic_text::Shaping::Advanced, - } } #[derive(Debug, Clone, Default)] @@ -358,135 +245,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)) - .style(to_style(key.font.style)), - 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/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 775cf9e5..a996fffc 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -28,9 +28,16 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { settings: Self::Settings, _compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let (compositor, backend) = new(settings); + let (compositor, backend) = new(); - Ok((compositor, Renderer::new(backend))) + Ok(( + compositor, + Renderer::new( + backend, + settings.default_font, + settings.default_text_size, + ), + )) } fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( @@ -113,12 +120,12 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { } } -pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { +pub fn new<Theme>() -> (Compositor<Theme>, Backend) { ( Compositor { _theme: PhantomData, }, - Backend::new(settings), + Backend::new(), ) } |