diff options
Diffstat (limited to '')
| -rw-r--r-- | graphics/Cargo.toml | 11 | ||||
| -rw-r--r-- | graphics/fonts/Iced-Icons.ttf (renamed from tiny_skia/fonts/Iced-Icons.ttf) | bin | 5108 -> 5108 bytes | |||
| -rw-r--r-- | graphics/src/backend.rs | 69 | ||||
| -rw-r--r-- | graphics/src/damage.rs | 26 | ||||
| -rw-r--r-- | graphics/src/geometry/text.rs | 6 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 3 | ||||
| -rw-r--r-- | graphics/src/primitive.rs | 14 | ||||
| -rw-r--r-- | graphics/src/renderer.rs | 125 | ||||
| -rw-r--r-- | graphics/src/text.rs | 113 | ||||
| -rw-r--r-- | graphics/src/text/cache.rs | 120 | ||||
| -rw-r--r-- | graphics/src/text/paragraph.rs | 246 | 
11 files changed, 596 insertions, 137 deletions
| diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index ca7bf61a..442eb007 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -23,6 +23,9 @@ log = "0.4"  raw-window-handle = "0.5"  thiserror = "1.0"  bitflags = "1.2" +cosmic-text = "0.9" +rustc-hash = "1.1" +unicode-segmentation = "1.6"  [dependencies.bytemuck]  version = "1.4" @@ -32,6 +35,14 @@ features = ["derive"]  version = "0.10"  path = "../core" +[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.image]  version = "0.24"  optional = true diff --git a/tiny_skia/fonts/Iced-Icons.ttf b/graphics/fonts/Iced-Icons.ttfBinary files differ index e3273141..e3273141 100644 --- a/tiny_skia/fonts/Iced-Icons.ttf +++ b/graphics/fonts/Iced-Icons.ttf diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 59e95bf8..6774b9ca 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -1,8 +1,8 @@  //! Write a graphics backend. -use iced_core::image; -use iced_core::svg; -use iced_core::text; -use iced_core::{Font, Point, Size}; +use crate::core::image; +use crate::core::svg; +use crate::core::Size; +use crate::text;  use std::borrow::Cow; @@ -12,70 +12,15 @@ use std::borrow::Cow;  pub trait Backend {      /// The custom kind of primitives this [`Backend`] supports.      type Primitive; - -    /// Trims the measurements cache. -    /// -    /// This method is currently necessary to properly trim the text cache in -    /// `iced_wgpu` and `iced_glow` because of limitations in the text rendering -    /// pipeline. It will be removed in the future. -    fn trim_measurements(&mut self) {}  }  /// A graphics backend that supports text rendering.  pub trait Text { -    /// The icon font of the backend. -    const ICON_FONT: Font; - -    /// The `char` representing a ✔ icon in the [`ICON_FONT`]. -    /// -    /// [`ICON_FONT`]: Self::ICON_FONT -    const CHECKMARK_ICON: char; - -    /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`]. -    /// -    /// [`ICON_FONT`]: Self::ICON_FONT -    const ARROW_DOWN_ICON: char; - -    /// Returns the default [`Font`]. -    fn default_font(&self) -> Font; - -    /// Returns the default size of text. -    fn default_size(&self) -> f32; - -    /// Measures the text contents with the given size and font, -    /// returning the size of a laid out paragraph that fits in the provided -    /// bounds. -    fn measure( -        &self, -        contents: &str, -        size: f32, -        line_height: text::LineHeight, -        font: Font, -        bounds: Size, -        shaping: text::Shaping, -    ) -> Size; - -    /// Tests whether the provided point is within the boundaries of [`Text`] -    /// laid out with the given parameters, returning information about -    /// the nearest character. -    /// -    /// If nearest_only is true, the hit test does not consider whether the -    /// the point is interior to any glyph bounds, returning only the character -    /// with the nearest centeroid. -    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>; -      /// Loads a [`Font`] from its bytes.      fn load_font(&mut self, font: Cow<'static, [u8]>); + +    /// Returns the [`cosmic_text::FontSystem`] of the [`Backend`]. +    fn font_system(&self) -> &text::FontSystem;  }  /// A graphics backend that supports image rendering. diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 2f29956e..3276c2d4 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -40,6 +40,32 @@ impl<T: Damage> Damage for Primitive<T> {                  bounds.expand(1.5)              } +            Self::Paragraph { +                paragraph, +                position, +                .. +            } => { +                let mut bounds = +                    Rectangle::new(*position, paragraph.min_bounds); + +                bounds.x = match paragraph.horizontal_alignment { +                    alignment::Horizontal::Left => bounds.x, +                    alignment::Horizontal::Center => { +                        bounds.x - bounds.width / 2.0 +                    } +                    alignment::Horizontal::Right => bounds.x - bounds.width, +                }; + +                bounds.y = match paragraph.vertical_alignment { +                    alignment::Vertical::Top => bounds.y, +                    alignment::Vertical::Center => { +                        bounds.y - bounds.height / 2.0 +                    } +                    alignment::Vertical::Bottom => bounds.y - bounds.height, +                }; + +                bounds.expand(1.5) +            }              Self::Quad { bounds, .. }              | Self::Image { bounds, .. }              | Self::Svg { bounds, .. } => bounds.expand(1.0), diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs index c584f3cd..0bf7ec97 100644 --- a/graphics/src/geometry/text.rs +++ b/graphics/src/geometry/text.rs @@ -1,6 +1,6 @@  use crate::core::alignment;  use crate::core::text::{LineHeight, Shaping}; -use crate::core::{Color, Font, Point}; +use crate::core::{Color, Font, Pixels, Point};  /// A bunch of text that can be drawn to a canvas  #[derive(Debug, Clone)] @@ -19,7 +19,7 @@ pub struct Text {      /// The color of the text      pub color: Color,      /// The size of the text -    pub size: f32, +    pub size: Pixels,      /// The line height of the text.      pub line_height: LineHeight,      /// The font of the text @@ -38,7 +38,7 @@ impl Default for Text {              content: String::new(),              position: Point::ORIGIN,              color: Color::BLACK, -            size: 16.0, +            size: Pixels(16.0),              line_height: LineHeight::Relative(1.2),              font: Font::default(),              horizontal_alignment: alignment::Horizontal::Left, diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index af374a2f..902eb5b0 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,7 +9,7 @@  )]  #![deny(      missing_debug_implementations, -    missing_docs, +    //missing_docs,      unsafe_code,      unused_results,      clippy::extra_unused_lifetimes, @@ -34,6 +34,7 @@ pub mod damage;  pub mod gradient;  pub mod mesh;  pub mod renderer; +pub mod text;  #[cfg(feature = "geometry")]  pub mod geometry; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 7592a410..cdc8923e 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -3,7 +3,8 @@ use crate::core::alignment;  use crate::core::image;  use crate::core::svg;  use crate::core::text; -use crate::core::{Background, Color, Font, Rectangle, Vector}; +use crate::core::{Background, Color, Font, Pixels, Point, Rectangle, Vector}; +use crate::text::paragraph;  use std::sync::Arc; @@ -19,7 +20,7 @@ pub enum Primitive<T> {          /// The color of the text          color: Color,          /// The size of the text in logical pixels -        size: f32, +        size: Pixels,          /// The line height of the text          line_height: text::LineHeight,          /// The font of the text @@ -31,6 +32,15 @@ pub enum Primitive<T> {          /// The shaping strategy of the text.          shaping: text::Shaping,      }, +    /// A paragraph primitive +    Paragraph { +        /// The [`Paragraph`]. +        paragraph: paragraph::Weak, +        /// The position of the [`Paragraph`]. +        position: Point, +        /// The color of the [`Paragraph`]. +        color: Color, +    },      /// A quad primitive      Quad {          /// The bounds of the quad diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index c0cec60a..f93f4a6d 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,15 +1,15 @@  //! Create a renderer from a [`Backend`].  use crate::backend::{self, Backend}; -use crate::Primitive; - -use iced_core::image; -use iced_core::layout; -use iced_core::renderer; -use iced_core::svg; -use iced_core::text::{self, Text}; -use iced_core::{ -    Background, Color, Element, Font, Point, Rectangle, Size, Vector, +use crate::core; +use crate::core::image; +use crate::core::renderer; +use crate::core::svg; +use crate::core::text::Text; +use crate::core::{ +    Background, Color, Font, Pixels, Point, Rectangle, Size, Vector,  }; +use crate::text; +use crate::Primitive;  use std::borrow::Cow;  use std::marker::PhantomData; @@ -18,15 +18,23 @@ use std::marker::PhantomData;  #[derive(Debug)]  pub struct Renderer<B: Backend, Theme> {      backend: B, +    default_font: Font, +    default_text_size: Pixels,      primitives: Vec<Primitive<B::Primitive>>,      theme: PhantomData<Theme>,  }  impl<B: Backend, T> Renderer<B, T> {      /// Creates a new [`Renderer`] from the given [`Backend`]. -    pub fn new(backend: B) -> Self { +    pub fn new( +        backend: B, +        default_font: Font, +        default_text_size: Pixels, +    ) -> Self {          Self {              backend, +            default_font, +            default_text_size,              primitives: Vec::new(),              theme: PhantomData,          } @@ -88,16 +96,6 @@ impl<B: Backend, T> Renderer<B, T> {  impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> {      type Theme = T; -    fn layout<Message>( -        &mut self, -        element: &Element<'_, Message, Self>, -        limits: &layout::Limits, -    ) -> layout::Node { -        self.backend.trim_measurements(); - -        element.as_widget().layout(self, limits) -    } -      fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {          let current = self.start_layer(); @@ -137,77 +135,66 @@ impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> {      }  } -impl<B, T> text::Renderer for Renderer<B, T> +impl<B, T> core::text::Renderer for Renderer<B, T>  where      B: Backend + backend::Text,  {      type Font = Font; +    type Paragraph = text::Paragraph; -    const ICON_FONT: Font = B::ICON_FONT; -    const CHECKMARK_ICON: char = B::CHECKMARK_ICON; -    const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON; +    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) -> Self::Font { -        self.backend().default_font() +        self.default_font      } -    fn default_size(&self) -> f32 { -        self.backend().default_size() +    fn default_size(&self) -> Pixels { +        self.default_text_size      } -    fn measure( -        &self, -        content: &str, -        size: f32, -        line_height: text::LineHeight, -        font: Font, -        bounds: Size, -        shaping: text::Shaping, -    ) -> Size { -        self.backend().measure( -            content, -            size, -            line_height, -            font, -            bounds, -            shaping, -        ) +    fn load_font(&mut self, bytes: Cow<'static, [u8]>) { +        self.backend.load_font(bytes); +    } + +    fn create_paragraph(&self, text: Text<'_, Self::Font>) -> text::Paragraph { +        text::Paragraph::with_text(text, self.backend.font_system())      } -    fn hit_test( +    fn resize_paragraph(          &self, -        content: &str, -        size: f32, -        line_height: text::LineHeight, -        font: Font, -        bounds: Size, -        shaping: text::Shaping, -        point: Point, -        nearest_only: bool, -    ) -> Option<text::Hit> { -        self.backend().hit_test( -            content, -            size, -            line_height, -            font, -            bounds, -            shaping, -            point, -            nearest_only, -        ) +        paragraph: &mut Self::Paragraph, +        new_bounds: Size, +    ) { +        paragraph.resize(new_bounds, self.backend.font_system());      } -    fn load_font(&mut self, bytes: Cow<'static, [u8]>) { -        self.backend.load_font(bytes); +    fn fill_paragraph( +        &mut self, +        paragraph: &Self::Paragraph, +        position: Point, +        color: Color, +    ) { +        self.primitives.push(Primitive::Paragraph { +            paragraph: paragraph.downgrade(), +            position, +            color, +        });      } -    fn fill_text(&mut self, text: Text<'_, Self::Font>) { +    fn fill_text( +        &mut self, +        text: Text<'_, Self::Font>, +        position: Point, +        color: Color, +    ) {          self.primitives.push(Primitive::Text {              content: text.content.to_string(), -            bounds: text.bounds, +            bounds: Rectangle::new(position, text.bounds),              size: text.size,              line_height: text.line_height, -            color: text.color, +            color,              font: text.font,              horizontal_alignment: text.horizontal_alignment,              vertical_alignment: text.vertical_alignment, diff --git a/graphics/src/text.rs b/graphics/src/text.rs new file mode 100644 index 00000000..bbe9d7cb --- /dev/null +++ b/graphics/src/text.rs @@ -0,0 +1,113 @@ +pub mod cache; +pub mod paragraph; + +pub use cache::Cache; +pub use paragraph::Paragraph; + +pub use cosmic_text; + +use crate::core::font::{self, Font}; +use crate::core::text::Shaping; +use crate::core::Size; + +use std::sync::{self, Arc, RwLock}; + +#[allow(missing_debug_implementations)] +pub struct FontSystem(RwLock<cosmic_text::FontSystem>); + +impl FontSystem { +    pub fn new() -> Self { +        FontSystem(RwLock::new(cosmic_text::FontSystem::new_with_fonts( +            [cosmic_text::fontdb::Source::Binary(Arc::new( +                include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), +            ))] +            .into_iter(), +        ))) +    } + +    pub fn get_mut(&mut self) -> &mut cosmic_text::FontSystem { +        self.0.get_mut().expect("Lock font system") +    } + +    pub fn write(&self) -> sync::RwLockWriteGuard<'_, cosmic_text::FontSystem> { +        self.0.write().expect("Write font system") +    } +} + +impl Default for FontSystem { +    fn default() -> Self { +        Self::new() +    } +} + +pub 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) +} + +pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> { +    cosmic_text::Attrs::new() +        .family(to_family(font.family)) +        .weight(to_weight(font.weight)) +        .stretch(to_stretch(font.stretch)) +        .style(to_style(font.style)) +} + +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, +    } +} + +pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping { +    match shaping { +        Shaping::Basic => cosmic_text::Shaping::Basic, +        Shaping::Advanced => cosmic_text::Shaping::Advanced, +    } +} diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs new file mode 100644 index 00000000..8aea6715 --- /dev/null +++ b/graphics/src/text/cache.rs @@ -0,0 +1,120 @@ +use crate::core::{Font, Size}; +use crate::text; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::hash_map; +use std::hash::{BuildHasher, Hash, Hasher}; + +#[allow(missing_debug_implementations)] +#[derive(Default)] +pub struct Cache { +    entries: FxHashMap<KeyHash, cosmic_text::Buffer>, +    aliases: FxHashMap<KeyHash, KeyHash>, +    recently_used: FxHashSet<KeyHash>, +    hasher: HashBuilder, +} + +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; + +impl Cache { +    pub fn new() -> Self { +        Self::default() +    } + +    pub fn get(&self, key: &KeyHash) -> Option<&cosmic_text::Buffer> { +        self.entries.get(key) +    } + +    pub fn allocate( +        &mut self, +        font_system: &mut cosmic_text::FontSystem, +        key: Key<'_>, +    ) -> (KeyHash, &mut cosmic_text::Buffer) { +        let hash = key.hash(self.hasher.build_hasher()); + +        if let Some(hash) = self.aliases.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.line_height); +            let mut buffer = cosmic_text::Buffer::new(font_system, metrics); + +            buffer.set_size( +                font_system, +                key.bounds.width, +                key.bounds.height.max(key.line_height), +            ); +            buffer.set_text( +                font_system, +                key.content, +                text::to_attributes(key.font), +                text::to_shaping(key.shaping), +            ); + +            let bounds = text::measure(&buffer); +            let _ = entry.insert(buffer); + +            for bounds in [ +                bounds, +                Size { +                    width: key.bounds.width, +                    ..bounds +                }, +            ] { +                if key.bounds != bounds { +                    let _ = self.aliases.insert( +                        Key { bounds, ..key }.hash(self.hasher.build_hasher()), +                        hash, +                    ); +                } +            } +        } + +        let _ = self.recently_used.insert(hash); + +        (hash, self.entries.get_mut(&hash).unwrap()) +    } + +    pub fn trim(&mut self) { +        self.entries +            .retain(|key, _| self.recently_used.contains(key)); + +        self.aliases +            .retain(|_, value| self.recently_used.contains(value)); + +        self.recently_used.clear(); +    } +} + +#[derive(Debug, Clone, Copy)] +pub struct Key<'a> { +    pub content: &'a str, +    pub size: f32, +    pub line_height: f32, +    pub font: Font, +    pub bounds: Size, +    pub shaping: text::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() +    } +} + +pub type KeyHash = u64; diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs new file mode 100644 index 00000000..7b70376a --- /dev/null +++ b/graphics/src/text/paragraph.rs @@ -0,0 +1,246 @@ +use crate::core; +use crate::core::alignment; +use crate::core::text::{Hit, LineHeight, Shaping, Text}; +use crate::core::{Font, Pixels, Point, Size}; +use crate::text::{self, FontSystem}; + +use std::fmt; +use std::sync::{self, Arc}; + +#[derive(Clone, PartialEq, Default)] +pub struct Paragraph(Arc<Internal>); + +struct Internal { +    buffer: cosmic_text::Buffer, +    content: String, // TODO: Reuse from `buffer` (?) +    font: Font, +    shaping: Shaping, +    horizontal_alignment: alignment::Horizontal, +    vertical_alignment: alignment::Vertical, +    bounds: Size, +    min_bounds: Size, +} + +impl Paragraph { +    pub fn new() -> Self { +        Self::default() +    } + +    pub fn with_text(text: Text<'_, Font>, font_system: &FontSystem) -> Self { +        let mut font_system = font_system.write(); + +        let mut buffer = cosmic_text::Buffer::new( +            &mut font_system, +            cosmic_text::Metrics::new( +                text.size.into(), +                text.line_height.to_absolute(text.size).into(), +            ), +        ); + +        buffer.set_size( +            &mut font_system, +            text.bounds.width, +            text.bounds.height, +        ); + +        buffer.set_text( +            &mut font_system, +            text.content, +            text::to_attributes(text.font), +            text::to_shaping(text.shaping), +        ); + +        let min_bounds = text::measure(&buffer); + +        Self(Arc::new(Internal { +            buffer, +            content: text.content.to_owned(), +            font: text.font, +            horizontal_alignment: text.horizontal_alignment, +            vertical_alignment: text.vertical_alignment, +            shaping: text.shaping, +            bounds: text.bounds, +            min_bounds, +        })) +    } + +    pub fn buffer(&self) -> &cosmic_text::Buffer { +        &self.0.buffer +    } + +    pub fn downgrade(&self) -> Weak { +        Weak { +            raw: Arc::downgrade(&self.0), +            min_bounds: self.0.min_bounds, +            horizontal_alignment: self.0.horizontal_alignment, +            vertical_alignment: self.0.vertical_alignment, +        } +    } + +    pub fn resize(&mut self, new_bounds: Size, font_system: &FontSystem) { +        if let Some(internal) = Arc::get_mut(&mut self.0) { +            // If there is no strong reference holding on to the paragraph, we +            // resize the buffer in-place +            internal.buffer.set_size( +                &mut font_system.write(), +                new_bounds.width, +                new_bounds.height, +            ); + +            internal.bounds = new_bounds; +            internal.min_bounds = text::measure(&internal.buffer); +        } else { +            let metrics = self.0.buffer.metrics(); + +            // If there is a strong reference somewhere, we recompute the buffer +            // from scratch +            *self = Self::with_text( +                Text { +                    content: &self.0.content, +                    bounds: self.0.bounds, +                    size: Pixels(metrics.font_size), +                    line_height: LineHeight::Absolute(Pixels( +                        metrics.line_height, +                    )), +                    font: self.0.font, +                    horizontal_alignment: self.0.horizontal_alignment, +                    vertical_alignment: self.0.vertical_alignment, +                    shaping: self.0.shaping, +                }, +                font_system, +            ); +        } +    } +} + +impl core::text::Paragraph for Paragraph { +    type Font = Font; + +    fn content(&self) -> &str { +        &self.0.content +    } + +    fn text_size(&self) -> Pixels { +        Pixels(self.0.buffer.metrics().font_size) +    } + +    fn line_height(&self) -> LineHeight { +        LineHeight::Absolute(Pixels(self.0.buffer.metrics().line_height)) +    } + +    fn font(&self) -> Font { +        self.0.font +    } + +    fn shaping(&self) -> Shaping { +        self.0.shaping +    } + +    fn horizontal_alignment(&self) -> alignment::Horizontal { +        self.0.horizontal_alignment +    } + +    fn vertical_alignment(&self) -> alignment::Vertical { +        self.0.vertical_alignment +    } + +    fn bounds(&self) -> Size { +        self.0.bounds +    } + +    fn min_bounds(&self) -> Size { +        self.0.min_bounds +    } + +    fn hit_test(&self, point: Point) -> Option<Hit> { +        let cursor = self.0.buffer.hit(point.x, point.y)?; + +        Some(Hit::CharOffset(cursor.index)) +    } + +    fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> { +        let run = self.0.buffer.layout_runs().nth(line)?; + +        // TODO: Index represents a grapheme, not a glyph +        let glyph = run.glyphs.get(index).or_else(|| run.glyphs.last())?; + +        let advance_last = if index == run.glyphs.len() { +            glyph.w +        } else { +            0.0 +        }; + +        Some(Point::new( +            glyph.x + glyph.x_offset * glyph.font_size + advance_last, +            glyph.y - glyph.y_offset * glyph.font_size, +        )) +    } +} + +impl PartialEq for Internal { +    fn eq(&self, other: &Self) -> bool { +        self.content == other.content +            && self.font == other.font +            && self.shaping == other.shaping +            && self.horizontal_alignment == other.horizontal_alignment +            && self.vertical_alignment == other.vertical_alignment +            && self.bounds == other.bounds +            && self.min_bounds == other.min_bounds +            && self.buffer.metrics() == other.buffer.metrics() +    } +} + +impl Default for Internal { +    fn default() -> Self { +        Self { +            buffer: cosmic_text::Buffer::new_empty(cosmic_text::Metrics { +                font_size: 1.0, +                line_height: 1.0, +            }), +            content: String::new(), +            font: Font::default(), +            shaping: Shaping::default(), +            horizontal_alignment: alignment::Horizontal::Left, +            vertical_alignment: alignment::Vertical::Top, +            bounds: Size::ZERO, +            min_bounds: Size::ZERO, +        } +    } +} + +impl fmt::Debug for Paragraph { +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        f.debug_struct("Paragraph") +            .field("content", &self.0.content) +            .field("font", &self.0.font) +            .field("shaping", &self.0.shaping) +            .field("horizontal_alignment", &self.0.horizontal_alignment) +            .field("vertical_alignment", &self.0.vertical_alignment) +            .field("bounds", &self.0.bounds) +            .field("min_bounds", &self.0.min_bounds) +            .finish() +    } +} + +#[derive(Debug, Clone)] +pub struct Weak { +    raw: sync::Weak<Internal>, +    pub min_bounds: Size, +    pub horizontal_alignment: alignment::Horizontal, +    pub vertical_alignment: alignment::Vertical, +} + +impl Weak { +    pub fn upgrade(&self) -> Option<Paragraph> { +        self.raw.upgrade().map(Paragraph) +    } +} + +impl PartialEq for Weak { +    fn eq(&self, other: &Self) -> bool { +        match (self.raw.upgrade(), other.raw.upgrade()) { +            (Some(p1), Some(p2)) => p1 == p2, +            _ => false, +        } +    } +} | 
