diff options
Diffstat (limited to '')
| -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 | 
8 files changed, 327 insertions, 411 deletions
| 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(),      )  } | 
