Skip to content

Commit

Permalink
Merge pull request iced-rs#2204 from iced-rs/vectorial-text-reloaded
Browse files Browse the repository at this point in the history
Vectorial text reloaded
  • Loading branch information
hecrj authored Jan 17, 2024
2 parents 0001a6d + 5d4c55c commit 070abff
Show file tree
Hide file tree
Showing 7 changed files with 512 additions and 127 deletions.
9 changes: 9 additions & 0 deletions examples/vectorial_text/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "vectorial_text"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <[email protected]>"]
edition = "2021"
publish = false

[dependencies]
iced = { path = "../..", features = ["canvas", "debug"] }
175 changes: 175 additions & 0 deletions examples/vectorial_text/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use iced::alignment::{self, Alignment};
use iced::mouse;
use iced::widget::{
canvas, checkbox, column, horizontal_space, row, slider, text,
};
use iced::{
Element, Length, Point, Rectangle, Renderer, Sandbox, Settings, Theme,
Vector,
};

pub fn main() -> iced::Result {
VectorialText::run(Settings {
antialiasing: true,
..Settings::default()
})
}

struct VectorialText {
state: State,
}

#[derive(Debug, Clone, Copy)]
enum Message {
SizeChanged(f32),
AngleChanged(f32),
ScaleChanged(f32),
ToggleJapanese(bool),
}

impl Sandbox for VectorialText {
type Message = Message;

fn new() -> Self {
Self {
state: State::new(),
}
}

fn title(&self) -> String {
String::from("Vectorial Text - Iced")
}

fn update(&mut self, message: Message) {
match message {
Message::SizeChanged(size) => {
self.state.size = size;
}
Message::AngleChanged(angle) => {
self.state.angle = angle;
}
Message::ScaleChanged(scale) => {
self.state.scale = scale;
}
Message::ToggleJapanese(use_japanese) => {
self.state.use_japanese = use_japanese;
}
}

self.state.cache.clear();
}

fn view(&self) -> Element<Message> {
let slider_with_label = |label, range, value, message: fn(f32) -> _| {
column![
row![
text(label),
horizontal_space(Length::Fill),
text(format!("{:.2}", value))
],
slider(range, value, message).step(0.01)
]
.spacing(2)
};

column![
canvas(&self.state).width(Length::Fill).height(Length::Fill),
column![
checkbox(
"Use Japanese",
self.state.use_japanese,
Message::ToggleJapanese
),
row![
slider_with_label(
"Size",
2.0..=80.0,
self.state.size,
Message::SizeChanged,
),
slider_with_label(
"Angle",
0.0..=360.0,
self.state.angle,
Message::AngleChanged,
),
slider_with_label(
"Scale",
1.0..=20.0,
self.state.scale,
Message::ScaleChanged,
),
]
.spacing(20),
]
.align_items(Alignment::Center)
.spacing(10)
]
.spacing(10)
.padding(20)
.into()
}

fn theme(&self) -> Theme {
Theme::Dark
}
}

struct State {
size: f32,
angle: f32,
scale: f32,
use_japanese: bool,
cache: canvas::Cache,
}

impl State {
pub fn new() -> Self {
Self {
size: 40.0,
angle: 0.0,
scale: 1.0,
use_japanese: false,
cache: canvas::Cache::new(),
}
}
}

impl<Message> canvas::Program<Message> for State {
type State = ();

fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
let palette = theme.palette();
let center = bounds.center();

frame.translate(Vector::new(center.x, center.y));
frame.scale(self.scale);
frame.rotate(self.angle * std::f32::consts::PI / 180.0);

frame.fill_text(canvas::Text {
position: Point::new(0.0, 0.0),
color: palette.text,
size: self.size.into(),
content: String::from(if self.use_japanese {
"ベクトルテキスト🎉"
} else {
"Vectorial Text! 🎉"
}),
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
shaping: text::Shaping::Advanced,
..canvas::Text::default()
});
});

vec![geometry]
}
}
135 changes: 134 additions & 1 deletion graphics/src/geometry/text.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 10 additions & 8 deletions tiny_skia/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,6 @@ impl Backend {
path,
paint,
rule,
transform,
}) => {
let bounds = path.bounds();

Expand All @@ -566,17 +565,18 @@ impl Backend {
path,
paint,
*rule,
transform
.post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor),
tiny_skia::Transform::from_translate(
translation.x,
translation.y,
)
.post_scale(scale_factor, scale_factor),
clip_mask,
);
}
Primitive::Custom(primitive::Custom::Stroke {
path,
paint,
stroke,
transform,
}) => {
let bounds = path.bounds();

Expand All @@ -599,9 +599,11 @@ impl Backend {
path,
paint,
stroke,
transform
.post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor),
tiny_skia::Transform::from_translate(
translation.x,
translation.y,
)
.post_scale(scale_factor, scale_factor),
clip_mask,
);
}
Expand Down
Loading

0 comments on commit 070abff

Please sign in to comment.