msdfgen 0.2.1

Safe bindings for msdfgen (multi-channel signed distance field generator)
Documentation
use crate::{Contour, EdgeColor, EdgeHolder, FontExt, Point2, Shape};

#[derive(Default)]
struct ContourBuilder {
    contour: Contour,
    point: Point2<f64>,
    start_point: Point2<f64>,
}

impl ContourBuilder {
    pub fn open_at(x: f64, y: f64) -> Self {
        Self {
            contour: Contour::default(),
            point: Point2::new(x, y),
            start_point: Point2::new(x, y),
        }
    }

    pub fn line_to(&mut self, x: f64, y: f64) {
        let point = Point2::new(x, y);
        self.contour.add_edge(&EdgeHolder::new_linear(
            self.point,
            point,
            EdgeColor::default(),
        ));
        self.point = point;
    }

    pub fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64) {
        let cpoint = Point2::new(cx, cy);
        let point = Point2::new(x, y);
        self.contour.add_edge(&EdgeHolder::new_quadratic(
            self.point,
            cpoint,
            point,
            EdgeColor::default(),
        ));
        self.point = point;
    }

    pub fn curve_to(&mut self, c1x: f64, c1y: f64, c2x: f64, c2y: f64, x: f64, y: f64) {
        let c1point = Point2::new(c1x, c1y);
        let c2point = Point2::new(c2x, c2y);
        let point = Point2::new(x, y);
        self.contour.add_edge(&EdgeHolder::new_cubic(
            self.point,
            c1point,
            c2point,
            point,
            EdgeColor::default(),
        ));
        self.point = point;
    }

    pub fn close(self) -> Contour {
        self.contour
    }
}

#[derive(Default)]
struct ShapeBuilder {
    shape: Shape,
    contour: Option<ContourBuilder>,
}

impl ShapeBuilder {
    pub fn build(self) -> Shape {
        self.shape
    }
}

impl ttf_parser::OutlineBuilder for ShapeBuilder {
    fn move_to(&mut self, x: f32, y: f32) {
        if self.contour.is_some() {
            panic!("Unexpected move_to");
        }

        self.contour = ContourBuilder::open_at(x as _, y as _).into();
    }

    fn line_to(&mut self, x: f32, y: f32) {
        self.contour
            .as_mut()
            .expect("Opened contour")
            .line_to(x as _, y as _);
    }

    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
        self.contour
            .as_mut()
            .expect("Opened contour")
            .quad_to(x1 as _, y1 as _, x as _, y as _);
    }

    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
        self.contour
            .as_mut()
            .expect("Opened contour")
            .curve_to(x1 as _, y1 as _, x2 as _, y2 as _, x as _, y as _);
    }

    fn close(&mut self) {
        let mut contour = self.contour.take().expect("Opened contour");
        if contour.point != contour.start_point {
            contour.line_to(contour.start_point.x as _, contour.start_point.y as _);
        }
        self.shape.add_contour(&contour.close());
    }
}

impl<'a> FontExt for ttf_parser::Face<'a> {
    type Glyph = ttf_parser::GlyphId;

    fn glyph_shape(&self, glyph: Self::Glyph) -> Option<Shape> {
        let mut builder = ShapeBuilder::default();

        self.outline_glyph(glyph, &mut builder)?;

        Some(builder.build())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use notosans::REGULAR_TTF;
    use ttf_parser::{Face, GlyphId};

    #[test]
    fn glyph_shape() {
        let font = Face::parse(REGULAR_TTF, 0).unwrap();

        let mut shapes = 0;

        for glyph in 0..font.number_of_glyphs() {
            let glyph = GlyphId(glyph);
            if let Some(_shape) = font.glyph_shape(glyph) {
                shapes += 1;
            }
        }

        assert_eq!(shapes, 2392);
    }
}