Graphite/graphene/src/layers/text_layer/to_path.rs

168 lines
5.8 KiB
Rust

use graphene_std::vector::consts::ManipulatorType;
use graphene_std::vector::manipulator_group::ManipulatorGroup;
use graphene_std::vector::manipulator_point::ManipulatorPoint;
use graphene_std::vector::subpath::Subpath;
use glam::DVec2;
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
use ttf_parser::{GlyphId, OutlineBuilder};
struct Builder {
path: Subpath,
pos: DVec2,
offset: DVec2,
ascender: f64,
scale: f64,
}
impl Builder {
fn point(&self, x: f32, y: f32) -> DVec2 {
self.pos + self.offset + DVec2::new(x as f64, self.ascender - y as f64) * self.scale
}
}
impl OutlineBuilder for Builder {
fn move_to(&mut self, x: f32, y: f32) {
let anchor = self.point(x, y);
if self.path.manipulator_groups().last().filter(|el| el.points.iter().any(Option::is_some)).is_some() {
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
}
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
}
fn line_to(&mut self, x: f32, y: f32) {
let anchor = self.point(x, y);
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
}
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
let [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];
self.path.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::OutHandle] = Some(ManipulatorPoint::new(handle, ManipulatorType::OutHandle));
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
let [handle1, handle2, anchor] = [self.point(x1, y1), self.point(x2, y2), self.point(x3, y3)];
self.path.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::OutHandle] = Some(ManipulatorPoint::new(handle1, ManipulatorType::OutHandle));
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
self.path.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::InHandle] = Some(ManipulatorPoint::new(handle2, ManipulatorType::InHandle));
}
fn close(&mut self) {
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
}
}
fn font_properties(buzz_face: &rustybuzz::Face, font_size: f64) -> (f64, f64, UnicodeBuffer) {
let scale = (buzz_face.units_per_em() as f64).recip() * font_size;
let line_height = font_size;
let buffer = UnicodeBuffer::new();
(scale, line_height, buffer)
}
fn push_str(buffer: &mut UnicodeBuffer, word: &str, trailing_space: bool) {
buffer.push_str(word);
if trailing_space {
buffer.push_str(" ");
}
}
fn wrap_word(line_width: Option<f64>, glyph_buffer: &GlyphBuffer, scale: f64, x_pos: f64) -> bool {
if let Some(line_width) = line_width {
let word_length: i32 = glyph_buffer.glyph_positions().iter().map(|pos| pos.x_advance).sum();
let scaled_word_length = word_length as f64 * scale;
if scaled_word_length + x_pos > line_width {
return true;
}
}
false
}
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Subpath {
let buzz_face = match buzz_face {
Some(face) => face,
// Show blank layer if font has not loaded
None => return Subpath::default(),
};
let (scale, line_height, mut buffer) = font_properties(&buzz_face, font_size);
let mut builder = Builder {
path: Subpath::new(),
pos: DVec2::ZERO,
offset: DVec2::ZERO,
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * font_size / scale,
scale,
};
for line in str.split('\n') {
let length = line.split(' ').count();
for (index, word) in line.split(' ').enumerate() {
push_str(&mut buffer, word, index != length - 1);
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
if wrap_word(line_width, &glyph_buffer, scale, builder.pos.x) {
builder.pos = DVec2::new(0., builder.pos.y + line_height);
}
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
if let Some(line_width) = line_width {
if builder.pos.x + (glyph_position.x_advance as f64 * builder.scale) >= line_width {
builder.pos = DVec2::new(0., builder.pos.y + line_height);
}
}
builder.offset = DVec2::new(glyph_position.x_offset as f64, glyph_position.y_offset as f64) * builder.scale;
buzz_face.outline_glyph(GlyphId(glyph_info.glyph_id as u16), &mut builder);
builder.pos += DVec2::new(glyph_position.x_advance as f64, glyph_position.y_advance as f64) * builder.scale;
}
buffer = glyph_buffer.clear();
}
builder.pos = DVec2::new(0., builder.pos.y + line_height);
}
builder.path
}
pub fn bounding_box(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> DVec2 {
let buzz_face = match buzz_face {
Some(face) => face,
// Show blank layer if font has not loaded
None => return DVec2::ZERO,
};
let (scale, line_height, mut buffer) = font_properties(&buzz_face, font_size);
let mut pos = DVec2::ZERO;
let mut bounds = DVec2::ZERO;
for line in str.split('\n') {
let length = line.split(' ').count();
for (index, word) in line.split(' ').enumerate() {
push_str(&mut buffer, word, index != length - 1);
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
if wrap_word(line_width, &glyph_buffer, scale, pos.x) {
pos = DVec2::new(0., pos.y + line_height);
}
for glyph_position in glyph_buffer.glyph_positions() {
if let Some(line_width) = line_width {
if pos.x + (glyph_position.x_advance as f64 * scale) >= line_width {
pos = DVec2::new(0., pos.y + line_height);
}
}
pos += DVec2::new(glyph_position.x_advance as f64, glyph_position.y_advance as f64) * scale;
}
bounds = bounds.max(pos + DVec2::new(0., line_height));
buffer = glyph_buffer.clear();
}
pos = DVec2::new(0., pos.y + line_height);
}
bounds
}