mplusfonts-macros 0.3.4

Procedural macros re-exported in the mplusfonts crate
Documentation
use std::array;

use swash::zeno::Point;

use crate::mplus::font::{Font, FontWidth};

#[derive(Clone, Copy)]
pub enum Halfwidth {
    Floor(f32),
    Ceil,
    Zero,
}

pub struct Grid<const N: usize>(pub [[Point; N]; N]);

impl Halfwidth {
    pub fn from_font(font: &Font, pixels_per_em: f32) -> Self {
        let em_per_halfwidth = match *font {
            Font::MPLUSCode {
                variable: (.., FontWidth(units)),
                ..
            } => f32::from(units).mul_add(0.4 / 100.0, 0.1),
            _ => 0.5,
        };

        match pixels_per_em {
            ..1.25 => Self::Zero,
            ..2.0 => Self::Ceil,
            _ => Self::Floor(pixels_per_em * em_per_halfwidth),
        }
    }
}

impl<const N: usize> Grid<N> {
    pub fn new(width: f32, height: f32, snap: Option<(f32, f32)>) -> Self {
        let target_x = width.floor();
        let target_y = height.floor();
        let x_scale = if width > 0.0 { target_x / width } else { 0.0 };
        let y_scale = if height > 0.0 { target_y / height } else { 0.0 };
        let x_increment = if N < 2 { 0.0 } else { width / (N - 1) as f32 };
        let y_increment = if N < 2 { 0.0 } else { height / (N - 1) as f32 };

        let mut x = 0f32;
        let mut y = 0f32;
        let coords: [(f32, f32); N] = array::from_fn(|_| {
            let coords = if let Some((x_translate, y_translate)) = snap {
                let x = (x.mul_add(x_scale, x_translate)).round() - x_translate;
                let y = (y.mul_add(y_scale, y_translate)).round() - y_translate;

                (x, y)
            } else {
                (x, y)
            };

            x += x_increment;
            y += y_increment;

            coords
        });

        let points = array::from_fn(|y_index| {
            array::from_fn(|x_index| {
                let (x, _) = coords[x_index];
                let (_, y) = coords[y_index];

                Point::new(x, y)
            })
        });

        Self(points)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! test_new_points {
        (
            $(
                $fn_ident:ident, $n:expr, $width:expr, $height:expr, $snap:expr, $expected:expr,
            )*
        ) => {
            $(
                #[test]
                fn $fn_ident() {
                    let Grid(points) = Grid::<$n>::new($width, $height, $snap);
                    assert_eq!(points, $expected);
                }
            )*
        }
    }

    test_new_points! {
        new_with_0_points, 0, 7.5, 15.0, None, [[]; 0],

        new_with_1_point_and_no_snap,
            1, 7.5, 15.0, None, [
                [Point::new(0.0, 0.0)]
            ],

        new_with_4_points_and_no_snap,
            2, 7.5, 15.0, None, [
                [Point::new(0.0, 0.0), Point::new(7.5, 0.0)],
                [Point::new(0.0, 15.0), Point::new(7.5, 15.0)],
            ],

        new_with_9_points_and_no_snap,
            3, 7.5, 15.0, None, [
                [Point::new(0.0, 0.0), Point::new(3.75, 0.0), Point::new(7.5, 0.0)],
                [Point::new(0.0, 7.5), Point::new(3.75, 7.5), Point::new(7.5, 7.5)],
                [Point::new(0.0, 15.0), Point::new(3.75, 15.0), Point::new(7.5, 15.0)],
            ],

        new_with_1_point_and_snap_to_grid,
            1, 7.5, 15.0, Some((0.0, 0.0)), [
                [Point::new(0.0, 0.0)]
            ],

        new_with_4_points_and_snap_to_grid,
            2, 7.5, 15.0, Some((0.0, 0.0)), [
                [Point::new(0.0, 0.0), Point::new(7.0, 0.0)],
                [Point::new(0.0, 15.0), Point::new(7.0, 15.0)],
            ],

        new_with_9_points_and_snap_to_grid,
            3, 7.5, 15.0, Some((0.0, 0.0)), [
                [Point::new(0.0, 0.0), Point::new(4.0, 0.0), Point::new(7.0, 0.0)],
                [Point::new(0.0, 8.0), Point::new(4.0, 8.0), Point::new(7.0, 8.0)],
                [Point::new(0.0, 15.0), Point::new(4.0, 15.0), Point::new(7.0, 15.0)],
            ],

        new_with_1_point_and_snap_in_between,
            1, 7.5, 15.0, Some((0.5, 0.5)), [
                [Point::new(0.5, 0.5)]
            ],

        new_with_4_points_and_snap_in_between,
            2, 7.5, 15.0, Some((0.5, 0.5)), [
                [Point::new(0.5, 0.5), Point::new(7.5, 0.5)],
                [Point::new(0.5, 15.5), Point::new(7.5, 15.5)],
            ],

        new_with_9_points_and_snap_in_between,
            3, 7.5, 15.0, Some((0.5, 0.5)), [
                [Point::new(0.5, 0.5), Point::new(3.5, 0.5), Point::new(7.5, 0.5)],
                [Point::new(0.5, 7.5), Point::new(3.5, 7.5), Point::new(7.5, 7.5)],
                [Point::new(0.5, 15.5), Point::new(3.5, 15.5), Point::new(7.5, 15.5)],
            ],
    }
}