~raph/interp-toy

03b54c0609d69ef0e5f65382c7412bb28aecfa78 — Raph Levien 2 years ago 4cfe4c8
Add "inco_fix" script

This does a bunch of affine and lerp stuff to a specific version of
Inconsolata-vf.glyphs, to make suitable placeholders, and so that it's
possible to generate a 700 normal weight from the file.

It's hacky and there was some manual fixup required (mostly declining
the offer to automatically adjust glyph positions), but it's worth
having the logic in a repo, as it's likely that adapting it into a more
general tool in some form will be useful.
M glyphstool/src/font.rs => glyphstool/src/font.rs +7 -0
@@ 85,6 85,7 @@ pub struct GuideLine {
pub struct FontMaster {
    pub id: String,
    pub weight_value: i64,
    pub width_value: Option<i64>,
    #[rest]
    pub other_stuff: HashMap<String, Plist>,
}


@@ 101,6 102,12 @@ impl Font {
    }
}

impl Glyph {
    pub fn get_layer(&self, layer_id: &str) -> Option<&Layer> {
        self.layers.iter().find(|l| l.layer_id == layer_id)
    }
}

impl FromPlist for Node {
    fn from_plist(plist: Plist) -> Self {
        let mut spl = plist.as_str().unwrap().splitn(3, ' ');

A glyphstool/src/inco_fix.rs => glyphstool/src/inco_fix.rs +122 -0
@@ 0,0 1,122 @@
//! A bit of scripting to automate a bunch of Inconsolata vf work.
//!
//! Note that this is a submodule of main, rather than in the lib, as it is not
//! generally useful. But it's very likely that logic in here can be adapted into
//! a more general tool.

use std::collections::HashMap;

use kurbo::Affine;

use glyphstool::{Font, Glyph, Layer, Node, Path};

#[derive(Default)]
struct LayerMap {
    params_to_id: HashMap<(i64, i64), String>,
    id_to_params: HashMap<String, (i64, i64)>,
}

impl LayerMap {
    fn add(&mut self, wght: i64, wdth: i64, id: &str) {
        self.params_to_id.insert((wght, wdth), id.to_string());
        self.id_to_params.insert(id.to_string(), (wght, wdth));
    }

    fn get_id(&self, wght: i64, wdth: i64) -> &str {
        &self.params_to_id[&(wght, wdth)]
    }

    fn get_params(&self, id: &str) -> Option<(i64, i64)> {
        self.id_to_params.get(id).copied()
    }
}

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

fn fix_path(path0: &Path, path1: &Path, t: f64, stretch: f64) -> Path {
    let a = affine_stretch(stretch);
    let nodes = path0
        .nodes
        .iter()
        .zip(path1.nodes.iter())
        .map(|(n0, n1)| Node {
            pt: a * n0.pt.lerp(n1.pt, t).round(),
            node_type: n0.node_type,
        })
        .collect();
    Path {
        closed: path0.closed,
        nodes,
    }
}

fn fix_glyph(glyph: &mut Glyph, layers: &LayerMap) {
    let paths0 = glyph
        .get_layer(layers.get_id(400, 100))
        .unwrap()
        .paths
        .clone();
    // This is actually the 700 from the master, but is stored in 900.
    let paths1 = glyph
        .get_layer(layers.get_id(900, 100))
        .unwrap()
        .paths
        .clone();
    println!("processing glyph {}", glyph.glyphname);
    for layer in &mut glyph.layers {
        if let Some((wght, wdth)) = layers.get_params(&layer.layer_id) {
            let t = (wght as f64 - 400.0) / 300.0;
            let stretch = wdth as f64 / 100.0;
            println!("  touching layer {}, t = {}", layer.layer_id, t);
            if let Some(ref p0) = paths0 {
                let paths = p0
                    .iter()
                    .zip(paths1.as_ref().unwrap().iter())
                    .map(|(p0, p1)| fix_path(p0, p1, t, stretch))
                    .collect();
                layer.paths = Some(paths);
            }
            layer.width = wdth as f64 * 5.0;

            // Possibly TODO: lerp the affine from the masters, rather than
            // doing the processing in-place. Not clear whether it makes much
            // difference.
            let a = affine_stretch(stretch);
            let a_inv = affine_stretch(stretch.recip());

            if let Some(ref mut anchors) = layer.anchors {
                for anchor in anchors {
                    anchor.position = (a * anchor.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;
                    }
                }
            }
        }
    }
}

pub fn inco_fix(font: &mut Font) {
    let mut layers = LayerMap::default();
    for master in &font.font_master {
        let wght = master.weight_value;
        let wdth = master.width_value.unwrap_or(100);
        println!("{}: wght {}, wdth {}", master.id, wght, wdth);
        layers.add(wght, wdth, &master.id);
    }
    let layer_400_narrow_id = layers.get_id(400, 50);
    for glyph in &mut font.glyphs {
        let narrow = glyph.get_layer(layer_400_narrow_id).unwrap();
        if narrow.width != 250. && !glyph.glyphname.starts_with("_corner") {
            fix_glyph(glyph, &layers);
        }
    }
}

M glyphstool/src/lib.rs => glyphstool/src/lib.rs +1 -1
@@ 7,7 7,7 @@ mod plist;
mod stretch;
mod to_plist;

pub use font::{Font, NodeType};
pub use font::{Font, Glyph, Layer, Node, NodeType, Path};
pub use from_plist::FromPlist;
pub use plist::Plist;
pub use stretch::stretch;

M glyphstool/src/main.rs => glyphstool/src/main.rs +16 -2
@@ 1,12 1,14 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use structopt::StructOpt;

mod inco_fix;

#[derive(StructOpt, Debug)]
enum Cmd {
    Merge(MergeCmd)
    Merge(MergeCmd),
    IncoFix(IncoFixCmd),
}

#[derive(StructOpt, Debug)]


@@ 23,6 25,13 @@ struct MergeCmd {
    layer: String,
}

#[derive(StructOpt, Debug)]
struct IncoFixCmd {
    /// The font file to operate on.
    #[structopt(parse(from_os_str))]
    font: PathBuf,
}

use glyphstool::{ops, stretch, Font, FromPlist, Plist, ToPlist};

fn usage() {


@@ 51,6 60,11 @@ fn main() {
            ops::merge(&mut font, &other, &m.layer);
            write_font(&m.font, font);
        }
        Cmd::IncoFix(m) => {
            let mut font = read_font(&m.font);
            inco_fix::inco_fix(&mut font);
            write_font(&m.font, font);
        }
    }
    /*
    let mut filename = None;

M src/interp_pane.rs => src/interp_pane.rs +1 -1
@@ 43,7 43,7 @@ fn reconstruct_path(pts: &[Point], structure: &[Vec<NodeType>]) -> BezPath {
            let node_type = subpath[ix];
            let p = pts[j + ix];
            match node_type {
                NodeType::Line => bez_path.line_to(p),
                NodeType::Line | NodeType::LineSmooth => bez_path.line_to(p),
                NodeType::OffCurve => ctrl_pts.push(p),
                NodeType::Curve | NodeType::CurveSmooth => {
                    bez_path.curve_to(ctrl_pts[0], ctrl_pts[1], p);