Skip to content

Commit

Permalink
Major new APIs: FromKurbo, IntoKurbo
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlcctrlv committed Jan 6, 2022
1 parent 807f7c0 commit 6aaf880
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
/target
Cargo.lock
tags
4 changes: 2 additions & 2 deletions Cargo.toml
@@ -1,8 +1,8 @@
[package]
name = "glifparser"
version = "1.1.0"
version = "1.2.0"
authors = ["Fredrick Brennan <copypaste@kittens.ph>"]
edition = "2018"
edition = "2021"

[dependencies]
xmltree = { version = "0.10", features = ["attribute-order"] }
Expand Down
5 changes: 4 additions & 1 deletion src/glif/write.rs
Expand Up @@ -87,6 +87,7 @@ pub fn write_ufo_glif_data<PD: PointData>(glif: &Glif<PD>) -> Result<Vec<u8>, Gl
{
Some(outline) => {
for (ci, contour) in outline.into_iter().enumerate() {
let contour_len = contour.len();
// if we find a move point at the start of things we set this to false
let open_contour = contour.first().unwrap().ptype == PointType::Move;
let mut contour_node = xmltree::Element::new("contour");
Expand Down Expand Up @@ -140,7 +141,9 @@ pub fn write_ufo_glif_data<PD: PointData>(glif: &Glif<PD>) -> Result<Vec<u8>, Gl
match ptype {
PointType::Line | PointType::Curve | PointType::Move => {
if let Some(handle_node) = build_ufo_point_from_handle(point.a) {
contour_node.children.push(xmltree::XMLNode::Element(handle_node));
if !(open_contour && pi == contour_len - 1) {
contour_node.children.push(xmltree::XMLNode::Element(handle_node));
}
}
},
PointType::QCurve => {
Expand Down
2 changes: 2 additions & 0 deletions src/outline.rs
@@ -1,5 +1,7 @@
pub mod contour;
pub mod create;
mod kurbo;
pub use self::kurbo::*;
mod refigure;
pub use refigure::*;
pub mod skia;
Expand Down
112 changes: 112 additions & 0 deletions src/outline/kurbo.rs
@@ -0,0 +1,112 @@
use kurbo::{BezPath, PathEl, Shape as _};

use std::collections::VecDeque;

use crate::error::GlifParserError;
use crate::point::{Handle, Point, PointType, PointData, WhichHandle};
use super::{Contour, Outline};

use crate::outline::contour::{PrevNext as _, State as _};
use super::RefigurePointTypes as _;

pub trait FromKurbo<PD: PointData>: Sized {
fn from_kurbo(kpath: &BezPath) -> Outline<PD>;
}

pub trait IntoKurbo: Sized {
fn into_kurbo(self) -> Result<BezPath, GlifParserError> {
Ok(BezPath::from_vec(self.into_kurbo_vec()?))
}
fn into_kurbo_vec(self) -> Result<Vec<PathEl>, GlifParserError>;
}

impl<PD: PointData> IntoKurbo for Outline<PD> {
fn into_kurbo_vec(self) -> Result<Vec<PathEl>, GlifParserError> {
Ok(self.into_iter().map(|c|c.into_kurbo_vec()).filter(|kv|kv.is_ok()).map(Result::unwrap).flatten().collect())
}
}

impl<PD: PointData> IntoKurbo for Contour<PD> {
fn into_kurbo_vec(mut self) -> Result<Vec<PathEl>, GlifParserError> {
let is_closed = self.is_closed();
self.refigure_point_types();
let mut kurbo_vec = vec![];

for (pi, point) in self.iter().enumerate() {
kurbo_vec.push(match point.ptype {
PointType::Move => PathEl::MoveTo(point.as_kpoint()),
PointType::Line => PathEl::LineTo(point.as_kpoint()),
PointType::QCurve => PathEl::QuadTo(point.handle_as_point(WhichHandle::A), point.as_kpoint()),
PointType::Curve => {
match self.contour_prev_next(pi)? {
(_, Some(next)) => PathEl::CurveTo(point.handle_as_point(WhichHandle::A), self[next].handle_as_point(WhichHandle::B), point.as_kpoint()),
(Some(prev), None) => PathEl::CurveTo(self[prev].handle_as_point(WhichHandle::A), point.handle_as_point(WhichHandle::B), point.as_kpoint()),
(None, None) => unreachable!()
}
}
ptype => return Err(GlifParserError::GlifContourHasBadPointType{pi, ptype})
});
}

if is_closed {
kurbo_vec.push(PathEl::ClosePath);
}

Ok(kurbo_vec)
}
}

impl<PD: PointData> FromKurbo<PD> for Outline<PD> {
fn from_kurbo(kpath: &BezPath) -> Outline<PD> {
let ptvec: Vec<_> = kpath.path_elements(f64::NAN).map(|el| { // accuracy not used for BezPath's
match el {
PathEl::MoveTo(kp) => (PointType::Move, kp.x as f32, kp.y as f32, Handle::Colocated, Handle::Colocated),
PathEl::LineTo(kp) => (PointType::Line, kp.x as f32, kp.y as f32, Handle::Colocated, Handle::Colocated),
PathEl::QuadTo(kpa, kp) => (PointType::QCurve, kp.x as f32, kp.y as f32, Handle::At(kpa.x as f32, kpa.y as f32), Handle::Colocated),
PathEl::CurveTo(kpa, kpb, kp) => (PointType::Curve, kp.x as f32, kp.y as f32, Handle::At(kpa.x as f32, kpa.y as f32), Handle::At(kpb.x as f32, kpb.y as f32)),
PathEl::ClosePath => (PointType::Undefined, f32::NAN, f32::NAN, Handle::Colocated, Handle::Colocated)
}
}).chain(vec![(PointType::Undefined, 0., 0., Handle::Colocated, Handle::Colocated)]).collect();

let mut ret = vec![];
let mut now_vec: Vec<Point<PD>> = vec![];
let mut open_closed = vec![];
let mut b_handles = VecDeque::with_capacity(ptvec.len());

for (ptype, x, y, a, next_b) in ptvec {
if ptype != PointType::Undefined {
b_handles.push_back(next_b);
}
match (ptype, now_vec.is_empty()) {
(PointType::Undefined, _) | (PointType::Move, false) => {
let closed = now_vec[0].ptype != PointType::Move;
let now_len = now_vec.len();
for (idx, point) in now_vec.iter_mut().enumerate() {
if idx == 0 && closed {
point.b = b_handles.pop_back().unwrap_or(Handle::Colocated);
} else {
point.b = b_handles.pop_front().unwrap_or(Handle::Colocated);
}
}
if !closed {
now_vec[now_len - 2].a = now_vec[now_len - 1].a;
}
#[cfg(debug_assertions)]
if !b_handles.is_empty() {
log::error!("B handles vec contained {} handles! {:?}", b_handles.len(), &b_handles);
}
b_handles.clear();
ret.push(now_vec);
now_vec = vec![];
open_closed.push(closed);
}
_ => ()
}
if ptype != PointType::Undefined {
now_vec.push(Point::from_x_y_a_b_type((x, y), (a, Handle::Colocated), ptype));
}
}

ret
}
}
2 changes: 1 addition & 1 deletion src/point/conv.rs
@@ -1,4 +1,4 @@
use crate::point::{Point, PointData, Handle, GlifPoint};
use crate::point::{GlifPoint, Handle, Point, PointData};

impl<PD: PointData> From<&Point<PD>> for Handle {
fn from(p: &Point<PD>) -> Handle {
Expand Down
30 changes: 30 additions & 0 deletions tests/kurbo_conv.rs
@@ -0,0 +1,30 @@
#![cfg(feature = "skia")]

use test_log::test;

use glifparser::outline::{FromKurbo as _, IntoKurbo as _, Outline};
use glifparser::outline::skia::*;
use glifparser::Glif;
use glifparser;
use skia_safe::{Path as SkPath, Rect as SkRect};

#[test]
fn test_kurbo_from() {
let mut path = SkPath::circle((0., 0.), 50.0, None);
path.add_rect(SkRect::new(0., 0., 100., 100.), None);
let mut path2 = SkPath::new();
path2.move_to((30., 30.));
path2.cubic_to((-35., 80.), (-150., -50.), (-100., -100.));
path2.move_to((30., 30.));
path2.close();
path.add_path(&path2, (0., 0.), None);
let outline = Outline::<()>::from_skia_path(&path);
eprintln!("{:?}", &outline);
let kpath = outline.into_kurbo().unwrap();
let outline2 = Outline::<()>::from_kurbo(&kpath);
let mut glif = Glif::new();
eprintln!("{:?}", &kpath);
eprintln!("{:?}", &outline2);
glif.outline = Some(outline2);
eprintln!("{}", glifparser::write(&glif).unwrap());
}

0 comments on commit 6aaf880

Please sign in to comment.