summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-02-04 11:12:15 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-02-24 13:29:11 +0100
commit238154af4ac8dda7f12dd90aa7be106e933bcb30 (patch)
tree4c8560b3464f0c900ddf31a3c063e925394923c6
parentb29de28d1f0f608f8029c93d154cfd1b0f8b8cbb (diff)
downloadiced-238154af4ac8dda7f12dd90aa7be106e933bcb30.tar.gz
iced-238154af4ac8dda7f12dd90aa7be106e933bcb30.tar.bz2
iced-238154af4ac8dda7f12dd90aa7be106e933bcb30.zip
Implement `font::load` command in `iced_native`
-rw-r--r--examples/todos/src/main.rs15
-rw-r--r--graphics/src/backend.rs5
-rw-r--r--graphics/src/renderer.rs5
-rw-r--r--native/src/command/action.rs16
-rw-r--r--native/src/font.rs19
-rw-r--r--native/src/lib.rs6
-rw-r--r--native/src/program.rs3
-rw-r--r--native/src/renderer/null.rs4
-rw-r--r--native/src/text.rs7
-rw-r--r--src/lib.rs6
-rw-r--r--wgpu/Cargo.toml1
-rw-r--r--wgpu/src/backend.rs6
-rw-r--r--wgpu/src/text.rs470
-rw-r--r--winit/src/application.rs10
14 files changed, 358 insertions, 215 deletions
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 5509cf0e..5df4e968 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -1,5 +1,6 @@
use iced::alignment::{self, Alignment};
use iced::event::{self, Event};
+use iced::font::{self, Font};
use iced::keyboard;
use iced::subscription;
use iced::theme::{self, Theme};
@@ -9,7 +10,7 @@ use iced::widget::{
};
use iced::window;
use iced::{Application, Element};
-use iced::{Color, Command, Font, Length, Settings, Subscription};
+use iced::{Color, Command, Length, Settings, Subscription};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@@ -44,6 +45,7 @@ struct State {
#[derive(Debug, Clone)]
enum Message {
Loaded(Result<SavedState, LoadError>),
+ FontLoaded(Result<(), font::Error>),
Saved(Result<(), SaveError>),
InputChanged(String),
CreateTask,
@@ -61,7 +63,11 @@ impl Application for Todos {
fn new(_flags: ()) -> (Todos, Command<Message>) {
(
Todos::Loading,
- Command::perform(SavedState::load(), Message::Loaded),
+ Command::batch(vec![
+ font::load(include_bytes!("../fonts/icons.ttf").as_slice())
+ .map(Message::FontLoaded),
+ Command::perform(SavedState::load(), Message::Loaded),
+ ]),
)
}
@@ -384,7 +390,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
let tasks_left = tasks.iter().filter(|task| !task.completed).count();
let filter_button = |label, filter, current_filter| {
- let label = text(label).size(16);
+ let label = text(label);
let button = button(label).style(if filter == current_filter {
theme::Button::Primary
@@ -401,8 +407,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
tasks_left,
if tasks_left == 1 { "task" } else { "tasks" }
))
- .width(Length::Fill)
- .size(16),
+ .width(Length::Fill),
row![
filter_button("All", Filter::All, current_filter),
filter_button("Active", Filter::Active, current_filter),
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index 7bbdee95..8658cffe 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -4,6 +4,8 @@ use iced_native::svg;
use iced_native::text;
use iced_native::{Font, Point, Size};
+use std::borrow::Cow;
+
/// The graphics backend of a [`Renderer`].
///
/// [`Renderer`]: crate::Renderer
@@ -64,6 +66,9 @@ pub trait Text {
point: Point,
nearest_only: bool,
) -> Option<text::Hit>;
+
+ /// Loads a [`Font`] from its bytes.
+ fn load_font(&mut self, font: Cow<'static, [u8]>);
}
/// A graphics backend that supports image rendering.
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 7ad53dec..b052c094 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -10,6 +10,7 @@ use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size};
pub use iced_native::renderer::Style;
+use std::borrow::Cow;
use std::marker::PhantomData;
/// A backend-agnostic renderer that supports all the built-in widgets.
@@ -167,6 +168,10 @@ where
)
}
+ fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
+ self.backend.load_font(bytes);
+ }
+
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
self.primitives.push(Primitive::Text {
content: text.content.to_string(),
diff --git a/native/src/command/action.rs b/native/src/command/action.rs
index a51b8c21..d1589c05 100644
--- a/native/src/command/action.rs
+++ b/native/src/command/action.rs
@@ -1,10 +1,12 @@
use crate::clipboard;
+use crate::font;
use crate::system;
use crate::widget;
use crate::window;
use iced_futures::MaybeSend;
+use std::borrow::Cow;
use std::fmt;
/// An action that a [`Command`] can perform.
@@ -27,6 +29,15 @@ pub enum Action<T> {
/// Run a widget action.
Widget(widget::Action<T>),
+
+ /// Load a font from its bytes.
+ LoadFont {
+ /// The bytes of the font to load.
+ bytes: Cow<'static, [u8]>,
+
+ /// The message to produce when the font has been loaded.
+ tagger: Box<dyn Fn(Result<(), font::Error>) -> T>,
+ },
}
impl<T> Action<T> {
@@ -49,6 +60,10 @@ impl<T> Action<T> {
Self::Window(window) => Action::Window(window.map(f)),
Self::System(system) => Action::System(system.map(f)),
Self::Widget(widget) => Action::Widget(widget.map(f)),
+ Self::LoadFont { bytes, tagger } => Action::LoadFont {
+ bytes,
+ tagger: Box::new(move |result| f(tagger(result))),
+ },
}
}
}
@@ -63,6 +78,7 @@ impl<T> fmt::Debug for Action<T> {
Self::Window(action) => write!(f, "Action::Window({action:?})"),
Self::System(action) => write!(f, "Action::System({action:?})"),
Self::Widget(_action) => write!(f, "Action::Widget"),
+ Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
}
}
}
diff --git a/native/src/font.rs b/native/src/font.rs
new file mode 100644
index 00000000..6840a25f
--- /dev/null
+++ b/native/src/font.rs
@@ -0,0 +1,19 @@
+//! Load and use fonts.
+pub use iced_core::Font;
+
+use crate::command::{self, Command};
+use std::borrow::Cow;
+
+/// An error while loading a font.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Error {}
+
+/// Load a font from its bytes.
+pub fn load(
+ bytes: impl Into<Cow<'static, [u8]>>,
+) -> Command<Result<(), Error>> {
+ Command::single(command::Action::LoadFont {
+ bytes: bytes.into(),
+ tagger: Box::new(std::convert::identity),
+ })
+}
diff --git a/native/src/lib.rs b/native/src/lib.rs
index ebdc8490..27b6fc0d 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -47,6 +47,7 @@
pub mod clipboard;
pub mod command;
pub mod event;
+pub mod font;
pub mod image;
pub mod keyboard;
pub mod layout;
@@ -80,8 +81,8 @@ mod debug;
pub use iced_core::alignment;
pub use iced_core::time;
pub use iced_core::{
- color, Alignment, Background, Color, ContentFit, Font, Length, Padding,
- Pixels, Point, Rectangle, Size, Vector,
+ color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
+ Point, Rectangle, Size, Vector,
};
pub use iced_futures::{executor, futures};
pub use iced_style::application;
@@ -95,6 +96,7 @@ pub use command::Command;
pub use debug::Debug;
pub use element::Element;
pub use event::Event;
+pub use font::Font;
pub use hasher::Hasher;
pub use layout::Layout;
pub use overlay::Overlay;
diff --git a/native/src/program.rs b/native/src/program.rs
index c71c237f..25cab332 100644
--- a/native/src/program.rs
+++ b/native/src/program.rs
@@ -1,4 +1,5 @@
//! Build interactive programs using The Elm Architecture.
+use crate::text;
use crate::{Command, Element, Renderer};
mod state;
@@ -8,7 +9,7 @@ pub use state::State;
/// The core of a user interface application following The Elm Architecture.
pub trait Program: Sized {
/// The graphics backend to use to draw the [`Program`].
- type Renderer: Renderer;
+ type Renderer: Renderer + text::Renderer;
/// The type of __messages__ your [`Program`] will produce.
type Message: std::fmt::Debug + Send;
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index 50d7d6d6..150ee786 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -2,6 +2,8 @@ use crate::renderer::{self, Renderer};
use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
+use std::borrow::Cow;
+
/// A renderer that does nothing.
///
/// It can be useful if you are writing tests!
@@ -52,6 +54,8 @@ impl text::Renderer for Null {
16.0
}
+ fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
+
fn measure(
&self,
_content: &str,
diff --git a/native/src/text.rs b/native/src/text.rs
index b7915a55..1bbd36cc 100644
--- a/native/src/text.rs
+++ b/native/src/text.rs
@@ -2,6 +2,8 @@
use crate::alignment;
use crate::{Color, Point, Rectangle, Size, Vector};
+use std::borrow::Cow;
+
/// A paragraph.
#[derive(Debug, Clone, Copy)]
pub struct Text<'a, Font> {
@@ -72,7 +74,7 @@ pub trait Renderer: crate::Renderer {
/// [`ICON_FONT`]: Self::ICON_FONT
const ARROW_DOWN_ICON: char;
- /// Returns the default [`Font`].
+ /// Returns the default [`Self::Font`].
fn default_font(&self) -> Self::Font;
/// Returns the default size of [`Text`].
@@ -112,6 +114,9 @@ pub trait Renderer: crate::Renderer {
nearest_only: bool,
) -> Option<Hit>;
+ /// Loads a [`Self::Font`] from its bytes.
+ fn load_font(&mut self, font: Cow<'static, [u8]>);
+
/// Draws the given [`Text`].
fn fill_text(&mut self, text: Text<'_, Self::Font>);
}
diff --git a/src/lib.rs b/src/lib.rs
index 318852f9..17c5ab97 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -196,6 +196,7 @@ use iced_glow as renderer;
pub use iced_native::theme;
pub use runtime::event;
+pub use runtime::font;
pub use runtime::subscription;
pub use application::Application;
@@ -203,6 +204,7 @@ pub use element::Element;
pub use error::Error;
pub use event::Event;
pub use executor::Executor;
+pub use font::Font;
pub use renderer::Renderer;
pub use result::Result;
pub use sandbox::Sandbox;
@@ -213,8 +215,8 @@ pub use theme::Theme;
pub use runtime::alignment;
pub use runtime::futures;
pub use runtime::{
- color, Alignment, Background, Color, Command, ContentFit, Font, Length,
- Padding, Point, Rectangle, Size, Vector,
+ color, Alignment, Background, Color, Command, ContentFit, Length, Padding,
+ Point, Rectangle, Size, Vector,
};
#[cfg(feature = "system")]
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 1a94c6a3..dffbbab0 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -36,6 +36,7 @@ bitflags = "1.2"
once_cell = "1.0"
rustc-hash = "1.1"
twox-hash = "1.6"
+ouroboros = "0.15"
[dependencies.bytemuck]
version = "1.9"
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 874edb96..5a275c8a 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -14,6 +14,8 @@ use tracing::info_span;
#[cfg(any(feature = "image", feature = "svg"))]
use crate::image;
+use std::borrow::Cow;
+
/// A [`wgpu`] graphics backend for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
@@ -234,6 +236,10 @@ impl backend::Text for Backend {
nearest_only,
)
}
+
+ fn load_font(&mut self, font: Cow<'static, [u8]>) {
+ self.text_pipeline.load_font(font);
+ }
}
#[cfg(feature = "image")]
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 083d9d2b..cdfcd576 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -5,108 +5,37 @@ use iced_native::alignment;
use iced_native::{Color, Font, Rectangle, Size};
use rustc_hash::{FxHashMap, FxHashSet};
+use std::borrow::Cow;
use std::cell::RefCell;
use std::hash::{BuildHasher, Hash, Hasher};
+use std::sync::Arc;
use twox_hash::RandomXxHashBuilder64;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
+ system: Option<System>,
renderers: Vec<glyphon::TextRenderer>,
atlas: glyphon::TextAtlas,
- cache: glyphon::SwashCache<'static>,
- measurement_cache: RefCell<Cache>,
- render_cache: Cache,
layer: usize,
}
-struct Cache {
- entries: FxHashMap<KeyHash, glyphon::Buffer<'static>>,
- recently_used: FxHashSet<KeyHash>,
- hasher: RandomXxHashBuilder64,
-}
-
-impl Cache {
- fn new() -> Self {
- Self {
- entries: FxHashMap::default(),
- recently_used: FxHashSet::default(),
- hasher: RandomXxHashBuilder64::default(),
- }
- }
-
- fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> {
- self.entries.get(key)
- }
-
- fn allocate(
- &mut self,
- key: Key<'_>,
- ) -> (KeyHash, &mut glyphon::Buffer<'static>) {
- let hash = {
- let mut hasher = self.hasher.build_hasher();
-
- key.content.hash(&mut hasher);
- (key.size as i32).hash(&mut hasher);
- key.font.hash(&mut hasher);
- (key.bounds.width as i32).hash(&mut hasher);
- (key.bounds.height as i32).hash(&mut hasher);
- key.color.into_rgba8().hash(&mut hasher);
-
- hasher.finish()
- };
+#[ouroboros::self_referencing]
+struct System {
+ fonts: glyphon::FontSystem,
- if !self.entries.contains_key(&hash) {
- let metrics =
- glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32);
+ #[borrows(fonts)]
+ #[not_covariant]
+ cache: glyphon::SwashCache<'this>,
- let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics);
+ #[borrows(fonts)]
+ #[not_covariant]
+ measurement_cache: RefCell<Cache<'this>>,
- buffer.set_size(key.bounds.width as i32, key.bounds.height as i32);
- buffer.set_text(
- key.content,
- glyphon::Attrs::new().family(to_family(key.font)).color({
- let [r, g, b, a] = key.color.into_linear();
-
- glyphon::Color::rgba(
- (r * 255.0) as u8,
- (g * 255.0) as u8,
- (b * 255.0) as u8,
- (a * 255.0) as u8,
- )
- }),
- );
-
- let _ = self.entries.insert(hash, buffer);
- }
-
- let _ = self.recently_used.insert(hash);
-
- (hash, self.entries.get_mut(&hash).unwrap())
- }
-
- fn trim(&mut self) {
- self.entries
- .retain(|key, _| self.recently_used.contains(key));
-
- self.recently_used.clear();
- }
+ #[borrows(fonts)]
+ #[not_covariant]
+ render_cache: Cache<'this>,
}
-#[derive(Debug, Clone, Copy)]
-struct Key<'a> {
- content: &'a str,
- size: f32,
- font: Font,
- bounds: Size,
- color: Color,
-}
-
-type KeyHash = u64;
-
-// TODO: Share with `iced_graphics`
-static FONT_SYSTEM: once_cell::sync::Lazy<glyphon::FontSystem> =
- once_cell::sync::Lazy::new(glyphon::FontSystem::new);
-
impl Pipeline {
pub fn new(
device: &wgpu::Device,
@@ -114,15 +43,41 @@ impl Pipeline {
format: wgpu::TextureFormat,
) -> Self {
Pipeline {
+ system: Some(
+ SystemBuilder {
+ fonts: glyphon::FontSystem::new(),
+ cache_builder: |fonts| glyphon::SwashCache::new(fonts),
+ measurement_cache_builder: |_| RefCell::new(Cache::new()),
+ render_cache_builder: |_| Cache::new(),
+ }
+ .build(),
+ ),
renderers: Vec::new(),
atlas: glyphon::TextAtlas::new(device, queue, format),
- cache: glyphon::SwashCache::new(&FONT_SYSTEM),
- measurement_cache: RefCell::new(Cache::new()),
- render_cache: Cache::new(),
layer: 0,
}
}
+ pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
+ let heads = self.system.take().unwrap().into_heads();
+
+ let (locale, mut db) = heads.fonts.into_locale_and_db();
+
+ db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new(
+ bytes.to_owned(),
+ )));
+
+ self.system = Some(
+ SystemBuilder {
+ fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db),
+ cache_builder: |fonts| glyphon::SwashCache::new(fonts),
+ measurement_cache_builder: |_| RefCell::new(Cache::new()),
+ render_cache_builder: |_| Cache::new(),
+ }
+ .build(),
+ );
+ }
+
pub fn prepare(
&mut self,
device: &wgpu::Device,
@@ -132,93 +87,100 @@ impl Pipeline {
scale_factor: f32,
target_size: Size<u32>,
) {
- if self.renderers.len() <= self.layer {
- self.renderers
- .push(glyphon::TextRenderer::new(device, queue));
- }
-
- let renderer = &mut self.renderers[self.layer];
-
- let keys: Vec<_> = sections
- .iter()
- .map(|section| {
- let (key, _) = self.render_cache.allocate(Key {
- content: section.content,
- size: section.size * scale_factor,
- font: section.font,
- bounds: Size {
- width: section.bounds.width * scale_factor,
- height: section.bounds.height * scale_factor,
+ self.system.as_mut().unwrap().with_mut(|fields| {
+ if self.renderers.len() <= self.layer {
+ self.renderers
+ .push(glyphon::TextRenderer::new(device, queue));
+ }
+
+ let renderer = &mut self.renderers[self.layer];
+
+ let keys: Vec<_> = sections
+ .iter()
+ .map(|section| {
+ let (key, _) = fields.render_cache.allocate(
+ fields.fonts,
+ Key {
+ content: section.content,
+ size: section.size * scale_factor,
+ font: section.font,
+ bounds: Size {
+ width: section.bounds.width * scale_factor,
+ height: section.bounds.height * scale_factor,
+ },
+ color: section.color,
+ },
+ );
+
+ key
+ })
+ .collect();
+
+ let bounds = glyphon::TextBounds {
+ left: (bounds.x * scale_factor) as i32,
+ top: (bounds.y * scale_factor) as i32,
+ right: ((bounds.x + bounds.width) * scale_factor) as i32,
+ bottom: ((bounds.y + bounds.height) * scale_factor) as i32,
+ };
+
+ let text_areas: Vec<_> = sections
+ .iter()
+ .zip(keys.iter())
+ .map(|(section, key)| {
+ let buffer = fields
+ .render_cache
+ .get(key)
+ .expect("Get cached buffer");
+
+ let x = section.bounds.x * scale_factor;
+ let y = section.bounds.y * scale_factor;
+
+ let (total_lines, max_width) = buffer
+ .layout_runs()
+ .enumerate()
+ .fold((0, 0.0), |(_, max), (i, buffer)| {
+ (i + 1, buffer.line_w.max(max))
+ });
+
+ let total_height =
+ total_lines as f32 * section.size * 1.2 * scale_factor;
+
+ let left = match section.horizontal_alignment {
+ alignment::Horizontal::Left => x,
+ alignment::Horizontal::Center => x - max_width / 2.0,
+ alignment::Horizontal::Right => x - max_width,
+ };
+
+ let top = match section.vertical_alignment {
+ alignment::Vertical::Top => y,
+ alignment::Vertical::Center => y - total_height / 2.0,
+ alignment::Vertical::Bottom => y - total_height,
+ };
+
+ glyphon::TextArea {
+ buffer,
+ left: left as i32,
+ top: top as i32,
+ bounds,
+ }
+ })
+ .collect();
+
+ renderer
+ .prepare(
+ device,
+ queue,
+ &mut self.atlas,
+ glyphon::Resolution {
+ width: target_size.width,
+ height: target_size.height,
},
- color: section.color,
- });
-
- key
- })
- .collect();
-
- let bounds = glyphon::TextBounds {
- left: (bounds.x * scale_factor) as i32,
- top: (bounds.y * scale_factor) as i32,
- right: ((bounds.x + bounds.width) * scale_factor) as i32,
- bottom: ((bounds.y + bounds.height) * scale_factor) as i32,
- };
-
- let text_areas: Vec<_> = sections
- .iter()
- .zip(keys.iter())
- .map(|(section, key)| {
- let buffer =
- self.render_cache.get(key).expect("Get cached buffer");
-
- let x = section.bounds.x * scale_factor;
- let y = section.bounds.y * scale_factor;
-
- let (total_lines, max_width) = buffer
- .layout_runs()
- .enumerate()
- .fold((0, 0.0), |(_, max), (i, buffer)| {
- (i + 1, buffer.line_w.max(max))
- });
-
- let total_height =
- total_lines as f32 * section.size * 1.2 * scale_factor;
-
- let left = match section.horizontal_alignment {
- alignment::Horizontal::Left => x,
- alignment::Horizontal::Center => x - max_width / 2.0,
- alignment::Horizontal::Right => x - max_width,
- };
-
- let top = match section.vertical_alignment {
- alignment::Vertical::Top => y,
- alignment::Vertical::Center => y - total_height / 2.0,
- alignment::Vertical::Bottom => y - total_height,
- };
-
- glyphon::TextArea {
- buffer,
- left: left as i32,
- top: top as i32,
- bounds,
- }
- })
- .collect();
-
- renderer
- .prepare(
- device,
- queue,
- &mut self.atlas,
- glyphon::Resolution {
- width: target_size.width,
- height: target_size.height,
- },
- &text_areas,
- glyphon::Color::rgb(0, 0, 0),
- &mut self.cache,
- )
- .expect("Prepare text sections");
+ &text_areas,
+ glyphon::Color::rgb(0, 0, 0),
+ fields.cache,
+ )
+ .expect("Prepare text sections");
+ });
}
pub fn render(
@@ -251,7 +213,10 @@ impl Pipeline {
pub fn end_frame(&mut self) {
self.renderers.truncate(self.layer);
- self.render_cache.trim();
+ self.system
+ .as_mut()
+ .unwrap()
+ .with_render_cache_mut(|cache| cache.trim());
self.layer = 0;
}
@@ -263,24 +228,29 @@ impl Pipeline {
font: Font,
bounds: Size,
) -> (f32, f32) {
- let mut measurement_cache = self.measurement_cache.borrow_mut();
-
- let (_, paragraph) = measurement_cache.allocate(Key {
- content,
- size: size,
- font,
- bounds,
- color: Color::BLACK,
- });
+ self.system.as_ref().unwrap().with(|fields| {
+ let mut measurement_cache = fields.measurement_cache.borrow_mut();
+
+ let (_, paragraph) = measurement_cache.allocate(
+ fields.fonts,
+ Key {
+ content,
+ size: size,
+ font,
+ bounds,
+ color: Color::BLACK,
+ },
+ );
- let (total_lines, max_width) = paragraph
- .layout_runs()
- .enumerate()
- .fold((0, 0.0), |(_, max), (i, buffer)| {
- (i + 1, buffer.line_w.max(max))
- });
+ let (total_lines, max_width) = paragraph
+ .layout_runs()
+ .enumerate()
+ .fold((0, 0.0), |(_, max), (i, buffer)| {
+ (i + 1, buffer.line_w.max(max))
+ });
- (max_width, size * 1.2 * total_lines as f32)
+ (max_width, size * 1.2 * total_lines as f32)
+ })
}
pub fn hit_test(
@@ -292,23 +262,31 @@ impl Pipeline {
point: iced_native::Point,
_nearest_only: bool,
) -> Option<Hit> {
- let mut measurement_cache = self.measurement_cache.borrow_mut();
-
- let (_, paragraph) = measurement_cache.allocate(Key {
- content,
- size: size,
- font,
- bounds,
- color: Color::BLACK,
- });
+ self.system.as_ref().unwrap().with(|fields| {
+ let mut measurement_cache = fields.measurement_cache.borrow_mut();
+
+ let (_, paragraph) = measurement_cache.allocate(
+ fields.fonts,
+ Key {
+ content,
+ size: size,
+ font,
+ bounds,
+ color: Color::BLACK,
+ },
+ );
- let cursor = paragraph.hit(point.x as i32, point.y as i32)?;
+ let cursor = paragraph.hit(point.x as i32, point.y as i32)?;
- Some(Hit::CharOffset(cursor.index))
+ Some(Hit::CharOffset(cursor.index))
+ })
}
pub fn trim_measurement_cache(&mut self) {
- self.measurement_cache.borrow_mut().trim();
+ self.system
+ .as_mut()
+ .unwrap()
+ .with_measurement_cache_mut(|cache| cache.borrow_mut().trim());
}
}
@@ -322,3 +300,87 @@ fn to_family(font: Font) -> glyphon::Family<'static> {
Font::Monospace => glyphon::Family::Monospace,
}
}
+
+struct Cache<'a> {
+ entries: FxHashMap<KeyHash, glyphon::Buffer<'a>>,
+ recently_used: FxHashSet<KeyHash>,
+ hasher: RandomXxHashBuilder64,
+}
+
+impl<'a> Cache<'a> {
+ fn new() -> Self {
+ Self {
+ entries: FxHashMap::default(),
+ recently_used: FxHashSet::default(),
+ hasher: RandomXxHashBuilder64::default(),
+ }
+ }
+
+ fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> {
+ self.entries.get(key)
+ }
+
+ fn allocate(
+ &mut self,
+ fonts: &'a glyphon::FontSystem,
+ key: Key<'_>,
+ ) -> (KeyHash, &mut glyphon::Buffer<'a>) {
+ let hash = {
+ let mut hasher = self.hasher.build_hasher();
+
+ key.content.hash(&mut hasher);
+ (key.size as i32).hash(&mut hasher);
+ key.font.hash(&mut hasher);
+ (key.bounds.width as i32).hash(&mut hasher);
+ (key.bounds.height as i32).hash(&mut hasher);
+ key.color.into_rgba8().hash(&mut hasher);
+
+ hasher.finish()
+ };
+
+ if !self.entries.contains_key(&hash) {
+ let metrics =
+ glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32);
+ let mut buffer = glyphon::Buffer::new(&fonts, metrics);
+
+ buffer.set_size(key.bounds.width as i32, key.bounds.height as i32);
+ buffer.set_text(
+ key.content,
+ glyphon::Attrs::new().family(to_family(key.font)).color({
+ let [r, g, b, a] = key.color.into_linear();
+
+ glyphon::Color::rgba(
+ (r * 255.0) as u8,
+ (g * 255.0) as u8,
+ (b * 255.0) as u8,
+ (a * 255.0) as u8,
+ )
+ }),
+ );
+
+ let _ = self.entries.insert(hash, buffer);
+ }
+
+ let _ = self.recently_used.insert(hash);
+
+ (hash, self.entries.get_mut(&hash).unwrap())
+ }
+
+ fn trim(&mut self) {
+ self.entries
+ .retain(|key, _| self.recently_used.contains(key));
+
+ self.recently_used.clear();
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Key<'a> {
+ content: &'a str,
+ size: f32,
+ font: Font,
+ bounds: Size,
+ color: Color,
+}
+
+type KeyHash = u64;
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 9781a453..889becad 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -851,6 +851,16 @@ pub fn run_command<A, E>(
current_cache = user_interface.into_cache();
*cache = current_cache;
}
+ command::Action::LoadFont { bytes, tagger } => {
+ use crate::text::Renderer;
+
+ // TODO: Error handling (?)
+ renderer.load_font(bytes);
+
+ proxy
+ .send_event(tagger(Ok(())))
+ .expect("Send message to event loop");
+ }
}
}
}