Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify the implementation of builder for path and that for stroke and fill #70

Merged
merged 9 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Imports:
tibble,
cli
Suggests:
testthat (>= 3.0.0)
testthat (>= 3.0.0),
vdiffr
URL: https://yutannihilation.github.io/string2path/, https://github.com/yutannihilation/string2path
BugReports: https://github.com/yutannihilation/string2path/issues
Encoding: UTF-8
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
* Fix `string2fill()` and `string2stroke()`; when the second argument is a path
to a file, these unintentionally worked as `string2path()`.

* `string2path()` now generates the same outline as `string2fill()` and
`string2stroke()` (#69).

# string2path 0.1.8

* This is a maintenance release to comply with the CRAN repository policy.
Expand Down
140 changes: 99 additions & 41 deletions src/rust/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ use ttf_parser::{
Face, NormalizedCoordinate, RgbaColor,
};

pub struct LyonPathBuilder {
pub trait BuildPath: Build<PathType = Path> + PathBuilder {
// TODO: lyon::path::builder::Transformed is a struct, not a trait. So, this
// method is needed to forward the operation.
fn set_transform(&mut self, transform: lyon::math::Transform);
fn new_builder(tolerance: f32) -> Self;
}

pub struct LyonPathBuilder<T: BuildPath> {
// It's not very elegant to store the glyph ID (not the `glyphId` ttf-parser
// uses, but the glyph count) and path ID in attributes, but it seems the
// attribute is the only thing we can pass to tessellators.
pub builders: Vec<
lyon::path::builder::Transformed<
lyon::path::path::BuilderWithAttributes,
lyon::math::Transform,
>,
>,
pub builders: Vec<T>,
// one layer has only one color
pub layer_color: HashMap<usize, RgbaColor>,

Expand All @@ -47,31 +49,9 @@ pub struct LyonPathBuilder {
pub line_width: f32,
}

impl LyonPathBuilder {
pub fn new(tolerance: f32, line_width: f32) -> Self {
Self {
builders: vec![lyon::path::Path::builder_with_attributes(2)
.transformed(lyon::geom::euclid::Transform2D::identity())],
layer_color: HashMap::new(),
cur_layer: 0,
cur_glyph_id: 0,
cur_path_id: 0,
base_transform: lyon::geom::euclid::Transform2D::identity(),
scale_factor: 1.,
offset_x: 0.,
offset_y: 0.,
tolerance,
line_width,
}
}

impl<T: BuildPath> LyonPathBuilder<T> {
#[inline]
pub fn cur_builder(
&mut self,
) -> &mut lyon::path::builder::Transformed<
lyon::path::path::BuilderWithAttributes,
lyon::math::Transform,
> {
pub fn cur_builder(&mut self) -> &mut T {
&mut self.builders[self.cur_layer]
}

Expand Down Expand Up @@ -154,10 +134,7 @@ impl LyonPathBuilder {
fn push_layer(&mut self) {
self.cur_layer += 1;
if self.builders.len() < self.cur_layer + 1 {
self.builders.push(
lyon::path::Path::builder_with_attributes(2)
.transformed(lyon::geom::euclid::Transform2D::identity()),
);
self.builders.push(T::new_builder(self.tolerance));
}
self.update_transform();
}
Expand All @@ -169,7 +146,88 @@ impl LyonPathBuilder {
// }
}

impl ttf_parser::OutlineBuilder for LyonPathBuilder {
// For path

pub type FlattenedPathBuilder = lyon::path::builder::Transformed<
lyon::path::builder::Flattened<lyon::path::path::BuilderWithAttributes>,
lyon::math::Transform,
>;

impl BuildPath for FlattenedPathBuilder {
fn set_transform(&mut self, transform: lyon::math::Transform) {
self.set_transform(transform);
}

fn new_builder(tolerance: f32) -> Self {
lyon::path::Path::builder_with_attributes(2)
.flattened(tolerance)
.transformed(lyon::geom::euclid::Transform2D::identity())
}
}

pub type LyonPathBuilderForPath = LyonPathBuilder<FlattenedPathBuilder>;

impl LyonPathBuilderForPath {
pub fn new(tolerance: f32, line_width: f32) -> Self {
let builder = FlattenedPathBuilder::new_builder(tolerance);
Self {
builders: vec![builder],
layer_color: HashMap::new(),
cur_layer: 0,
cur_glyph_id: 0,
cur_path_id: 0,
base_transform: lyon::geom::euclid::Transform2D::identity(),
scale_factor: 1.,
offset_x: 0.,
offset_y: 0.,
tolerance,
line_width,
}
}
}

// For stroke and fill

pub type NonFlattenedPathBuilder = lyon::path::builder::Transformed<
lyon::path::path::BuilderWithAttributes,
lyon::math::Transform,
>;

impl BuildPath for NonFlattenedPathBuilder {
fn set_transform(&mut self, transform: lyon::math::Transform) {
self.set_transform(transform);
}

fn new_builder(_tolerance: f32) -> Self {
lyon::path::Path::builder_with_attributes(2)
.transformed(lyon::geom::euclid::Transform2D::identity())
}
}

pub type LyonPathBuilderForStrokeAndFill = LyonPathBuilder<NonFlattenedPathBuilder>;

impl LyonPathBuilderForStrokeAndFill {
pub fn new(tolerance: f32, line_width: f32) -> Self {
let builder = NonFlattenedPathBuilder::new_builder(tolerance);
Self {
builders: vec![builder],
layer_color: HashMap::new(),
cur_layer: 0,
cur_glyph_id: 0,
cur_path_id: 0,
base_transform: lyon::geom::euclid::Transform2D::identity(),
scale_factor: 1.,
offset_x: 0.,
offset_y: 0.,
tolerance,
line_width,
}
}
}

// ttf-parser

impl<T: BuildPath> ttf_parser::OutlineBuilder for LyonPathBuilder<T> {
fn move_to(&mut self, x: f32, y: f32) {
let at = self.point(x, y);
let custom_attributes = &self.ids();
Expand Down Expand Up @@ -205,18 +263,18 @@ impl ttf_parser::OutlineBuilder for LyonPathBuilder {
}
}

pub struct LyonPathBuilderForPaint<'a> {
builder: &'a mut LyonPathBuilder,
pub struct LyonPathBuilderForPaint<'a, T: BuildPath> {
builder: &'a mut LyonPathBuilder<T>,
face: &'a Face<'a>,
}

impl<'a> LyonPathBuilderForPaint<'a> {
pub fn new(builder: &'a mut LyonPathBuilder, face: &'a Face<'a>) -> Self {
impl<'a, T: BuildPath> LyonPathBuilderForPaint<'a, T> {
pub fn new(builder: &'a mut LyonPathBuilder<T>, face: &'a Face<'a>) -> Self {
Self { builder, face }
}
}

impl<'a> Painter<'a> for LyonPathBuilderForPaint<'a> {
impl<'a, T: BuildPath> Painter<'a> for LyonPathBuilderForPaint<'a, T> {
fn outline_glyph(&mut self, glyph_id: ttf_parser::GlyphId) {
self.face.outline_glyph(glyph_id, self.builder);
}
Expand Down
4 changes: 2 additions & 2 deletions src/rust/src/font.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::builder::{LyonPathBuilder, LyonPathBuilderForPaint};
use crate::builder::{BuildPath, LyonPathBuilder, LyonPathBuilderForPaint};

use once_cell::sync::Lazy;

Expand Down Expand Up @@ -43,7 +43,7 @@ impl From<FontLoadingError> for savvy::Error {
}
}

impl LyonPathBuilder {
impl<T: BuildPath> LyonPathBuilder<T> {
pub fn outline(
&mut self,
text: &str,
Expand Down
4 changes: 2 additions & 2 deletions src/rust/src/into_fill_stroke.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{builder::LyonPathBuilder, result::PathTibble};
use crate::{builder::LyonPathBuilderForStrokeAndFill, result::PathTibble};

use lyon::tessellation::*;
use ttf_parser::RgbaColor;
Expand Down Expand Up @@ -38,7 +38,7 @@ impl StrokeVertexConstructor<Vertex> for VertexCtor {
}
}

impl LyonPathBuilder {
impl LyonPathBuilderForStrokeAndFill {
/// Convert the outline paths into fill as triangles.
pub fn into_fill(mut self) -> PathTibble {
let paths = self.build();
Expand Down
115 changes: 30 additions & 85 deletions src/rust/src/into_path.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
use ttf_parser::RgbaColor;

use crate::builder::LyonPathBuilder;
use crate::builder::LyonPathBuilderForPath;
use crate::result::PathTibble;

impl LyonPathBuilder {
/// Extract the outline path to PathTibble.
impl LyonPathBuilderForPath {
pub fn into_path(mut self) -> PathTibble {
let paths = self.build();

let mut x: Vec<f64> = Vec::new();
let mut y: Vec<f64> = Vec::new();
let mut glyph_id: Vec<i32> = Vec::new();
let mut path_id: Vec<i32> = Vec::new();
let mut color: Option<Vec<String>> = if self.layer_color.is_empty() {
let color = if self.layer_color.is_empty() {
None
} else {
Some(Vec::new())
};

let mut result = PathTibble {
x: Vec::new(),
y: Vec::new(),
glyph_id: Vec::new(),
path_id: Vec::new(),
triangle_id: None,
color,
};
for (path, paint_color) in paths {
let paint_color = match paint_color {
Some(RgbaColor {
Expand All @@ -29,88 +30,32 @@ impl LyonPathBuilder {
None => "#00000000".to_string(),
};
for p in path.iter_with_attributes() {
match p {
lyon::path::Event::Begin { at } => {
glyph_id.push(at.1[0] as _);
path_id.push(at.1[1] as _);
x.push(at.0.x as _);
y.push(at.0.y as _);
if let Some(v) = color.as_mut() {
v.push(paint_color.clone())
}
}
lyon::path::Event::Line { from, to } => {
glyph_id.push(from.1[0] as _);
path_id.push(from.1[1] as _);
x.push(to.0.x as _);
y.push(to.0.y as _);
if let Some(v) = color.as_mut() {
v.push(paint_color.clone())
}
}
lyon::path::Event::Quadratic { from, ctrl, to } => {
let seg = lyon::geom::QuadraticBezierSegment {
from: from.0,
ctrl,
to: to.0,
};
// skip the first point as it's already added
for p in seg.flattened(self.tolerance).skip(1) {
glyph_id.push(from.1[0] as _);
path_id.push(from.1[1] as _);
x.push(p.x as _);
y.push(p.y as _);
if let Some(v) = color.as_mut() {
v.push(paint_color.clone())
}
}
}
lyon::path::Event::Cubic {
from,
ctrl1,
ctrl2,
to,
} => {
let seg = lyon::geom::CubicBezierSegment {
from: from.0,
ctrl1,
ctrl2,
to: to.0,
};
// skip the first point as it's already added
for p in seg.flattened(self.tolerance).skip(1) {
glyph_id.push(from.1[0] as _);
path_id.push(from.1[1] as _);
x.push(p.x as _);
y.push(p.y as _);
if let Some(v) = color.as_mut() {
v.push(paint_color.clone())
}
}
}
let point = match p {
lyon::path::Event::Begin { at } => Some(at),
lyon::path::Event::Line { to, .. } => Some(to),
lyon::path::Event::Quadratic { to, .. } => Some(to),
lyon::path::Event::Cubic { to, .. } => Some(to),
// glyph can be "open path," even when `close` is true. In that case, `first` should point to the begin point.
lyon::path::Event::End { last, first, close } => {
if close && last != first {
glyph_id.push(first.1[0] as _);
path_id.push(first.1[1] as _);
x.push(first.0.x as _);
y.push(first.0.y as _);
if let Some(v) = color.as_mut() {
v.push(paint_color.clone())
}
Some(first)
} else {
None
}
}
};

if let Some(point) = point {
result.glyph_id.push(point.1[0] as _);
result.path_id.push(point.1[1] as _);
result.x.push(point.0.x as _);
result.y.push(point.0.y as _);
if let Some(v) = result.color.as_mut() {
v.push(paint_color.clone())
}
}
}
}

PathTibble {
x,
y,
glyph_id,
path_id,
triangle_id: None,
color,
}
result
}
}
Loading