~raph/interp-toy

dfc14edd722b05757e070973c84420c49e1bdb3d — Raph Levien 2 years ago 80668d5 master
Add box drawing glyphs

The "inco_syms" command draws the box drawing range.
M glyphstool/src/font.rs => glyphstool/src/font.rs +10 -0
@@ 210,4 210,14 @@ impl Path {
        let pt = pt.into();
        self.nodes.push(Node { pt, node_type });
    }

    /// Rotate left by one, placing the first point at the end. This is because
    /// it's what glyphs seems to expect.
    pub fn rotate_left(&mut self, delta: usize) {
        self.nodes.rotate_left(delta);
    }

    pub fn reverse(&mut self) {
        self.nodes.reverse();
    }
}

M glyphstool/src/inco_fix.rs => glyphstool/src/inco_fix.rs +571 -9
@@ 6,9 6,9 @@

use std::collections::HashMap;

use kurbo::Affine;
use kurbo::{Affine, Point, Rect};

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

#[derive(Default)]
struct LayerMap {


@@ 158,10 158,7 @@ const NUM_PAIRS: &[(&str, &str)] = &[
    ("nine", "ninesuperior"),
];

const ORD_PAIRS: &[(&str, &str)] = &[
    ("a", "ordfeminine"),
    ("o", "ordmasculine"),
];
const ORD_PAIRS: &[(&str, &str)] = &[("a", "ordfeminine"), ("o", "ordmasculine")];

const FRACS: &[(&str, &str, &str)] = &[
    ("one", "two", "onehalf"),


@@ 169,6 166,8 @@ const FRACS: &[(&str, &str, &str)] = &[
    ("three", "four", "threequarters"),
];

const CARONS: &[(&str, &str)] = &[("d", "dcaron")];

fn lerp_layers(glyph: &Glyph, weight: f64, width: f64, a: Affine, layers: &LayerMap) -> Vec<Path> {
    let (wt0, wt1, wtt) = if weight < 400.0 {
        (200, 400, (weight - 200.0) / 200.0)


@@ 257,6 256,16 @@ fn add_fraction_ref(layer: &mut Layer) {
    layer.components = Some(vec![component]);
}

fn add_caron(layer: &mut Layer, wdth: i64) {
    let a = Affine::translate((wdth as f64 * 1.7, 0.0));
    let component = Component {
        name: "caroncomb.alt".to_string(),
        transform: Some(a),
        other_stuff: Default::default(),
    };
    layer.components = Some(vec![component]);
}

pub fn inco_scale(font: &mut Font, subcmd: i32) {
    let layers = get_layer_map(font);



@@ 280,7 289,7 @@ pub fn inco_scale(font: &mut Font, subcmd: i32) {
                        layer.paths = Some(paths);
                    }
                }
                let dst_glyph  = font.get_glyph_mut(dst).expect("dst glyph not found");
                let dst_glyph = font.get_glyph_mut(dst).expect("dst glyph not found");
                glyph.other_stuff = dst_glyph.other_stuff.clone();
                *dst_glyph = glyph;
            }


@@ 303,7 312,7 @@ pub fn inco_scale(font: &mut Font, subcmd: i32) {
                        layer.paths = Some(paths);
                    }
                }
                let dst_glyph  = font.get_glyph_mut(dst).expect("dst glyph not found");
                let dst_glyph = font.get_glyph_mut(dst).expect("dst glyph not found");
                glyph.other_stuff = dst_glyph.other_stuff.clone();
                *dst_glyph = glyph;
            }


@@ 336,7 345,31 @@ pub fn inco_scale(font: &mut Font, subcmd: i32) {
                        add_fraction_ref(layer);
                    }
                }
                let dst_glyph  = font.get_glyph_mut(dst).expect("dst glyph not found");
                let dst_glyph = font.get_glyph_mut(dst).expect("dst glyph not found");
                glyph.other_stuff = dst_glyph.other_stuff.clone();
                *dst_glyph = glyph;
            }
        }
        // ascender caron letters
        3 => {
            for (src, dst) in CARONS {
                println!("{} -> {}", src, dst);
                let src_glyph = font.get_glyph(src).expect("glyph not found");
                let mut glyph = src_glyph.clone();
                glyph.glyphname = dst.to_string();
                for layer in &mut glyph.layers {
                    if let Some((wght, wdth)) = layers.get_params(&layer.layer_id) {
                        let dst_wght = (wght as f64 * 1.05).min(1000.0);
                        let dst_wdth = wdth as f64 * 0.8;
                        let x = (wdth as f64 - dst_wdth * 1.0) * 5.0 * 0.0;
                        let a = Affine::new([1.0, 0.0, 0.0, 1.0, x, 0.0]);
                        let paths = lerp_layers(src_glyph, dst_wght, dst_wdth, a, &layers);
                        layer.paths = Some(paths);
                        add_caron(layer, wdth);
                        layer.anchors = None;
                    }
                }
                let dst_glyph = font.get_glyph_mut(dst).expect("dst glyph not found");
                glyph.other_stuff = dst_glyph.other_stuff.clone();
                *dst_glyph = glyph;
            }


@@ 346,3 379,532 @@ pub fn inco_scale(font: &mut Font, subcmd: i32) {
        }
    }
}

/// Bézier point distance for circular arcs.
const KAPPA: f64 = 4.0 * (std::f64::consts::SQRT_2 - 1.0) / 3.0;

// A way to organize state for box drawing
struct BoxDraw {
    wght: i64,
    wdth: i64,

    region: Region,
}

#[derive(Clone, Copy)]
enum BoxType {
    Empty,
    Light,
    Double,
    Heavy,
}

impl BoxDraw {
    fn new(wght: i64, wdth: i64) -> BoxDraw {
        let region = Default::default();
        BoxDraw { wght, wdth, region }
    }

    // Line width of light line
    fn light(&self) -> f64 {
        let thickness = match self.wght {
            200 => 60.,
            400 => 120.,
            900 => 180.,
            _ => panic!("unexpected weight"),
        };
        let thickness_fudge = match self.wdth {
            50 => 0.6,
            100 => 1.0,
            200 => 1.1,
            _ => panic!("unexpected width"),
        };
        thickness * thickness_fudge
    }

    fn heavy(&self) -> f64 {
        self.light() * 2.0
    }

    // here, x and y are percent of width, y and w in actual units
    fn hline(&mut self, x0: f64, x1: f64, y: f64, w: f64) {
        let sx0 = (self.wdth as f64 * x0 * 0.05).round();
        let sx1 = (self.wdth as f64 * x1 * 0.05).round();
        let y0 = (y - 0.5 * w).round();
        let y1 = (y + 0.5 * w).round();
        self.rect(sx0, y0, sx1, y1);
    }

    // Based on Source Code Pro box drawing logic
    fn dashed_hline(&mut self, step: i64, thickness: f64) {
        let width = self.wdth as f64 * 5.0;
        let step_length = width / (step as f64);
        let gap = step_length / (step as f64);
        let yc = 300.0;
        let y0 = (yc - 0.5 * thickness).round();
        let y1 = (yc + 0.5 * thickness).round();
        for i in 0..step {
            let x0 = i as f64 * step_length + gap / 2.0;
            let x1 = x0 + step_length - gap;
            self.rect(x0, y0, x1, y1);
        }
    }

    fn dashed_vline(&mut self, step: i64, thickness: f64) {
        let height = 1400.0;
        let step_length = height / (step as f64);
        let gap = step_length / (step as f64);
        let xc = self.wdth as f64 * 2.5;
        let x0 = (xc - 0.5 * thickness).round();
        let x1 = (xc + 0.5 * thickness).round();
        for i in 0..step {
            let y0 = -400.0 + i as f64 * step_length + gap / 2.0;
            let y1 = y0 + step_length - gap;
            self.rect(x0, y0, x1, y1);
        }
    }

    // Assume left->right dir. Based on Source Code Pro box drawing logic
    fn diagonal(&self, start: Point, end: Point, width: f64) -> Path {
        let mut path = Path::new(true);
        let diag = (end - start).hypot();
        let angle = ((end.x - start.x) / diag).asin();
        let dx = (width * 0.5 / angle.cos()).round();
        let dy = (width * 0.5 / angle.sin()).round().copysign(end.y - start.y);
        path.add((start.x + dx, start.y), NodeType::Line);
        path.add((end.x, end.y - dy), NodeType::Line);
        path.add(end, NodeType::Line);
        path.add((end.x - dx, end.y), NodeType::Line);
        path.add((start.x, start.y + dy), NodeType::Line);
        path.add(start, NodeType::Line);
        if end.y < start.y {
            path.rotate_left(3);
            path.reverse();
        }
        path
    }

    fn arc(&self, flip_x: bool, flip_y: bool) -> Vec<Path> {
        let h = (self.light() * 0.5).round(); // half-width
        let r = (self.wdth as f64 * 2.5).round();
        let y0 = -400.0;
        let yc = 300.0;
        let x0 = self.wdth as f64 * -0.8;
        let xc = self.wdth as f64 * 2.5;
        let mut path = Path::new(true);
        let mut p = |x: f64, y: f64, nt| {
            let x = if flip_x { 2.0 * xc - x } else { x };
            let y = if flip_y { 2.0 * yc - y } else { y };
            path.add((x.round(), y.round()), nt);
        };
        let (ls, cs) = if flip_x == flip_y {
            (NodeType::LineSmooth, NodeType::CurveSmooth)
        } else {
            (NodeType::CurveSmooth, NodeType::LineSmooth)
        };
        p(xc - h, y0, NodeType::Line);
        p(xc + h, y0, NodeType::Line);
        p(xc + h, yc - r, ls);
        p(xc + h, yc - r + KAPPA * (r + h), NodeType::OffCurve);
        p(xc - r + KAPPA * (r + h), yc + h, NodeType::OffCurve);
        p(xc - r, yc + h, cs);
        p(x0, yc + h, NodeType::Line);
        p(x0, yc - h, NodeType::Line);
        p(xc - r, yc - h, ls);
        p(xc - r + KAPPA * (r - h), yc - h, NodeType::OffCurve);
        p(xc - h, yc - r + KAPPA * (r - h), NodeType::OffCurve);
        p(xc - h, yc - r, cs);
        if flip_x != flip_y {
            path.reverse();
        }
        vec![path]
    }

    // Used for "up" also
    fn dnblock(&mut self, y0: i64, y1: i64) {
        self.quadrant(0, 8, y0, y1)
    }

    fn lrblock(&mut self, x0: i64, x1: i64) {
        self.quadrant(x0, x1, 0, 8)
    }

    fn quadrant(&mut self, x0: i64, x1: i64, y0: i64, y1: i64) {
        let w = (y1 - y0) as f64 * 175.0;
        let y = (y1 + y0) as f64 * 175.0 * 0.5 - 400.0;
        self.hline(x0 as f64 * 12.5, x1 as f64 * 12.5, y, w);
    }

    fn rect(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) {
        self.region = self.region.add(Rect::new(x0, y0, x1, y1).round());
    }

    /// The general function for drawing most boxes
    fn bxd(&mut self, top: BoxType, left: BoxType, right: BoxType, bot: BoxType) {
        use BoxType::*;
        let light = self.light() * 0.5; // half-width!
        let heavy = self.heavy() * 0.5;
        let mut dbly = light;
        if self.wdth == 50 && self.wght == 900 {
            dbly *= 0.8;
        }
        let dblx = dbly;
        let wdth = self.wdth as f64;
        let xc = wdth * 2.5;
        let yc = 300.0;
        let yb = -400.0;
        let yt = 1000.0;
        let xl = -0.8 * wdth;
        let xr = 5.8 * wdth;
        let dblspy = wdth * 1.2;
        let dblspx = dblspy;
        // counterclockwise starting from this arm
        let yoff = |a, b, c, d| match (a, b, c, d) {
            (Double, Double, _, _) => dbly - dblspy,
            (Double, Empty, Empty, Double) => dbly + dblspy,
            (Light, Double, Empty, Empty) => dbly + dblspy,
            (Light, Empty, Empty, Double) => dbly + dblspy,
            (Light, Double, Empty, Double) => dbly - dblspy,
            (Light, _, _, _) => light,
            (Heavy, Heavy, _, _) => heavy,
            (Heavy, _, _, Heavy) => heavy,
            (Heavy, _, _, _) => light,
            _ => 0.0,
        };
        let xoff = |a, b, c, d| match (a, b, c, d) {
            (Double, Double, _, _) => dblx - dblspx,
            (Double, Empty, Empty, Double) => dblx + dblspx,
            (Light, Double, Empty, Empty) => dblx + dblspx,
            (Light, Empty, Empty, Double) => dblx + dblspx,
            (Light, Double, Empty, Double) => dblx - dblspx,
            (Light, _, _, _) => light,
            (Heavy, Heavy, _, _) => heavy,
            (Heavy, _, _, Heavy) => heavy,
            (Heavy, _, _, _) => light,
            _ => 0.0,
        };
        match top {
            Empty => (),
            Light => {
                let y = yc - yoff(top, left, bot, right);
                self.rect(xc - light, y, xc + light, yt);
            }
            Heavy => {
                let y = yc - yoff(top, left, bot, right);
                self.rect(xc - heavy, y, xc + heavy, yt);
            }
            Double => {
                let y = yc - yoff(top, left, bot, right);
                self.rect(xc - dblx - dblspx, y, xc + dblx - dblspx, yt);
                let y = yc - yoff(top, right, bot, left);
                self.rect(xc - dblx + dblspx, y, xc + dblx + dblspx, yt);
            }
        }
        match left {
            Empty => (),
            Light => {
                let x = xc + xoff(left, bot, right, top);
                self.rect(xl, yc - light, x, yc + light);
            }
            Heavy => {
                let x = xc + xoff(left, bot, right, top);
                self.rect(xl, yc - heavy, x, yc + heavy);
            }
            Double => {
                let x = xc + xoff(left, bot, right, top);
                self.rect(xl, yc - dbly - dblspy, x, yc + dbly - dblspy);
                let x = xc + xoff(left, top, right, bot);
                self.rect(xl, yc - dbly + dblspy, x, yc + dbly + dblspy);
            }
        }
        match right {
            Empty => (),
            Light => {
                let x = xc - xoff(right, top, left, bot);
                self.rect(x, yc - light, xr, yc + light);
            }
            Heavy => {
                let x = xc - xoff(right, top, left, bot);
                self.rect(x, yc - heavy, xr, yc + heavy);
            }
            Double => {
                let x = xc - xoff(right, top, left, bot);
                self.rect(x, yc - dbly + dblspy, xr, yc + dbly + dblspy);
                let x = xc - xoff(right, bot, left, top);
                self.rect(x, yc - dbly - dblspy, xr, yc + dbly - dblspy);
            }
        }
        match bot {
            Empty => (),
            Light => {
                let y = yc + yoff(bot, right, top, left);
                self.rect(xc - light, yb, xc + light, y);
            }
            Heavy => {
                let y = yc + yoff(bot, right, top, left);
                self.rect(xc - heavy, yb, xc + heavy, y);
            }
            Double => {
                let y = yc + yoff(bot, right, top, left);
                self.rect(xc - dblx + dblspx, yb, xc + dblx + dblspx, y);
                let y = yc + yoff(bot, left, top, right);
                self.rect(xc - dblx - dblspx, yb, xc + dblx - dblspx, y);
            }
        }
    }

    fn shade(&mut self, w200: f64, w400: f64, w900: f64) {
        let unit = match self.wght {
            200 => w200,
            400 => w400,
            900 => w900,
            _ => panic!("unexpected weight"),
        };
        let half = 0.5 * unit; // TODO: adjust based on weight
        let halfx = half * (self.wdth as f64) / 100.0;
        for j in 0..14 {
            for i in 0..3 {
                let xc = ((4 * i + 2 * (j & 1) + 1) * self.wdth) as f64 * 5.0 / 12.0;
                let yc = j as f64 * 100.0 - 350.0;
                self.rect(xc - halfx, yc - half, xc + halfx, yc + half);
            }
        }
    }

    fn draw(&mut self, glyphname: &str) -> Option<Vec<Path>> {
        use BoxType::*;

        match glyphname {
            "dneighthblock" => self.dnblock(0, 1),
            "dnquarterblock" => self.dnblock(0, 2),
            "dnthreeeighthsblock" => self.dnblock(0, 3),
            "dnhalfblock" => self.dnblock(0, 4),
            "dnfiveeighthsblock" => self.dnblock(0, 5),
            "dnthreequartersblock" => self.dnblock(0, 6),
            "dnseveneighthsblock" => self.dnblock(0, 7),
            "fullblock" => self.dnblock(0, 8),
            "uphalfblock" => self.dnblock(4, 8),
            "upeighthblock" => self.dnblock(7, 8),
            "lefteighthblock" => self.lrblock(0, 1),
            "leftquarterblock" => self.lrblock(0, 2),
            "leftthreeeighthsblock" => self.lrblock(0, 3),
            "lefthalfblock" => self.lrblock(0, 4),
            "leftfiveeighthsblock" => self.lrblock(0, 5),
            "leftthreequartersblock" => self.lrblock(0, 6),
            "leftseveneighthsblock" => self.lrblock(0, 7),
            "righthalfblock" => self.lrblock(4, 8),
            "righteighthblock" => self.lrblock(7, 8),
            "dnleftquadrant" => self.quadrant(0, 4, 0, 4),
            "dnrightquadrant" => self.quadrant(4, 8, 0, 4),
            "upleftquadrant" => self.quadrant(0, 4, 4, 8),
            "uprightquadrant" => self.quadrant(4, 8, 4, 8),
            "upleftdnrightquadrant" => {
                self.quadrant(0, 4, 4, 8);
                self.quadrant(4, 8, 0, 4);
            }
            "uprightdnleftquadrant" => {
                self.quadrant(4, 8, 4, 8);
                self.quadrant(0, 4, 0, 4);
            }
            "upleftdnleftdnrightquadrant" => {
                self.quadrant(0, 4, 4, 8);
                self.quadrant(0, 4, 0, 4);
                self.quadrant(4, 8, 0, 4);
            }
            "upleftuprightdnleftquadrant" => {
                self.quadrant(0, 4, 4, 8);
                self.quadrant(4, 8, 4, 8);
                self.quadrant(0, 4, 0, 4);
            }
            "upleftuprightdnrightquadrant" => {
                self.quadrant(0, 4, 4, 8);
                self.quadrant(4, 8, 4, 8);
                self.quadrant(4, 8, 0, 4);
            }
            "uprightdnleftdnrightquadrant" => {
                self.quadrant(4, 8, 4, 8);
                self.quadrant(0, 4, 0, 4);
                self.quadrant(4, 8, 0, 4);
            }
            // TODO: three quadrant pieces (L shapes)
            "lightdnbxd" => self.bxd(Empty, Empty, Empty, Light),
            "lightdnhorzbxd" => self.bxd(Empty, Light, Light, Light),
            "lightdnleftbxd" => self.bxd(Empty, Light, Empty, Light),
            "lightdnrightbxd" => self.bxd(Empty, Empty, Light, Light),
            "lightvertbxd" => self.bxd(Light, Empty, Empty, Light),
            "lighthorzbxd" => self.bxd(Empty, Light, Light, Empty),
            "lightleftbxd" => self.bxd(Empty, Light, Empty, Empty),
            "lightrightbxd" => self.bxd(Empty, Empty, Light, Empty),
            "lightupbxd" => self.bxd(Light, Empty, Empty, Empty),
            "lightuphorzbxd" => self.bxd(Light, Light, Light, Empty),
            "lightupleftbxd" => self.bxd(Light, Light, Empty, Empty),
            "lightuprightbxd" => self.bxd(Light, Empty, Light, Empty),
            "lightverthorzbxd" => self.bxd(Light, Light, Light, Light),
            "lightvertleftbxd" => self.bxd(Light, Light, Empty, Light),
            "lightvertrightbxd" => self.bxd(Light, Empty, Light, Light),

            "heavydnbxd" => self.bxd(Empty, Empty, Empty, Heavy),
            "heavydnhorzbxd" => self.bxd(Empty, Heavy, Heavy, Heavy),
            "heavydnleftbxd" => self.bxd(Empty, Heavy, Empty, Heavy),
            "heavydnrightbxd" => self.bxd(Empty, Empty, Heavy, Heavy),
            "heavyvertbxd" => self.bxd(Heavy, Empty, Empty, Heavy),
            "heavyhorzbxd" => self.bxd(Empty, Heavy, Heavy, Empty),
            "heavyleftbxd" => self.bxd(Empty, Heavy, Empty, Empty),
            "heavyrightbxd" => self.bxd(Empty, Empty, Heavy, Empty),
            "heavyupbxd" => self.bxd(Heavy, Empty, Empty, Empty),
            "heavyuphorzbxd" => self.bxd(Heavy, Heavy, Heavy, Empty),
            "heavyupleftbxd" => self.bxd(Heavy, Heavy, Empty, Empty),
            "heavyuprightbxd" => self.bxd(Heavy, Empty, Heavy, Empty),
            "heavyverthorzbxd" => self.bxd(Heavy, Heavy, Heavy, Heavy),
            "heavyvertleftbxd" => self.bxd(Heavy, Heavy, Empty, Heavy),
            "heavyvertrightbxd" => self.bxd(Heavy, Empty, Heavy, Heavy),

            "dbldnhorzbxd" => self.bxd(Empty, Double, Double, Double),
            "dbldnleftbxd" => self.bxd(Empty, Double, Empty, Double),
            "dbldnrightbxd" => self.bxd(Empty, Empty, Double, Double),
            "dblvertbxd" => self.bxd(Double, Empty, Empty, Double),
            "dblhorzbxd" => self.bxd(Empty, Double, Double, Empty),
            "dbluphorzbxd" => self.bxd(Double, Double, Double, Empty),
            "dblupleftbxd" => self.bxd(Double, Double, Empty, Empty),
            "dbluprightbxd" => self.bxd(Double, Empty, Double, Empty),
            "dblverthorzbxd" => self.bxd(Double, Double, Double, Double),
            "dblvertleftbxd" => self.bxd(Double, Double, Empty, Double),
            "dblvertrightbxd" => self.bxd(Double, Empty, Double, Double),

            "dndblhorzsngbxd" => self.bxd(Empty, Light, Light, Double),
            "dndblleftsngbxd" => self.bxd(Empty, Light, Empty, Double),
            "dndblrightsngbxd" => self.bxd(Empty, Empty, Light, Double),
            "dnsnghorzdblbxd" => self.bxd(Empty, Double, Double, Light),
            "dnsngleftdblbxd" => self.bxd(Empty, Double, Empty, Light),
            "dnsngrightdblbxd" => self.bxd(Empty, Empty, Double, Light),
            "updblhorzsngbxd" => self.bxd(Double, Light, Light, Empty),
            "updblleftsngbxd" => self.bxd(Double, Light, Empty, Empty),
            "updblrightsngbxd" => self.bxd(Double, Empty, Light, Empty),
            "upsnghorzdblbxd" => self.bxd(Light, Double, Double, Empty),
            "upsngleftdblbxd" => self.bxd(Light, Double, Empty, Empty),
            "upsngrightdblbxd" => self.bxd(Light, Empty, Double, Empty),
            "vertdblhorzsngbxd" => self.bxd(Double, Light, Light, Double),
            "vertdblleftsngbxd" => self.bxd(Double, Light, Empty, Double),
            "vertdblrightsngbxd" => self.bxd(Double, Empty, Light, Double),
            "vertsnghorzdblbxd" => self.bxd(Light, Double, Double, Light),
            "vertsngleftdblbxd" => self.bxd(Light, Double, Empty, Light),
            "vertsngrightdblbxd" => self.bxd(Light, Empty, Double, Light),

            "dnheavyhorzlightbxd" => self.bxd(Empty, Light, Light, Heavy),
            "dnheavyleftlightbxd" => self.bxd(Empty, Light, Empty, Heavy),
            "dnheavyleftuplightbxd" => self.bxd(Light, Light, Empty, Heavy),
            "dnheavyrightlightbxd" => self.bxd(Empty, Empty, Light, Heavy),
            "dnheavyrightuplightbxd" => self.bxd(Light, Empty, Light, Heavy),
            "dnheavyuphorzlightbxd" => self.bxd(Light, Light, Light, Heavy),
            "dnlighthorzheavybxd" => self.bxd(Empty, Heavy, Heavy, Light),
            "dnlightleftheavybxd" => self.bxd(Empty, Heavy, Empty, Light),
            "dnlightrightheavybxd" => self.bxd(Empty, Empty, Heavy, Light),
            "dnlightleftupheavybxd" => self.bxd(Heavy, Heavy, Empty, Light),
            "dnlightrightupheavybxd" => self.bxd(Heavy, Empty, Heavy, Light),
            "dnlightuphorzheavybxd" => self.bxd(Heavy, Heavy, Heavy, Light),
            "heavyleftlightrightbxd" => self.bxd(Empty, Heavy, Light, Empty),
            "heavyuplightdnbxd" => self.bxd(Heavy, Empty, Empty, Light),
            "leftdnheavyrightuplightbxd" => self.bxd(Light, Heavy, Light, Heavy),
            "leftheavyrightdnlightbxd" => self.bxd(Empty, Heavy, Light, Light),
            "leftheavyrightuplightbxd" => self.bxd(Light, Heavy, Light, Empty),
            "leftheavyrightvertlightbxd" => self.bxd(Light, Heavy, Light, Light),
            "leftlightrightdnheavybxd" => self.bxd(Empty, Light, Heavy, Heavy),
            "leftlightrightupheavybxd" => self.bxd(Heavy, Light, Heavy, Empty),
            "leftlightrightvertheavybxd" => self.bxd(Heavy, Light, Heavy, Heavy),
            "leftupheavyrightdnlightbxd" => self.bxd(Heavy, Heavy, Light, Light),
            "lightleftheavyrightbxd" => self.bxd(Empty, Light, Heavy, Empty),
            "lightupheavydnbxd" => self.bxd(Light, Empty, Empty, Heavy),
            "rightdnheavyleftuplightbxd" => self.bxd(Light, Light, Heavy, Heavy),
            "rightheavyleftdnlightbxd" => self.bxd(Empty, Light, Heavy, Light),
            "rightheavyleftuplightbxd" => self.bxd(Light, Light, Heavy, Empty),
            "rightheavyleftvertlightbxd" => self.bxd(Light, Light, Heavy, Light),
            "rightlightleftdnheavybxd" => self.bxd(Empty, Heavy, Light, Heavy),
            "rightlightleftupheavybxd" => self.bxd(Heavy, Heavy, Light, Empty),
            "rightlightleftvertheavybxd" => self.bxd(Heavy, Heavy, Light, Heavy),
            "rightupheavyleftdnlightbxd" => self.bxd(Heavy, Light, Heavy, Light),
            "upheavydnhorzlightbxd" => self.bxd(Heavy, Light, Light, Light),
            "upheavyhorzlightbxd" => self.bxd(Heavy, Light, Light, Empty),
            "upheavyleftdnlightbxd" => self.bxd(Heavy, Light, Empty, Light),
            "upheavyleftlightbxd" => self.bxd(Heavy, Light, Empty, Empty),
            "upheavyrightdnlightbxd" => self.bxd(Heavy, Empty, Light, Light),
            "upheavyrightlightbxd" => self.bxd(Heavy, Empty, Light, Empty),
            "uplightdnhorzheavybxd" => self.bxd(Light, Heavy, Heavy, Heavy),
            "uplighthorzheavybxd" => self.bxd(Light, Heavy, Heavy, Empty),
            "uplightleftdnheavybxd" => self.bxd(Light, Heavy, Empty, Heavy),
            "uplightleftheavybxd" => self.bxd(Light, Heavy, Empty, Empty),
            "uplightrightdnheavybxd" => self.bxd(Light, Empty, Heavy, Heavy),
            "uplightrightheavybxd" => self.bxd(Light, Empty, Heavy, Empty),
            "vertheavyhorzlightbxd" => self.bxd(Heavy, Light, Light, Heavy),
            "vertheavyleftlightbxd" => self.bxd(Heavy, Light, Empty, Heavy),
            "vertheavyrightlightbxd" => self.bxd(Heavy, Empty, Light, Heavy),
            "vertlighthorzheavybxd" => self.bxd(Light, Heavy, Heavy, Light),
            "vertlightleftheavybxd" => self.bxd(Light, Heavy, Empty, Light),
            "vertlightrightheavybxd" => self.bxd(Light, Empty, Heavy, Light),

            "lightshade" => self.shade(40.0, 50.0, 70.0),
            "mediumshade" => self.shade(50.0, 80.0, 90.0),
            // Maybe TODO: clip the dark one to the glyph box
            "darkshade" => self.shade(110.0, 120.0, 130.0),

            "heavydbldashhorzbxd" => self.dashed_hline(2, self.heavy()),
            "heavytrpldashhorzbxd" => self.dashed_hline(3, self.heavy()),
            "heavyquaddashhorzbxd" => self.dashed_hline(4, self.heavy()),
            "lightdbldashhorzbxd" => self.dashed_hline(2, self.light()),
            "lighttrpldashhorzbxd" => self.dashed_hline(3, self.light()),
            "lightquaddashhorzbxd" => self.dashed_hline(4, self.light()),
            "heavydbldashvertbxd" => self.dashed_vline(2, self.heavy()),
            "heavytrpldashvertbxd" => self.dashed_vline(3, self.heavy()),
            "heavyquaddashvertbxd" => self.dashed_vline(4, self.heavy()),
            "lightdbldashvertbxd" => self.dashed_vline(2, self.light()),
            "lighttrpldashvertbxd" => self.dashed_vline(3, self.light()),
            "lightquaddashvertbxd" => self.dashed_vline(4, self.light()),

            "lightdiaguprightdnleftbxd" => {
                let start = Point::new(0.0, -300.0);
                let end = Point::new(self.wdth as f64 * 5.0, 900.0);
                return Some(vec![self.diagonal(start, end, self.light())]);
            }
            "lightdiagupleftdnrightbxd" => {
                let start = Point::new(0.0, 900.0);
                let end = Point::new(self.wdth as f64 * 5.0, -300.0);
                return Some(vec![self.diagonal(start, end, self.light())]);
            }
            "lightdiagcrossbxd" => {
                // Note: it would probably be more efficient to use components
                let start = Point::new(0.0, -300.0);
                let end = Point::new(self.wdth as f64 * 5.0, 900.0);
                let path1 = self.diagonal(start, end, self.light());
                let start = Point::new(0.0, 900.0);
                let end = Point::new(self.wdth as f64 * 5.0, -300.0);
                let path2 = self.diagonal(start, end, self.light());
                return Some(vec![path1, path2]);
            }
            "lightarcdnleftbxd" => return Some(self.arc(false, false)),
            "lightarcdnrightbxd" => return Some(self.arc(true, false)),
            "lightarcupleftbxd" => return Some(self.arc(false, true)),
            "lightarcuprightbxd" => return Some(self.arc(true, true)),
            _ => return None,
        }
        Some(self.region.to_paths())
    }
}

/// Create symbols, mostly box-drawing.
///
/// Note that it should be practical to adapt this into a fairly general tool.
pub fn inco_syms(font: &mut Font) {
    let layers = get_layer_map(font);

    for glyph in &mut font.glyphs {
        for layer in &mut glyph.layers {
            if let Some((wght, wdth)) = layers.get_params(&layer.layer_id) {
                let mut box_draw = BoxDraw::new(wght, wdth);
                if let Some(paths) = box_draw.draw(&glyph.glyphname) {
                    layer.paths = Some(paths);
                } else {
                    break;
                }
            }
        }
    }
}

M glyphstool/src/lib.rs => glyphstool/src/lib.rs +2 -0
@@ 4,11 4,13 @@ mod font;
mod from_plist;
pub mod ops;
mod plist;
mod region;
mod stretch;
mod to_plist;

pub use font::{Component, Font, Glyph, Layer, Node, NodeType, Path};
pub use from_plist::FromPlist;
pub use plist::Plist;
pub use region::Region;
pub use stretch::stretch;
pub use to_plist::ToPlist;

M glyphstool/src/main.rs => glyphstool/src/main.rs +14 -6
@@ 10,6 10,7 @@ enum Cmd {
    Merge(MergeCmd),
    IncoFix(IncoFixCmd),
    IncoScale(IncoScaleCmd),
    IncoSyms(IncoSymsCmd),
}

#[derive(StructOpt, Debug)]


@@ 31,7 32,6 @@ struct IncoFixCmd {
    /// The font file to operate on.
    #[structopt(parse(from_os_str))]
    font: PathBuf,

}

#[derive(StructOpt, Debug)]


@@ 46,12 46,15 @@ struct IncoScaleCmd {
    subcmd: i32,
}

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

fn usage() {
    eprintln!("usage: glyphstool font.glyphs");
#[derive(StructOpt, Debug)]
struct IncoSymsCmd {
    /// The font file to operate on.
    #[structopt(parse(from_os_str))]
    font: PathBuf,
}

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

fn read_font(path: &Path) -> Font {
    let contents = fs::read_to_string(path).expect("error reading font file");
    let plist = Plist::parse(&contents).expect("error parsing font file");


@@ 60,7 63,7 @@ fn read_font(path: &Path) -> Font {

fn write_font(path: &Path, font: Font) {
    let plist = font.to_plist();
    fs::write(path, &plist.to_string());
    fs::write(path, &plist.to_string()).unwrap();
}

fn main() {


@@ 84,6 87,11 @@ fn main() {
            inco_fix::inco_scale(&mut font, m.subcmd);
            write_font(&m.font, font);
        }
        Cmd::IncoSyms(m) => {
            let mut font = read_font(&m.font);
            inco_fix::inco_syms(&mut font);
            write_font(&m.font, font);
        }
    }
    /*
    let mut filename = None;

A glyphstool/src/region.rs => glyphstool/src/region.rs +312 -0
@@ 0,0 1,312 @@
//! A data structure representing the union of rectangles.

use std::collections::BTreeSet;

use kurbo::{Point, Rect};

use crate::font::{NodeType, Path};

#[derive(Default, Debug)]
pub struct Region {
    slices: Vec<Slice>,
}

#[derive(Clone, PartialEq, Debug)]
struct Slice {
    y0: f64,
    y1: f64,
    intervals: Vec<Interval>,
}

#[derive(Clone, Copy, PartialEq, Debug)]
struct Interval {
    x0: f64,
    x1: f64,
}

impl From<(f64, f64)> for Interval {
    fn from(xs: (f64, f64)) -> Interval {
        Interval { x0: xs.0, x1: xs.1 }
    }
}

impl Slice {
    fn from_rect(rect: Rect) -> Slice {
        Slice {
            y0: rect.y0,
            y1: rect.y1,
            intervals: vec![(rect.x0, rect.x1).into()],
        }
    }

    // Trims the slice to the height of the rect, unions the rect into the intervals.
    fn add_rect(&self, rect: impl Into<Rect>) -> Slice {
        let rect = rect.into();
        let mut iv = Interval::from((rect.x0, rect.x1));
        let mut intervals = Vec::new();
        let mut i = 0;
        while i < self.intervals.len() && self.intervals[i].x1 < iv.x0 {
            intervals.push(self.intervals[i]);
            i += 1;
        }
        while i < self.intervals.len() && self.intervals[i].x0 <= iv.x1 {
            iv.x0 = iv.x0.min(self.intervals[i].x0);
            iv.x1 = iv.x1.max(self.intervals[i].x1);
            i += 1;
        }
        intervals.push(iv);
        intervals.extend_from_slice(&self.intervals[i..]);

        Slice {
            y0: rect.y0,
            y1: rect.y1,
            intervals,
        }
    }

    fn slice(&self, y0: f64, y1: f64) -> Slice {
        Slice {
            y0,
            y1,
            intervals: self.intervals.clone(),
        }
    }
}

impl Region {
    fn push(&mut self, slice: Slice) {
        if let Some(last) = self.slices.last_mut() {
            if last.y1 == slice.y0 && last.intervals == slice.intervals {
                last.y1 = slice.y1;
                return;
            }
        }
        self.slices.push(slice);
    }

    pub fn add(&self, rect: impl Into<Rect>) -> Region {
        let rect = rect.into();
        let mut result = Region::default();
        let mut i = 0;
        while i < self.slices.len() && self.slices[i].y1 <= rect.y0 {
            result.push(self.slices[i].clone());
            i += 1;
        }
        if i < self.slices.len() && self.slices[i].y0 < rect.y0 {
            result.push(self.slices[i].slice(self.slices[i].y0, rect.y0));
        }
        let mut y = rect.y0;
        while i < self.slices.len() && self.slices[i].y0 < rect.y1 {
            if self.slices[i].y0 > y {
                let trim_rect = Rect::new(rect.x0, y, rect.x1, self.slices[i].y0);
                result.push(Slice::from_rect(trim_rect));
                y = self.slices[i].y0;
            }
            let y1 = self.slices[i].y1.min(rect.y1);
            let trim_rect = Rect::new(rect.x0, y, rect.x1, y1);
            result.push(self.slices[i].add_rect(trim_rect));
            y = y1;
            if y < self.slices[i].y1 {
                result.push(self.slices[i].slice(y, self.slices[i].y1));
            }
            i += 1;
        }
        if y < rect.y1 {
            let trim_rect = Rect::new(rect.x0, y, rect.x1, rect.y1);
            result.push(Slice::from_rect(trim_rect));
        }
        while i < self.slices.len() {
            result.push(self.slices[i].clone());
            i += 1;
        }
        result
    }

    /*
    // This is the dumb version, for reference
    pub fn to_paths(&self) -> Vec<Path> {
        // TODO: generate more optimized path
        let mut result = Vec::new();
        for slice in &self.slices {
            for iv in &slice.intervals {
                let mut path = Path::new(true);
                path.add((iv.x0, slice.y0), NodeType::Line);
                path.add((iv.x1, slice.y0), NodeType::Line);
                path.add((iv.x1, slice.y1), NodeType::Line);
                path.add((iv.x0, slice.y1), NodeType::Line);
                result.push(path);
            }
        }
        result
    }
    */

    pub fn to_paths(&self) -> Vec<Path> {
        let mut tracer = PathTracer::default();
        for i in 0..self.slices.len() {
            let slice = &self.slices[i];
            let (y0, y1) = (slice.y0, slice.y1);
            if i == 0 || self.slices[i - 1].y1 != y0 {
                tracer.process_line(&[], &slice.intervals, y0);
            } else {
                tracer.process_line(&self.slices[i - 1].intervals, &slice.intervals, y0);
            }
            if i == self.slices.len() - 1 || self.slices[i + 1].y0 != y1 {
                tracer.process_line(&slice.intervals, &[], y1);
            }
        }
        tracer.to_paths()
    }
}

type VertexIx = usize;
type EdgeIx = usize;

#[derive(Default)]
struct PathTracer {
    vertices: Vec<Vertex>,
    edges: Vec<(VertexIx, VertexIx)>,
    prev_verts: Vec<VertexIx>,
    next_verts: Vec<VertexIx>,

    pending_edges: BTreeSet<EdgeIx>,
}

struct Vertex {
    pt: Point,
    pred: EdgeIx,
    succ: EdgeIx,
}

const NIL: usize = !0;

impl PathTracer {
    /// Create a new vertex (no edges) and return its index.
    fn new_vertex(&mut self, x: f64, y: f64) -> VertexIx {
        let v = self.vertices.len();
        self.vertices.push(Vertex {
            pt: Point::new(x, y),
            pred: NIL,
            succ: NIL,
        });
        v
    }

    /// Draw an edge from `v0` to `v1`.
    fn edge(&mut self, v0: VertexIx, v1: VertexIx) {
        let e = self.edges.len();
        self.edges.push((v0, v1));
        self.vertices[v0].succ = e;
        self.vertices[v1].pred = e;
    }

    fn process_line(&mut self, prev: &[Interval], next: &[Interval], y: f64) {
        fn get(ivs: &[Interval], i: usize) -> f64 {
            let iv = ivs[i / 2];
            if (i & 1) == 0 {
                iv.x0
            } else {
                iv.x1
            }
        }
        std::mem::swap(&mut self.prev_verts, &mut self.next_verts);
        self.next_verts.clear();
        // These are indices to 2x interval + 1 if right edge
        let imax = prev.len() * 2;
        let jmax = next.len() * 2;
        let mut i = 0;
        let mut j = 0;
        let mut last_v: Option<usize> = None;
        while i < imax || j < jmax {
            if j >= jmax || (i < imax && get(prev, i) <= get(next, j)) {
                // Process a point from prev.
                let x = get(prev, i);
                if let Some(last_v) = last_v.take() {
                    let last_x = self.vertices[last_v].pt.x;
                    if x > last_x {
                        let v = self.new_vertex(x, y);
                        if (i & 1) == 0 {
                            self.edge(last_v, v);
                            self.edge(v, self.prev_verts[i]);
                        } else {
                            self.edge(self.prev_verts[i], v);
                            self.edge(v, last_v);
                        }
                    } else {
                        if (i & 1) == 0 {
                            self.edge(last_v, self.prev_verts[i]);
                        } else {
                            self.edge(self.prev_verts[i], last_v);
                        }
                    }
                } else {
                    let v = self.new_vertex(x, y);
                    if (i & 1) == 0 {
                        self.edge(v, self.prev_verts[i]);
                    } else {
                        self.edge(self.prev_verts[i], v);
                    }
                    last_v = Some(v);
                }
                i += 1;
            } else {
                // Process a point from next.
                let x = get(next, j);
                if let Some(last_v) = last_v.take() {
                    let last_x = self.vertices[last_v].pt.x;
                    if x > last_x {
                        let v = self.new_vertex(x, y);
                        self.next_verts.push(v);
                        if (i & 1) == 0 {
                            self.edge(last_v, v);
                        } else {
                            self.edge(v, last_v);
                        }
                    } else {
                        self.next_verts.push(last_v);
                    }
                } else {
                    let v = self.new_vertex(x, y);
                    self.next_verts.push(v);
                    last_v = Some(v);
                }
                j += 1;
            }
        }
    }

    fn trace_path(&mut self) -> Option<Path> {
        if let Some(e) = self.pending_edges.iter().next() {
            let mut e = *e;
            let mut path = Path::new(true);
            let mut last_pt: Option<Point> = None;
            while self.pending_edges.remove(&e) {
                let edge = self.edges[e];
                let pt = self.vertices[edge.0].pt;
                if let Some(last_pt) = last_pt {
                    if last_pt.x != pt.x {
                        path.add(last_pt, NodeType::Line);
                        path.add(pt, NodeType::Line);
                    }
                }
                last_pt = Some(pt);
                e = self.vertices[edge.1].succ;
            }
            // Note: should probably rotate by two for clockwise, to make
            // it match what glyphs does for "correct path direction".
            path.rotate_left(1);
            Some(path)
        } else {
            None
        }
    }

    fn to_paths(&mut self) -> Vec<Path> {
        self.pending_edges = (0..self.edges.len()).collect();
        let mut result = Vec::new();
        while let Some(path) = self.trace_path() {
            result.push(path);
        }
        result
    }
}