Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rsheeter committed Jan 23, 2023
1 parent 5deab6c commit 8ce2df0
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]

members = [
"shape-brokkr",
"fontdrasil",
"fontbe",
"fontir",
Expand Down
26 changes: 26 additions & 0 deletions shape-brokkr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "shape-brokkr"
version = "0.0.1"
edition = "2021"
license = "MIT/Apache-2.0"
description = "A simple representation of a path, meant for use in fonts. Tools to find reusable parts in sets of shapes."
repository = "https://github.com/googlefonts/fontmake-rs"
readme = "README.md"
categories = ["text-processing", "parsing", "graphics"]

[features]

[dependencies]
thiserror = "1.0.37"

kurbo = "0.9.0"
ordered-float = "3.4.0"
serde = {version = "1.0", features = ["derive"] }

log = "0.4"
env_logger = "0.9.0"

[dev-dependencies]
diff = "0.1.12"
ansi_term = "0.12.1"
tempfile = "3.3.0"
8 changes: 8 additions & 0 deletions shape-brokkr/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use kurbo::BezPath;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("{1} {0:?}")]
InvalidPath(BezPath, String),
}
7 changes: 7 additions & 0 deletions shape-brokkr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Library to aid in identifying reusable shapes.
//!
//! Derived from <https://github.com/googlefonts/nanoemoji>'s implementation
//! of shape reuse.
mod error;
pub use error::Error;
pub mod reuse;
87 changes: 87 additions & 0 deletions shape-brokkr/src/reuse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Functions to help find opportunities to reuse shapes.
//!
//! We assume the provided path is valid, in particular if it contains any
//! path elements it starts with a move.
use kurbo::{Affine, BezPath, PathEl, Point, Vec2};

use crate::Error;

// Shift the path such that it begins at 0,0
fn move_to_origin(path: &BezPath) -> Result<BezPath, Error> {
if path.elements().is_empty() {
return Ok(path.clone());
}

let Some(PathEl::MoveTo(first_move)) = path.elements().first() else {
return Err(Error::InvalidPath(path.clone(), "Does not start with MoveTo".to_string()));
};
let shift = Vec2::new(-first_move.x, -first_move.y);
let transform = Affine::translate(shift);
let mut path = path.clone();
path.apply_affine(transform);
Ok(path)
}

/// If we thought of the path as a series of vectors to the endpoints of each successive
/// drawing command what would it look like?
fn vectors(path: &BezPath) -> Vec<Vec2> {
// BezPath deals in absolutes and may not start at 0

let mut vecs = Vec::new();
let mut last_move = Point::ZERO; // path must start with a move
let mut curr_pos = Point::ZERO;

for el in path.elements().iter() {
match el {
PathEl::MoveTo(p) => {
last_move = *p;
curr_pos = *p;
}
PathEl::LineTo(p) | PathEl::QuadTo(_, p) | PathEl::CurveTo(_, _, p) => {
vecs.push(*p - curr_pos);
curr_pos = *p;
}
PathEl::ClosePath => {
vecs.push(last_move - curr_pos);
curr_pos = last_move;
}
}
}

vecs
}

#[cfg(test)]
mod tests {
use kurbo::{BezPath, Vec2};

use crate::reuse::vectors;

use super::move_to_origin;

fn sample_triangle() -> BezPath {
BezPath::from_svg("M5,5 L10,0 L10,10 Z").unwrap()
}

#[test]
fn simple_move_to_origin() {
let original = sample_triangle();
assert_eq!(
"M0 0L5 -5L5 5Z",
move_to_origin(&original).unwrap().to_svg()
);
}

#[test]
fn vecs_ing_triangle() {
assert_eq!(
vec![
Vec2::new(5.0, -5.0),
Vec2::new(0.0, 10.0),
Vec2::new(-5.0, -5.0),
],
vectors(&sample_triangle())
);
}
}

0 comments on commit 8ce2df0

Please sign in to comment.