Implement font::load command in iced_native

This commit is contained in:
Héctor Ramón Jiménez 2023-02-04 11:12:15 +01:00
parent b29de28d1f
commit 238154af4a
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
14 changed files with 384 additions and 241 deletions

View file

@ -1,5 +1,6 @@
use iced::alignment::{self, Alignment}; use iced::alignment::{self, Alignment};
use iced::event::{self, Event}; use iced::event::{self, Event};
use iced::font::{self, Font};
use iced::keyboard; use iced::keyboard;
use iced::subscription; use iced::subscription;
use iced::theme::{self, Theme}; use iced::theme::{self, Theme};
@ -9,7 +10,7 @@ use iced::widget::{
}; };
use iced::window; use iced::window;
use iced::{Application, Element}; 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 once_cell::sync::Lazy;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -44,6 +45,7 @@ struct State {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
Loaded(Result<SavedState, LoadError>), Loaded(Result<SavedState, LoadError>),
FontLoaded(Result<(), font::Error>),
Saved(Result<(), SaveError>), Saved(Result<(), SaveError>),
InputChanged(String), InputChanged(String),
CreateTask, CreateTask,
@ -61,7 +63,11 @@ impl Application for Todos {
fn new(_flags: ()) -> (Todos, Command<Message>) { fn new(_flags: ()) -> (Todos, Command<Message>) {
( (
Todos::Loading, 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 tasks_left = tasks.iter().filter(|task| !task.completed).count();
let filter_button = |label, filter, current_filter| { 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 { let button = button(label).style(if filter == current_filter {
theme::Button::Primary theme::Button::Primary
@ -401,8 +407,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
tasks_left, tasks_left,
if tasks_left == 1 { "task" } else { "tasks" } if tasks_left == 1 { "task" } else { "tasks" }
)) ))
.width(Length::Fill) .width(Length::Fill),
.size(16),
row![ row![
filter_button("All", Filter::All, current_filter), filter_button("All", Filter::All, current_filter),
filter_button("Active", Filter::Active, current_filter), filter_button("Active", Filter::Active, current_filter),

View file

@ -4,6 +4,8 @@ use iced_native::svg;
use iced_native::text; use iced_native::text;
use iced_native::{Font, Point, Size}; use iced_native::{Font, Point, Size};
use std::borrow::Cow;
/// The graphics backend of a [`Renderer`]. /// The graphics backend of a [`Renderer`].
/// ///
/// [`Renderer`]: crate::Renderer /// [`Renderer`]: crate::Renderer
@ -64,6 +66,9 @@ pub trait Text {
point: Point, point: Point,
nearest_only: bool, nearest_only: bool,
) -> Option<text::Hit>; ) -> 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. /// A graphics backend that supports image rendering.

View file

@ -10,6 +10,7 @@ use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size};
pub use iced_native::renderer::Style; pub use iced_native::renderer::Style;
use std::borrow::Cow;
use std::marker::PhantomData; use std::marker::PhantomData;
/// A backend-agnostic renderer that supports all the built-in widgets. /// 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>) { fn fill_text(&mut self, text: Text<'_, Self::Font>) {
self.primitives.push(Primitive::Text { self.primitives.push(Primitive::Text {
content: text.content.to_string(), content: text.content.to_string(),

View file

@ -1,10 +1,12 @@
use crate::clipboard; use crate::clipboard;
use crate::font;
use crate::system; use crate::system;
use crate::widget; use crate::widget;
use crate::window; use crate::window;
use iced_futures::MaybeSend; use iced_futures::MaybeSend;
use std::borrow::Cow;
use std::fmt; use std::fmt;
/// An action that a [`Command`] can perform. /// An action that a [`Command`] can perform.
@ -27,6 +29,15 @@ pub enum Action<T> {
/// Run a widget action. /// Run a widget action.
Widget(widget::Action<T>), 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> { impl<T> Action<T> {
@ -49,6 +60,10 @@ impl<T> Action<T> {
Self::Window(window) => Action::Window(window.map(f)), Self::Window(window) => Action::Window(window.map(f)),
Self::System(system) => Action::System(system.map(f)), Self::System(system) => Action::System(system.map(f)),
Self::Widget(widget) => Action::Widget(widget.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::Window(action) => write!(f, "Action::Window({action:?})"),
Self::System(action) => write!(f, "Action::System({action:?})"), Self::System(action) => write!(f, "Action::System({action:?})"),
Self::Widget(_action) => write!(f, "Action::Widget"), Self::Widget(_action) => write!(f, "Action::Widget"),
Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
} }
} }
} }

19
native/src/font.rs Normal file
View file

@ -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),
})
}

View file

@ -47,6 +47,7 @@
pub mod clipboard; pub mod clipboard;
pub mod command; pub mod command;
pub mod event; pub mod event;
pub mod font;
pub mod image; pub mod image;
pub mod keyboard; pub mod keyboard;
pub mod layout; pub mod layout;
@ -80,8 +81,8 @@ mod debug;
pub use iced_core::alignment; pub use iced_core::alignment;
pub use iced_core::time; pub use iced_core::time;
pub use iced_core::{ pub use iced_core::{
color, Alignment, Background, Color, ContentFit, Font, Length, Padding, color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
Pixels, Point, Rectangle, Size, Vector, Point, Rectangle, Size, Vector,
}; };
pub use iced_futures::{executor, futures}; pub use iced_futures::{executor, futures};
pub use iced_style::application; pub use iced_style::application;
@ -95,6 +96,7 @@ pub use command::Command;
pub use debug::Debug; pub use debug::Debug;
pub use element::Element; pub use element::Element;
pub use event::Event; pub use event::Event;
pub use font::Font;
pub use hasher::Hasher; pub use hasher::Hasher;
pub use layout::Layout; pub use layout::Layout;
pub use overlay::Overlay; pub use overlay::Overlay;

View file

@ -1,4 +1,5 @@
//! Build interactive programs using The Elm Architecture. //! Build interactive programs using The Elm Architecture.
use crate::text;
use crate::{Command, Element, Renderer}; use crate::{Command, Element, Renderer};
mod state; mod state;
@ -8,7 +9,7 @@ pub use state::State;
/// The core of a user interface application following The Elm Architecture. /// The core of a user interface application following The Elm Architecture.
pub trait Program: Sized { pub trait Program: Sized {
/// The graphics backend to use to draw the [`Program`]. /// 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. /// The type of __messages__ your [`Program`] will produce.
type Message: std::fmt::Debug + Send; type Message: std::fmt::Debug + Send;

View file

@ -2,6 +2,8 @@ use crate::renderer::{self, Renderer};
use crate::text::{self, Text}; use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
use std::borrow::Cow;
/// A renderer that does nothing. /// A renderer that does nothing.
/// ///
/// It can be useful if you are writing tests! /// It can be useful if you are writing tests!
@ -52,6 +54,8 @@ impl text::Renderer for Null {
16.0 16.0
} }
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn measure( fn measure(
&self, &self,
_content: &str, _content: &str,

View file

@ -2,6 +2,8 @@
use crate::alignment; use crate::alignment;
use crate::{Color, Point, Rectangle, Size, Vector}; use crate::{Color, Point, Rectangle, Size, Vector};
use std::borrow::Cow;
/// A paragraph. /// A paragraph.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Text<'a, Font> { pub struct Text<'a, Font> {
@ -72,7 +74,7 @@ pub trait Renderer: crate::Renderer {
/// [`ICON_FONT`]: Self::ICON_FONT /// [`ICON_FONT`]: Self::ICON_FONT
const ARROW_DOWN_ICON: char; const ARROW_DOWN_ICON: char;
/// Returns the default [`Font`]. /// Returns the default [`Self::Font`].
fn default_font(&self) -> Self::Font; fn default_font(&self) -> Self::Font;
/// Returns the default size of [`Text`]. /// Returns the default size of [`Text`].
@ -112,6 +114,9 @@ pub trait Renderer: crate::Renderer {
nearest_only: bool, nearest_only: bool,
) -> Option<Hit>; ) -> Option<Hit>;
/// Loads a [`Self::Font`] from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
/// Draws the given [`Text`]. /// Draws the given [`Text`].
fn fill_text(&mut self, text: Text<'_, Self::Font>); fn fill_text(&mut self, text: Text<'_, Self::Font>);
} }

View file

@ -196,6 +196,7 @@ use iced_glow as renderer;
pub use iced_native::theme; pub use iced_native::theme;
pub use runtime::event; pub use runtime::event;
pub use runtime::font;
pub use runtime::subscription; pub use runtime::subscription;
pub use application::Application; pub use application::Application;
@ -203,6 +204,7 @@ pub use element::Element;
pub use error::Error; pub use error::Error;
pub use event::Event; pub use event::Event;
pub use executor::Executor; pub use executor::Executor;
pub use font::Font;
pub use renderer::Renderer; pub use renderer::Renderer;
pub use result::Result; pub use result::Result;
pub use sandbox::Sandbox; pub use sandbox::Sandbox;
@ -213,8 +215,8 @@ pub use theme::Theme;
pub use runtime::alignment; pub use runtime::alignment;
pub use runtime::futures; pub use runtime::futures;
pub use runtime::{ pub use runtime::{
color, Alignment, Background, Color, Command, ContentFit, Font, Length, color, Alignment, Background, Color, Command, ContentFit, Length, Padding,
Padding, Point, Rectangle, Size, Vector, Point, Rectangle, Size, Vector,
}; };
#[cfg(feature = "system")] #[cfg(feature = "system")]

View file

@ -36,6 +36,7 @@ bitflags = "1.2"
once_cell = "1.0" once_cell = "1.0"
rustc-hash = "1.1" rustc-hash = "1.1"
twox-hash = "1.6" twox-hash = "1.6"
ouroboros = "0.15"
[dependencies.bytemuck] [dependencies.bytemuck]
version = "1.9" version = "1.9"

View file

@ -14,6 +14,8 @@ use tracing::info_span;
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
use crate::image; use crate::image;
use std::borrow::Cow;
/// A [`wgpu`] graphics backend for [`iced`]. /// A [`wgpu`] graphics backend for [`iced`].
/// ///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
@ -234,6 +236,10 @@ impl backend::Text for Backend {
nearest_only, nearest_only,
) )
} }
fn load_font(&mut self, font: Cow<'static, [u8]>) {
self.text_pipeline.load_font(font);
}
} }
#[cfg(feature = "image")] #[cfg(feature = "image")]

View file

@ -5,27 +5,309 @@ use iced_native::alignment;
use iced_native::{Color, Font, Rectangle, Size}; use iced_native::{Color, Font, Rectangle, Size};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use std::borrow::Cow;
use std::cell::RefCell; use std::cell::RefCell;
use std::hash::{BuildHasher, Hash, Hasher}; use std::hash::{BuildHasher, Hash, Hasher};
use std::sync::Arc;
use twox_hash::RandomXxHashBuilder64; use twox_hash::RandomXxHashBuilder64;
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Pipeline { pub struct Pipeline {
system: Option<System>,
renderers: Vec<glyphon::TextRenderer>, renderers: Vec<glyphon::TextRenderer>,
atlas: glyphon::TextAtlas, atlas: glyphon::TextAtlas,
cache: glyphon::SwashCache<'static>,
measurement_cache: RefCell<Cache>,
render_cache: Cache,
layer: usize, layer: usize,
} }
struct Cache { #[ouroboros::self_referencing]
entries: FxHashMap<KeyHash, glyphon::Buffer<'static>>, struct System {
fonts: glyphon::FontSystem,
#[borrows(fonts)]
#[not_covariant]
cache: glyphon::SwashCache<'this>,
#[borrows(fonts)]
#[not_covariant]
measurement_cache: RefCell<Cache<'this>>,
#[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<u32>,
) {
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<Hit> {
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<KeyHash, glyphon::Buffer<'a>>,
recently_used: FxHashSet<KeyHash>, recently_used: FxHashSet<KeyHash>,
hasher: RandomXxHashBuilder64, hasher: RandomXxHashBuilder64,
} }
impl Cache { impl<'a> Cache<'a> {
fn new() -> Self { fn new() -> Self {
Self { Self {
entries: FxHashMap::default(), 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) self.entries.get(key)
} }
fn allocate( fn allocate(
&mut self, &mut self,
fonts: &'a glyphon::FontSystem,
key: Key<'_>, key: Key<'_>,
) -> (KeyHash, &mut glyphon::Buffer<'static>) { ) -> (KeyHash, &mut glyphon::Buffer<'a>) {
let hash = { let hash = {
let mut hasher = self.hasher.build_hasher(); let mut hasher = self.hasher.build_hasher();
@ -58,8 +341,7 @@ impl Cache {
if !self.entries.contains_key(&hash) { if !self.entries.contains_key(&hash) {
let metrics = let metrics =
glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32);
let mut buffer = glyphon::Buffer::new(&fonts, metrics);
let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics);
buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); buffer.set_size(key.bounds.width as i32, key.bounds.height as i32);
buffer.set_text( buffer.set_text(
@ -102,223 +384,3 @@ struct Key<'a> {
} }
type KeyHash = u64; 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,
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<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,
},
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<Hit> {
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,
}
}

View file

@ -851,6 +851,16 @@ pub fn run_command<A, E>(
current_cache = user_interface.into_cache(); current_cache = user_interface.into_cache();
*cache = current_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");
}
} }
} }
} }