Draft text support in iced_tiny_skia
This commit is contained in:
parent
744f3028f4
commit
64fb722dfe
4 changed files with 523 additions and 112 deletions
|
|
@ -12,6 +12,8 @@ raw-window-handle = "0.5"
|
||||||
softbuffer = "0.2"
|
softbuffer = "0.2"
|
||||||
tiny-skia = "0.8"
|
tiny-skia = "0.8"
|
||||||
bytemuck = "1"
|
bytemuck = "1"
|
||||||
|
rustc-hash = "1.1"
|
||||||
|
ouroboros = "0.15"
|
||||||
|
|
||||||
[dependencies.iced_native]
|
[dependencies.iced_native]
|
||||||
version = "0.9"
|
version = "0.9"
|
||||||
|
|
@ -20,3 +22,16 @@ path = "../native"
|
||||||
[dependencies.iced_graphics]
|
[dependencies.iced_graphics]
|
||||||
version = "0.7"
|
version = "0.7"
|
||||||
path = "../graphics"
|
path = "../graphics"
|
||||||
|
|
||||||
|
[dependencies.cosmic-text]
|
||||||
|
features = ["std", "swash"]
|
||||||
|
git = "https://github.com/hecrj/cosmic-text"
|
||||||
|
rev = "dc83efbf00a2efb4118403538e8a47bfd69c3e5e"
|
||||||
|
|
||||||
|
[dependencies.twox-hash]
|
||||||
|
version = "1.6"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
|
||||||
|
version = "1.6.1"
|
||||||
|
features = ["std"]
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use std::borrow::Cow;
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
default_font: Font,
|
default_font: Font,
|
||||||
default_text_size: f32,
|
default_text_size: f32,
|
||||||
|
text_pipeline: crate::text::Pipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend {
|
impl Backend {
|
||||||
|
|
@ -16,6 +17,7 @@ impl Backend {
|
||||||
Self {
|
Self {
|
||||||
default_font: settings.default_font,
|
default_font: settings.default_font,
|
||||||
default_text_size: settings.default_text_size,
|
default_text_size: settings.default_text_size,
|
||||||
|
text_pipeline: crate::text::Pipeline::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,123 +34,161 @@ impl Backend {
|
||||||
let scale_factor = viewport.scale_factor() as f32;
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
|
|
||||||
for primitive in primitives {
|
for primitive in primitives {
|
||||||
draw_primitive(primitive, pixels, None, scale_factor, Vector::ZERO);
|
self.draw_primitive(
|
||||||
|
primitive,
|
||||||
|
pixels,
|
||||||
|
None,
|
||||||
|
scale_factor,
|
||||||
|
Vector::ZERO,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.text_pipeline.end_frame();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_primitive(
|
fn draw_primitive(
|
||||||
primitive: &Primitive,
|
&mut self,
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
primitive: &Primitive,
|
||||||
clip_mask: Option<&tiny_skia::ClipMask>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
scale_factor: f32,
|
clip_mask: Option<&tiny_skia::ClipMask>,
|
||||||
translation: Vector,
|
scale_factor: f32,
|
||||||
) {
|
translation: Vector,
|
||||||
match primitive {
|
) {
|
||||||
Primitive::None => {}
|
match primitive {
|
||||||
Primitive::Quad {
|
Primitive::None => {}
|
||||||
bounds,
|
Primitive::Quad {
|
||||||
background,
|
bounds,
|
||||||
border_radius: _, // TODO
|
background,
|
||||||
border_width,
|
border_radius: _, // TODO
|
||||||
border_color,
|
border_width,
|
||||||
} => {
|
border_color,
|
||||||
let transform = tiny_skia::Transform::from_translate(
|
} => {
|
||||||
translation.x,
|
let transform = tiny_skia::Transform::from_translate(
|
||||||
translation.y,
|
translation.x,
|
||||||
)
|
translation.y,
|
||||||
.post_scale(scale_factor, scale_factor);
|
|
||||||
|
|
||||||
let path = tiny_skia::PathBuilder::from_rect(
|
|
||||||
tiny_skia::Rect::from_xywh(
|
|
||||||
bounds.x,
|
|
||||||
bounds.y,
|
|
||||||
bounds.width,
|
|
||||||
bounds.height,
|
|
||||||
)
|
)
|
||||||
.expect("Create quad rectangle"),
|
.post_scale(scale_factor, scale_factor);
|
||||||
);
|
|
||||||
|
|
||||||
pixels.fill_path(
|
let path = tiny_skia::PathBuilder::from_rect(
|
||||||
&path,
|
tiny_skia::Rect::from_xywh(
|
||||||
&tiny_skia::Paint {
|
bounds.x,
|
||||||
shader: match background {
|
bounds.y,
|
||||||
Background::Color(color) => {
|
bounds.width,
|
||||||
tiny_skia::Shader::SolidColor(into_color(*color))
|
bounds.height,
|
||||||
}
|
)
|
||||||
},
|
.expect("Create quad rectangle"),
|
||||||
anti_alias: true,
|
);
|
||||||
..tiny_skia::Paint::default()
|
|
||||||
},
|
|
||||||
tiny_skia::FillRule::EvenOdd,
|
|
||||||
transform,
|
|
||||||
clip_mask,
|
|
||||||
);
|
|
||||||
|
|
||||||
if *border_width > 0.0 {
|
pixels.fill_path(
|
||||||
pixels.stroke_path(
|
|
||||||
&path,
|
&path,
|
||||||
&tiny_skia::Paint {
|
&tiny_skia::Paint {
|
||||||
shader: tiny_skia::Shader::SolidColor(into_color(
|
shader: match background {
|
||||||
*border_color,
|
Background::Color(color) => {
|
||||||
)),
|
tiny_skia::Shader::SolidColor(into_color(
|
||||||
|
*color,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
anti_alias: true,
|
anti_alias: true,
|
||||||
..tiny_skia::Paint::default()
|
..tiny_skia::Paint::default()
|
||||||
},
|
},
|
||||||
&tiny_skia::Stroke {
|
tiny_skia::FillRule::EvenOdd,
|
||||||
width: *border_width,
|
|
||||||
..tiny_skia::Stroke::default()
|
|
||||||
},
|
|
||||||
transform,
|
transform,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if *border_width > 0.0 {
|
||||||
|
pixels.stroke_path(
|
||||||
|
&path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: tiny_skia::Shader::SolidColor(into_color(
|
||||||
|
*border_color,
|
||||||
|
)),
|
||||||
|
anti_alias: true,
|
||||||
|
..tiny_skia::Paint::default()
|
||||||
|
},
|
||||||
|
&tiny_skia::Stroke {
|
||||||
|
width: *border_width,
|
||||||
|
..tiny_skia::Stroke::default()
|
||||||
|
},
|
||||||
|
transform,
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Primitive::Text {
|
||||||
Primitive::Text { .. } => {
|
content,
|
||||||
// TODO
|
bounds,
|
||||||
}
|
color,
|
||||||
Primitive::Image { .. } => {
|
size,
|
||||||
// TODO
|
font,
|
||||||
}
|
horizontal_alignment,
|
||||||
Primitive::Svg { .. } => {
|
vertical_alignment,
|
||||||
// TODO
|
} => {
|
||||||
}
|
self.text_pipeline.draw(
|
||||||
Primitive::Group { primitives } => {
|
content,
|
||||||
for primitive in primitives {
|
(*bounds + translation) * scale_factor,
|
||||||
draw_primitive(
|
*color,
|
||||||
primitive,
|
*size * scale_factor,
|
||||||
|
*font,
|
||||||
|
*horizontal_alignment,
|
||||||
|
*vertical_alignment,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Primitive::Image { .. } => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
Primitive::Svg { .. } => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
Primitive::Group { primitives } => {
|
||||||
|
for primitive in primitives {
|
||||||
|
self.draw_primitive(
|
||||||
|
primitive,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
scale_factor,
|
||||||
|
translation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Primitive::Translate {
|
||||||
|
translation: offset,
|
||||||
|
content,
|
||||||
|
} => {
|
||||||
|
self.draw_primitive(
|
||||||
|
content,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
scale_factor,
|
||||||
|
translation + *offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Primitive::Clip { bounds, content } => {
|
||||||
|
self.draw_primitive(
|
||||||
|
content,
|
||||||
|
pixels,
|
||||||
|
Some(&rectangular_clip_mask(
|
||||||
|
pixels,
|
||||||
|
*bounds * scale_factor,
|
||||||
|
)),
|
||||||
|
scale_factor,
|
||||||
|
translation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Primitive::Cached { cache } => {
|
||||||
|
self.draw_primitive(
|
||||||
|
cache,
|
||||||
pixels,
|
pixels,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
translation,
|
translation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {}
|
||||||
}
|
}
|
||||||
Primitive::Translate {
|
|
||||||
translation: offset,
|
|
||||||
content,
|
|
||||||
} => {
|
|
||||||
draw_primitive(
|
|
||||||
content,
|
|
||||||
pixels,
|
|
||||||
clip_mask,
|
|
||||||
scale_factor,
|
|
||||||
translation + *offset,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Primitive::Clip { bounds, content } => {
|
|
||||||
draw_primitive(
|
|
||||||
content,
|
|
||||||
pixels,
|
|
||||||
Some(&rectangular_clip_mask(pixels, *bounds * scale_factor)),
|
|
||||||
scale_factor,
|
|
||||||
translation,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Primitive::Cached { cache } => {
|
|
||||||
draw_primitive(cache, pixels, clip_mask, scale_factor, translation);
|
|
||||||
}
|
|
||||||
Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +225,7 @@ fn rectangular_clip_mask(
|
||||||
|
|
||||||
impl iced_graphics::Backend for Backend {
|
impl iced_graphics::Backend for Backend {
|
||||||
fn trim_measurements(&mut self) {
|
fn trim_measurements(&mut self) {
|
||||||
// TODO
|
self.text_pipeline.trim_measurement_cache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,30 +244,35 @@ impl backend::Text for Backend {
|
||||||
|
|
||||||
fn measure(
|
fn measure(
|
||||||
&self,
|
&self,
|
||||||
_contents: &str,
|
contents: &str,
|
||||||
_size: f32,
|
size: f32,
|
||||||
_font: Font,
|
font: Font,
|
||||||
_bounds: Size,
|
bounds: Size,
|
||||||
) -> (f32, f32) {
|
) -> (f32, f32) {
|
||||||
// TODO
|
self.text_pipeline.measure(contents, size, font, bounds)
|
||||||
(0.0, 0.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hit_test(
|
fn hit_test(
|
||||||
&self,
|
&self,
|
||||||
_contents: &str,
|
contents: &str,
|
||||||
_size: f32,
|
size: f32,
|
||||||
_font: Font,
|
font: Font,
|
||||||
_bounds: Size,
|
bounds: Size,
|
||||||
_point: iced_native::Point,
|
point: iced_native::Point,
|
||||||
_nearest_only: bool,
|
nearest_only: bool,
|
||||||
) -> Option<text::Hit> {
|
) -> Option<text::Hit> {
|
||||||
// TODO
|
self.text_pipeline.hit_test(
|
||||||
None
|
contents,
|
||||||
|
size,
|
||||||
|
font,
|
||||||
|
bounds,
|
||||||
|
point,
|
||||||
|
nearest_only,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {
|
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||||
// TODO
|
self.text_pipeline.load_font(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ pub mod window;
|
||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
mod text;
|
||||||
|
|
||||||
pub use backend::Backend;
|
pub use backend::Backend;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
|
|
||||||
350
tiny_skia/src/text.rs
Normal file
350
tiny_skia/src/text.rs
Normal file
|
|
@ -0,0 +1,350 @@
|
||||||
|
pub use iced_native::text::Hit;
|
||||||
|
|
||||||
|
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::collections::hash_map;
|
||||||
|
use std::hash::{BuildHasher, Hash, Hasher};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Pipeline {
|
||||||
|
system: Option<System>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ouroboros::self_referencing]
|
||||||
|
struct System {
|
||||||
|
fonts: cosmic_text::FontSystem,
|
||||||
|
|
||||||
|
#[borrows(fonts)]
|
||||||
|
#[not_covariant]
|
||||||
|
measurement_cache: RefCell<Cache<'this>>,
|
||||||
|
|
||||||
|
#[borrows(fonts)]
|
||||||
|
#[not_covariant]
|
||||||
|
render_cache: Cache<'this>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Pipeline {
|
||||||
|
system: Some(
|
||||||
|
SystemBuilder {
|
||||||
|
fonts: cosmic_text::FontSystem::new_with_fonts(
|
||||||
|
[cosmic_text::fontdb::Source::Binary(Arc::new(
|
||||||
|
include_bytes!("../../wgpu/fonts/Iced-Icons.ttf")
|
||||||
|
.as_slice(),
|
||||||
|
))]
|
||||||
|
.into_iter(),
|
||||||
|
),
|
||||||
|
measurement_cache_builder: |_| RefCell::new(Cache::new()),
|
||||||
|
render_cache_builder: |_| Cache::new(),
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(cosmic_text::fontdb::Source::Binary(Arc::new(
|
||||||
|
bytes.into_owned(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
self.system = Some(
|
||||||
|
SystemBuilder {
|
||||||
|
fonts: cosmic_text::FontSystem::new_with_locale_and_db(
|
||||||
|
locale, db,
|
||||||
|
),
|
||||||
|
measurement_cache_builder: |_| RefCell::new(Cache::new()),
|
||||||
|
render_cache_builder: |_| Cache::new(),
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(
|
||||||
|
&mut self,
|
||||||
|
content: &str,
|
||||||
|
bounds: Rectangle,
|
||||||
|
color: Color,
|
||||||
|
size: f32,
|
||||||
|
font: Font,
|
||||||
|
_horizontal_alignment: alignment::Horizontal, // TODO
|
||||||
|
_vertical_alignment: alignment::Vertical, // TODO
|
||||||
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
|
clip_mask: Option<&tiny_skia::ClipMask>,
|
||||||
|
) {
|
||||||
|
self.system.as_mut().unwrap().with_mut(|fields| {
|
||||||
|
let key = Key {
|
||||||
|
bounds: bounds.size(),
|
||||||
|
content,
|
||||||
|
font,
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_, buffer) = fields.render_cache.allocate(&fields.fonts, key);
|
||||||
|
|
||||||
|
let mut swash = cosmic_text::SwashCache::new(&fields.fonts);
|
||||||
|
|
||||||
|
for run in buffer.layout_runs() {
|
||||||
|
for glyph in run.glyphs {
|
||||||
|
// TODO: Outline support
|
||||||
|
if let Some(image) = swash.get_image(glyph.cache_key) {
|
||||||
|
let glyph_size = image.placement.width as usize
|
||||||
|
* image.placement.height as usize;
|
||||||
|
|
||||||
|
if glyph_size == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cache glyph rasterization
|
||||||
|
let mut buffer = vec![0u32; glyph_size];
|
||||||
|
|
||||||
|
match image.content {
|
||||||
|
cosmic_text::SwashContent::Mask => {
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
// TODO: Blend alpha
|
||||||
|
let [r, g, b, _a] = color.into_rgba8();
|
||||||
|
|
||||||
|
for _y in 0..image.placement.height {
|
||||||
|
for _x in 0..image.placement.width {
|
||||||
|
buffer[i] =
|
||||||
|
tiny_skia::ColorU8::from_rgba(
|
||||||
|
b,
|
||||||
|
g,
|
||||||
|
r,
|
||||||
|
image.data[i],
|
||||||
|
)
|
||||||
|
.premultiply()
|
||||||
|
.get();
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cosmic_text::SwashContent::Color => {
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
for _y in 0..image.placement.height {
|
||||||
|
for _x in 0..image.placement.width {
|
||||||
|
// TODO: Blend alpha
|
||||||
|
buffer[i] = (image.data[i + 3] as u32)
|
||||||
|
<< 24
|
||||||
|
| (image.data[i + 2] as u32) << 16
|
||||||
|
| (image.data[i + 1] as u32) << 8
|
||||||
|
| image.data[i] as u32;
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cosmic_text::SwashContent::SubpixelMask => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pixmap = tiny_skia::PixmapRef::from_bytes(
|
||||||
|
bytemuck::cast_slice(&buffer),
|
||||||
|
image.placement.width,
|
||||||
|
image.placement.height,
|
||||||
|
)
|
||||||
|
.expect("Create glyph pixel map");
|
||||||
|
|
||||||
|
pixels.draw_pixmap(
|
||||||
|
bounds.x as i32
|
||||||
|
+ glyph.x_int
|
||||||
|
+ image.placement.left,
|
||||||
|
bounds.y as i32 - glyph.y_int - image.placement.top
|
||||||
|
+ run.line_y as i32,
|
||||||
|
pixmap,
|
||||||
|
&tiny_skia::PixmapPaint::default(),
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_frame(&mut self) {
|
||||||
|
self.system
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.with_render_cache_mut(|cache| cache.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
font,
|
||||||
|
bounds,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
font,
|
||||||
|
bounds,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let cursor = paragraph.hit(point.x, point.y)?;
|
||||||
|
|
||||||
|
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) -> cosmic_text::Family<'static> {
|
||||||
|
match font {
|
||||||
|
Font::Name(name) => cosmic_text::Family::Name(name),
|
||||||
|
Font::SansSerif => cosmic_text::Family::SansSerif,
|
||||||
|
Font::Serif => cosmic_text::Family::Serif,
|
||||||
|
Font::Cursive => cosmic_text::Family::Cursive,
|
||||||
|
Font::Fantasy => cosmic_text::Family::Fantasy,
|
||||||
|
Font::Monospace => cosmic_text::Family::Monospace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cache<'a> {
|
||||||
|
entries: FxHashMap<KeyHash, cosmic_text::Buffer<'a>>,
|
||||||
|
recently_used: FxHashSet<KeyHash>,
|
||||||
|
hasher: HashBuilder,
|
||||||
|
trim_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
type HashBuilder = twox_hash::RandomXxHashBuilder64;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
||||||
|
|
||||||
|
impl<'a> Cache<'a> {
|
||||||
|
const TRIM_INTERVAL: usize = 300;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
entries: FxHashMap::default(),
|
||||||
|
recently_used: FxHashSet::default(),
|
||||||
|
hasher: HashBuilder::default(),
|
||||||
|
trim_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate(
|
||||||
|
&mut self,
|
||||||
|
fonts: &'a cosmic_text::FontSystem,
|
||||||
|
key: Key<'_>,
|
||||||
|
) -> (KeyHash, &mut cosmic_text::Buffer<'a>) {
|
||||||
|
let hash = {
|
||||||
|
let mut hasher = self.hasher.build_hasher();
|
||||||
|
|
||||||
|
key.content.hash(&mut hasher);
|
||||||
|
key.size.to_bits().hash(&mut hasher);
|
||||||
|
key.font.hash(&mut hasher);
|
||||||
|
key.bounds.width.to_bits().hash(&mut hasher);
|
||||||
|
key.bounds.height.to_bits().hash(&mut hasher);
|
||||||
|
|
||||||
|
hasher.finish()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
|
||||||
|
let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2);
|
||||||
|
let mut buffer = cosmic_text::Buffer::new(fonts, metrics);
|
||||||
|
|
||||||
|
buffer.set_size(
|
||||||
|
key.bounds.width,
|
||||||
|
key.bounds.height.max(key.size * 1.2),
|
||||||
|
);
|
||||||
|
buffer.set_text(
|
||||||
|
key.content,
|
||||||
|
cosmic_text::Attrs::new()
|
||||||
|
.family(to_family(key.font))
|
||||||
|
.monospaced(matches!(key.font, Font::Monospace)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = entry.insert(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.recently_used.insert(hash);
|
||||||
|
|
||||||
|
(hash, self.entries.get_mut(&hash).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trim(&mut self) {
|
||||||
|
if self.trim_count >= Self::TRIM_INTERVAL {
|
||||||
|
self.entries
|
||||||
|
.retain(|key, _| self.recently_used.contains(key));
|
||||||
|
|
||||||
|
self.recently_used.clear();
|
||||||
|
|
||||||
|
self.trim_count = 0;
|
||||||
|
} else {
|
||||||
|
self.trim_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Key<'a> {
|
||||||
|
content: &'a str,
|
||||||
|
size: f32,
|
||||||
|
font: Font,
|
||||||
|
bounds: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyHash = u64;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue