From 4cb53a6e225f9e533126eb03d3cc34be3fd09f1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 17 Jan 2024 14:48:33 +0100 Subject: Implement vectorial text support for `iced_tiny_skia` --- graphics/src/geometry/text.rs | 135 ++++++++++++++++++++++++++++++++++++++- tiny_skia/src/geometry.rs | 99 ++++++++++++++++------------- wgpu/src/geometry.rs | 142 +----------------------------------------- 3 files changed, 191 insertions(+), 185 deletions(-) diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs index 0bf7ec97..d314e85e 100644 --- a/graphics/src/geometry/text.rs +++ b/graphics/src/geometry/text.rs @@ -1,6 +1,8 @@ use crate::core::alignment; use crate::core::text::{LineHeight, Shaping}; -use crate::core::{Color, Font, Pixels, Point}; +use crate::core::{Color, Font, Pixels, Point, Size, Vector}; +use crate::geometry::Path; +use crate::text; /// A bunch of text that can be drawn to a canvas #[derive(Debug, Clone)] @@ -32,6 +34,137 @@ pub struct Text { pub shaping: Shaping, } +impl Text { + /// Computes the [`Path`]s of the [`Text`] and draws them using + /// the given closure. + pub fn draw_with(&self, mut f: impl FnMut(Path, Color)) { + let mut font_system = + text::font_system().write().expect("Write font system"); + + let mut buffer = cosmic_text::BufferLine::new( + &self.content, + cosmic_text::AttrsList::new(text::to_attributes(self.font)), + text::to_shaping(self.shaping), + ); + + let layout = buffer.layout( + font_system.raw(), + self.size.0, + f32::MAX, + cosmic_text::Wrap::None, + ); + + let translation_x = match self.horizontal_alignment { + alignment::Horizontal::Left => self.position.x, + alignment::Horizontal::Center | alignment::Horizontal::Right => { + let mut line_width = 0.0f32; + + for line in layout.iter() { + line_width = line_width.max(line.w); + } + + if self.horizontal_alignment == alignment::Horizontal::Center { + self.position.x - line_width / 2.0 + } else { + self.position.x - line_width + } + } + }; + + let translation_y = { + let line_height = self.line_height.to_absolute(self.size); + + match self.vertical_alignment { + alignment::Vertical::Top => self.position.y, + alignment::Vertical::Center => { + self.position.y - line_height.0 / 2.0 + } + alignment::Vertical::Bottom => self.position.y - line_height.0, + } + }; + + let mut swash_cache = cosmic_text::SwashCache::new(); + + for run in layout.iter() { + for glyph in run.glyphs.iter() { + let physical_glyph = glyph.physical((0.0, 0.0), 1.0); + + let start_x = translation_x + glyph.x + glyph.x_offset; + let start_y = translation_y + glyph.y_offset + self.size.0; + let offset = Vector::new(start_x, start_y); + + if let Some(commands) = swash_cache.get_outline_commands( + font_system.raw(), + physical_glyph.cache_key, + ) { + let glyph = Path::new(|path| { + use cosmic_text::Command; + + for command in commands { + match command { + Command::MoveTo(p) => { + path.move_to( + Point::new(p.x, -p.y) + offset, + ); + } + Command::LineTo(p) => { + path.line_to( + Point::new(p.x, -p.y) + offset, + ); + } + Command::CurveTo(control_a, control_b, to) => { + path.bezier_curve_to( + Point::new(control_a.x, -control_a.y) + + offset, + Point::new(control_b.x, -control_b.y) + + offset, + Point::new(to.x, -to.y) + offset, + ); + } + Command::QuadTo(control, to) => { + path.quadratic_curve_to( + Point::new(control.x, -control.y) + + offset, + Point::new(to.x, -to.y) + offset, + ); + } + Command::Close => { + path.close(); + } + } + } + }); + + f(glyph, self.color); + } else { + // TODO: Raster image support for `Canvas` + let [r, g, b, a] = self.color.into_rgba8(); + + swash_cache.with_pixels( + font_system.raw(), + physical_glyph.cache_key, + cosmic_text::Color::rgba(r, g, b, a), + |x, y, color| { + f( + Path::rectangle( + Point::new(x as f32, y as f32) + offset, + Size::new(1.0, 1.0), + ), + Color::from_rgba8( + color.r(), + color.g(), + color.b(), + color.a() as f32 / 255.0, + ), + ); + }, + ); + } + } + } + } +} + impl Default for Text { fn default() -> Text { Text { diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 4cc04c6e..b00f4676 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -97,54 +97,65 @@ impl Frame { pub fn fill_text(&mut self, text: impl Into) { let text = text.into(); - let (position, size, line_height) = if self.transform.is_identity() { - (text.position, text.size, text.line_height) - } else { - let mut position = [tiny_skia::Point { - x: text.position.x, - y: text.position.y, - }]; - - self.transform.map_points(&mut position); - - let (_, scale_y) = self.transform.get_scale(); - - let size = text.size.0 * scale_y; + let (scale_x, scale_y) = self.transform.get_scale(); + + if self.transform.is_scale_translate() + && scale_x == scale_y + && scale_x > 0.0 + && scale_y > 0.0 + { + let (position, size, line_height) = if self.transform.is_identity() + { + (text.position, text.size, text.line_height) + } else { + let mut position = [tiny_skia::Point { + x: text.position.x, + y: text.position.y, + }]; + + self.transform.map_points(&mut position); + + let size = text.size.0 * scale_y; + + let line_height = match text.line_height { + LineHeight::Absolute(size) => { + LineHeight::Absolute(Pixels(size.0 * scale_y)) + } + LineHeight::Relative(factor) => { + LineHeight::Relative(factor) + } + }; + + ( + Point::new(position[0].x, position[0].y), + size.into(), + line_height, + ) + }; - let line_height = match text.line_height { - LineHeight::Absolute(size) => { - LineHeight::Absolute(Pixels(size.0 * scale_y)) - } - LineHeight::Relative(factor) => LineHeight::Relative(factor), + let bounds = Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, }; - ( - Point::new(position[0].x, position[0].y), - size.into(), + // TODO: Honor layering! + self.primitives.push(Primitive::Text { + content: text.content, + bounds, + color: text.color, + size, line_height, - ) - }; - - let bounds = Rectangle { - x: position.x, - y: position.y, - width: f32::INFINITY, - height: f32::INFINITY, - }; - - // TODO: Use vectorial text instead of primitive - self.primitives.push(Primitive::Text { - content: text.content, - bounds, - color: text.color, - size, - line_height, - font: text.font, - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - shaping: text.shaping, - clip_bounds: Rectangle::with_size(Size::INFINITY), - }); + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + shaping: text.shaping, + clip_bounds: Rectangle::with_size(Size::INFINITY), + }); + } else { + text.draw_with(|path, color| self.fill(&path, color)); + } } pub fn push_transform(&mut self) { diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index a1583a07..4d7f443e 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,7 +1,6 @@ //! Build and draw geometry. -use crate::core::alignment; use crate::core::text::LineHeight; -use crate::core::{Color, Pixels, Point, Rectangle, Size, Vector}; +use crate::core::{Pixels, Point, Rectangle, Size, Vector}; use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ @@ -9,7 +8,6 @@ use crate::graphics::geometry::{ }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::text::{self, cosmic_text}; use crate::primitive::{self, Primitive}; use lyon::geom::euclid; @@ -380,143 +378,7 @@ impl Frame { clip_bounds: Rectangle::with_size(Size::INFINITY), }); } else { - let mut font_system = - text::font_system().write().expect("Write font system"); - - let mut buffer = cosmic_text::BufferLine::new( - &text.content, - cosmic_text::AttrsList::new(text::to_attributes(text.font)), - text::to_shaping(text.shaping), - ); - - let layout = buffer.layout( - font_system.raw(), - text.size.0, - f32::MAX, - cosmic_text::Wrap::None, - ); - - let translation_x = match text.horizontal_alignment { - alignment::Horizontal::Left => text.position.x, - alignment::Horizontal::Center - | alignment::Horizontal::Right => { - let mut line_width = 0.0f32; - - for line in layout.iter() { - line_width = line_width.max(line.w); - } - - if text.horizontal_alignment - == alignment::Horizontal::Center - { - text.position.x - line_width / 2.0 - } else { - text.position.x - line_width - } - } - }; - - let translation_y = { - let line_height = text.line_height.to_absolute(text.size); - - match text.vertical_alignment { - alignment::Vertical::Top => text.position.y, - alignment::Vertical::Center => { - text.position.y - line_height.0 / 2.0 - } - alignment::Vertical::Bottom => { - text.position.y - line_height.0 - } - } - }; - - let mut swash_cache = cosmic_text::SwashCache::new(); - - for run in layout.iter() { - for glyph in run.glyphs.iter() { - let physical_glyph = glyph.physical((0.0, 0.0), 1.0); - - let start_x = translation_x + glyph.x + glyph.x_offset; - let start_y = translation_y + glyph.y_offset + text.size.0; - let offset = Vector::new(start_x, start_y); - - if let Some(commands) = swash_cache.get_outline_commands( - font_system.raw(), - physical_glyph.cache_key, - ) { - let glyph = Path::new(|path| { - use cosmic_text::Command; - - for command in commands { - match command { - Command::MoveTo(p) => { - path.move_to( - Point::new(p.x, -p.y) + offset, - ); - } - Command::LineTo(p) => { - path.line_to( - Point::new(p.x, -p.y) + offset, - ); - } - Command::CurveTo( - control_a, - control_b, - to, - ) => { - path.bezier_curve_to( - Point::new( - control_a.x, - -control_a.y, - ) + offset, - Point::new( - control_b.x, - -control_b.y, - ) + offset, - Point::new(to.x, -to.y) + offset, - ); - } - Command::QuadTo(control, to) => { - path.quadratic_curve_to( - Point::new(control.x, -control.y) - + offset, - Point::new(to.x, -to.y) + offset, - ); - } - Command::Close => { - path.close(); - } - } - } - }); - - self.fill(&glyph, text.color); - } else { - // TODO: Raster image support for `Canvas` - let [r, g, b, a] = text.color.into_rgba8(); - - swash_cache.with_pixels( - font_system.raw(), - physical_glyph.cache_key, - cosmic_text::Color::rgba(r, g, b, a), - |x, y, color| { - self.fill( - &Path::rectangle( - Point::new(x as f32, y as f32) + offset, - Size::new(1.0, 1.0), - ), - Color::from_rgba8( - color.r(), - color.g(), - color.b(), - color.a() as f32 / 255.0, - ), - ); - }, - ) - } - } - } + text.draw_with(|path, color| self.fill(&path, color)); } } -- cgit