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), + FontLoaded(Result<(), font::Error>), Saved(Result<(), SaveError>), InputChanged(String), CreateTask, @@ -61,7 +63,11 @@ impl Application for Todos { fn new(_flags: ()) -> (Todos, Command) { ( 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 { 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 { 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; + + /// 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 { /// Run a widget action. Widget(widget::Action), + + /// 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) -> T>, + }, } impl Action { @@ -49,6 +60,10 @@ impl Action { 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 fmt::Debug for Action { 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>, +) -> Command> { + 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; + /// 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,27 +5,309 @@ 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, renderers: Vec, atlas: glyphon::TextAtlas, - cache: glyphon::SwashCache<'static>, - measurement_cache: RefCell, - render_cache: Cache, layer: usize, } -struct Cache { - entries: FxHashMap>, +#[ouroboros::self_referencing] +struct System { + fonts: glyphon::FontSystem, + + #[borrows(fonts)] + #[not_covariant] + cache: glyphon::SwashCache<'this>, + + #[borrows(fonts)] + #[not_covariant] + measurement_cache: RefCell>, + + #[borrows(fonts)] + #[not_covariant] + render_cache: Cache<'this>, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + 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), + 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, + queue: &wgpu::Queue, + sections: &[Text<'_>], + bounds: Rectangle, + scale_factor: f32, + target_size: Size, + ) { + 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, + }, + &text_areas, + glyphon::Color::rgb(0, 0, 0), + fields.cache, + ) + .expect("Prepare text sections"); + }); + } + + pub fn render( + &mut self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + ) { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + let renderer = &mut self.renderers[self.layer]; + + renderer + .render(&self.atlas, &mut render_pass) + .expect("Render text"); + + self.layer += 1; + } + + pub fn end_frame(&mut self) { + self.renderers.truncate(self.layer); + self.system + .as_mut() + .unwrap() + .with_render_cache_mut(|cache| cache.trim()); + + self.layer = 0; + } + + pub fn measure( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + 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)) + }); + + (max_width, size * 1.2 * total_lines as f32) + }) + } + + pub fn hit_test( + &self, + content: &str, + size: f32, + font: iced_native::Font, + bounds: iced_native::Size, + point: iced_native::Point, + _nearest_only: bool, + ) -> Option { + 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)?; + + Some(Hit::CharOffset(cursor.index)) + }) + } + + pub fn trim_measurement_cache(&mut self) { + self.system + .as_mut() + .unwrap() + .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); + } +} + +fn to_family(font: Font) -> glyphon::Family<'static> { + match font { + Font::Name(name) => glyphon::Family::Name(name), + Font::SansSerif => glyphon::Family::SansSerif, + Font::Serif => glyphon::Family::Serif, + Font::Cursive => glyphon::Family::Cursive, + Font::Fantasy => glyphon::Family::Fantasy, + Font::Monospace => glyphon::Family::Monospace, + } +} + +struct Cache<'a> { + entries: FxHashMap>, recently_used: FxHashSet, hasher: RandomXxHashBuilder64, } -impl Cache { +impl<'a> Cache<'a> { fn new() -> Self { Self { entries: FxHashMap::default(), @@ -34,14 +316,15 @@ impl Cache { } } - fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> { + 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<'static>) { + ) -> (KeyHash, &mut glyphon::Buffer<'a>) { let hash = { let mut hasher = self.hasher.build_hasher(); @@ -58,8 +341,7 @@ impl Cache { 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(&FONT_SYSTEM, metrics); + let mut buffer = glyphon::Buffer::new(&fonts, metrics); buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); buffer.set_text( @@ -102,223 +384,3 @@ struct Key<'a> { } type KeyHash = u64; - -// TODO: Share with `iced_graphics` -static FONT_SYSTEM: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(glyphon::FontSystem::new); - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - ) -> Self { - Pipeline { - 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 prepare( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - sections: &[Text<'_>], - bounds: Rectangle, - scale_factor: f32, - target_size: Size, - ) { - 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, - }, - 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"); - } - - pub fn render( - &mut self, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - ) { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - let renderer = &mut self.renderers[self.layer]; - - renderer - .render(&self.atlas, &mut render_pass) - .expect("Render text"); - - self.layer += 1; - } - - pub fn end_frame(&mut self) { - self.renderers.truncate(self.layer); - self.render_cache.trim(); - - self.layer = 0; - } - - pub fn measure( - &self, - content: &str, - size: f32, - 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, - }); - - 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) - } - - pub fn hit_test( - &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, - _nearest_only: bool, - ) -> Option { - let mut measurement_cache = self.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate(Key { - content, - size: size, - font, - bounds, - color: Color::BLACK, - }); - - let cursor = paragraph.hit(point.x as i32, point.y as i32)?; - - Some(Hit::CharOffset(cursor.index)) - } - - pub fn trim_measurement_cache(&mut self) { - self.measurement_cache.borrow_mut().trim(); - } -} - -fn to_family(font: Font) -> glyphon::Family<'static> { - match font { - Font::Name(name) => glyphon::Family::Name(name), - Font::SansSerif => glyphon::Family::SansSerif, - Font::Serif => glyphon::Family::Serif, - Font::Cursive => glyphon::Family::Cursive, - Font::Fantasy => glyphon::Family::Fantasy, - Font::Monospace => glyphon::Family::Monospace, - } -} 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( 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"); + } } } }