~raph/glyphstool

b10f2beac391079e9de53ad00d08fe28323e725f — Raph Levien 2 years ago c093d14 master
Add tool for stretching an entire layer of a font
6 files changed, 172 insertions(+), 8 deletions(-)

M Cargo.lock
M Cargo.toml
M src/font.rs
M src/main.rs
M src/plist.rs
A src/stretch.rs
M Cargo.lock => Cargo.lock +25 -0
@@ 1,13 1,35 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "arrayvec"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "glyphstool"
version = "0.1.0"
dependencies = [
 "kurbo 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "plist_derive 0.1.0",
]

[[package]]
name = "kurbo"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "nodrop"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "plist_derive"
version = "0.1.0"
dependencies = [


@@ 48,6 70,9 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[metadata]
"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba"
"checksum kurbo 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2f0caeb26248a62abf92dea93aad4f8244f54668e2f1060ed9cd9fd1d5545723"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"

M Cargo.toml => Cargo.toml +2 -0
@@ 1,10 1,12 @@
[package]
name = "glyphstool"
version = "0.1.0"
license = "MIT/Apache-2.0"
authors = ["Raph Levien <raph.levien@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
kurbo = "0.5.1"
plist_derive = { path = "plist_derive" }

M src/font.rs => src/font.rs +70 -5
@@ 6,6 6,8 @@

use std::collections::HashMap;

use kurbo::{Affine, Point};

use crate::from_plist::FromPlist;
use crate::plist::Plist;
use crate::to_plist::ToPlist;


@@ 13,12 15,16 @@ use crate::to_plist::ToPlist;
#[derive(Debug, FromPlist, ToPlist)]
pub struct Font {
    pub glyphs: Vec<Glyph>,
    #[rest]
    pub other_stuff: HashMap<String, Plist>,
}

#[derive(Debug, FromPlist, ToPlist)]
pub struct Glyph {
    pub layers: Vec<Layer>,
    pub glyphname: String,
    #[rest]
    pub other_stuff: HashMap<String, Plist>,
}

#[derive(Debug, FromPlist, ToPlist)]


@@ 27,6 33,8 @@ pub struct Layer {
    pub width: f64,
    pub paths: Option<Vec<Path>>,
    pub components: Option<Vec<Component>>,
    pub anchors: Option<Vec<Anchor>>,
    pub guide_lines: Option<Vec<GuideLine>>,
    #[rest]
    pub other_stuff: HashMap<String, Plist>,
}


@@ 39,8 47,7 @@ pub struct Path {

#[derive(Debug)]
pub struct Node {
    pub x: f64,
    pub y: f64,
    pub pt: Point,
    pub node_type: NodeType,
}



@@ 55,7 62,21 @@ pub enum NodeType {
#[derive(Debug, FromPlist, ToPlist)]
pub struct Component {
    pub name: String,
    pub transform: Option<String>,
    pub transform: Option<Affine>,
    #[rest]
    pub other_stuff: HashMap<String, Plist>,
}

#[derive(Debug, FromPlist, ToPlist)]
pub struct Anchor {
    pub name: String,
    pub position: Point,
}

#[derive(Debug, FromPlist, ToPlist)]
pub struct GuideLine {
    pub angle: Option<f64>,
    pub position: Point,
}

impl FromPlist for Node {


@@ 63,8 84,9 @@ impl FromPlist for Node {
        let mut spl = plist.as_str().unwrap().split(' ');
        let x = spl.next().unwrap().parse().unwrap();
        let y = spl.next().unwrap().parse().unwrap();
        let pt = Point::new(x, y);
        let node_type = spl.next().unwrap().parse().unwrap();
        Node { x, y, node_type }
        Node { pt, node_type }
    }
}



@@ 94,6 116,49 @@ impl NodeType {

impl ToPlist for Node {
    fn to_plist(self) -> Plist {
        format!("{} {} {}", self.x, self.y, self.node_type.glyphs_str()).into()
        format!(
            "{} {} {}",
            self.pt.x,
            self.pt.y,
            self.node_type.glyphs_str()
        )
        .into()
    }
}

impl FromPlist for Affine {
    fn from_plist(plist: Plist) -> Self {
        let raw = plist.as_str().unwrap();
        let raw = &raw[1..raw.len() - 1];
        let coords: Vec<f64> = raw.split(", ").map(|c| c.parse().unwrap()).collect();
        Affine::new([
            coords[0], coords[1], coords[2], coords[3], coords[4], coords[5],
        ])
    }
}

impl ToPlist for Affine {
    fn to_plist(self) -> Plist {
        let c = self.as_coeffs();
        format!(
            "{{{}, {}, {}, {}, {}, {}}}",
            c[0], c[1], c[2], c[3], c[4], c[5]
        )
        .into()
    }
}

impl FromPlist for Point {
    fn from_plist(plist: Plist) -> Self {
        let raw = plist.as_str().unwrap();
        let raw = &raw[1..raw.len() - 1];
        let coords: Vec<f64> = raw.split(", ").map(|c| c.parse().unwrap()).collect();
        Point::new(coords[0], coords[1])
    }
}

impl ToPlist for Point {
    fn to_plist(self) -> Plist {
        format!("{{{}, {}}}", self.x, self.y).into()
    }
}

M src/main.rs => src/main.rs +3 -1
@@ 4,6 4,7 @@ use std::fs;
mod font;
mod from_plist;
mod plist;
mod stretch;
mod to_plist;

use font::Font;


@@ 39,8 40,9 @@ fn main() {
        }
    }
    */
    let font: Font = FromPlist::from_plist(plist);
    let mut font: Font = FromPlist::from_plist(plist);
    //println!("{:?}", font);
    stretch::stretch(&mut font, 0.5, "051EFAE4-8BBE-4FBB-A016-4335C3E52F59");
    let plist = font.to_plist();
    println!("{}", plist.to_string());
}

M src/plist.rs => src/plist.rs +18 -2
@@ 53,7 53,7 @@ fn numeric_ok(s: &str) -> bool {
        return false;
    }
    if s.len() > 1 && s.as_bytes()[0] == b'0' {
        return !s.as_bytes().iter().all(|&b| b >= b'0' && b <= b'9')
        return !s.as_bytes().iter().all(|&b| b >= b'0' && b <= b'9');
    }
    true
}


@@ 309,7 309,23 @@ impl<'a> Token<'a> {
                                    buf.push('\r');
                                    cow_start = ix + 1;
                                }
                                _ => return Err(Error::UnknownEscape),
                                _ => {
                                    if b >= b'0' && b <= b'3' && ix + 2 < s.len() {
                                        // octal escape
                                        let b1 = s.as_bytes()[ix + 1];
                                        let b2 = s.as_bytes()[ix + 2];
                                        if b1 >= b'0' && b1 <= b'7' && b2 >= b'0' && b2 <= b'7' {
                                            let oct = (b - b'0') * 64 + (b1 - b'0') * 8 + (b2 - b'0');
                                            buf.push(oct as char);
                                            ix += 2;
                                            cow_start = ix + 1;
                                        } else {
                                            return Err(Error::UnknownEscape);
                                        }
                                    } else {
                                        return Err(Error::UnknownEscape);
                                    }
                                }
                            }
                            ix += 1;
                        }

A src/stretch.rs => src/stretch.rs +54 -0
@@ 0,0 1,54 @@
//! A little logic to apply horizontal stretching to a font.

use kurbo::Affine;

use crate::font::{Font, Glyph, Layer};

fn affine_stretch(stretch: f64) -> Affine {
    Affine::new([stretch, 0., 0., 1., 0., 0.])
}

fn stretch_layer(layer: &mut Layer, stretch: f64) {
    let a = affine_stretch(stretch);
    let a_inv = affine_stretch(stretch.recip());
    layer.width = (layer.width * stretch).round();
    if let Some(ref mut paths) = layer.paths {
        for path in paths {
            for node in &mut path.nodes {
                node.pt = (a * node.pt).round();
            }
        }
    }
    if let Some(ref mut anchors) = layer.anchors {
        for anchor in anchors {
            anchor.position = (a * anchor.position).round();
        }
    }
    if let Some(ref mut guide_lines) = layer.guide_lines {
        for guide_line in guide_lines {
            guide_line.position = (a * guide_line.position).round();
        }
    }
    if let Some(ref mut components) = layer.components {
        for component in components {
            if let Some(ref mut transform) = component.transform {
                // TODO: round the translation component
                *transform = a * *transform * a_inv;
            }
        }
    }
}

fn stretch_glyph(glyph: &mut Glyph, stretch: f64, layer_id: &str) {
    for layer in &mut glyph.layers {
        if layer.layer_id == layer_id {
            stretch_layer(layer, stretch);
        }
    }
}

pub fn stretch(font: &mut Font, stretch: f64, layer_id: &str) {
    for glyph in &mut font.glyphs {
        stretch_glyph(glyph, stretch, layer_id);
    }
}