Skip to main content

retroglyph_window/
geometry.rs

1//! Cell and surface pixel geometry shared by the graphical backends.
2
3/// The pixel geometry of a fixed cell grid: a glyph size and an integer scale.
4///
5/// [`Presenter::cell_size`](crate::Presenter::cell_size)'s contract -- physical pixels,
6/// `glyph x scale`, never DPI-auto-scaled -- had no code embodiment: each graphical backend
7/// re-derived `glyph_w * scale` (and `cols * cell_w` for the surface) on its own, in slightly
8/// different integer types, so the shared rule could drift. This is that rule as one small,
9/// `const`, testable value type. A backend stores one and returns [`cell_size`](Self::cell_size)
10/// from `Presenter::cell_size`.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct CellGeometry {
13    /// Glyph width in unscaled font pixels.
14    pub glyph_w: u8,
15    /// Glyph height in unscaled font pixels.
16    pub glyph_h: u8,
17    /// Integer pixel scale: each glyph pixel becomes a `scale x scale` block of physical pixels.
18    pub scale: u16,
19}
20
21impl CellGeometry {
22    /// A geometry for `glyph_w x glyph_h` glyphs drawn at integer `scale`.
23    #[must_use]
24    pub const fn new(glyph_w: u8, glyph_h: u8, scale: u16) -> Self {
25        Self {
26            glyph_w,
27            glyph_h,
28            scale,
29        }
30    }
31
32    /// Cell size in physical pixels: `(glyph_w * scale, glyph_h * scale)`.
33    ///
34    /// The single embodiment of `Presenter::cell_size`'s "physical pixels, glyph x scale" contract.
35    #[must_use]
36    pub const fn cell_size(&self) -> (u32, u32) {
37        // `as` (not `u32::from`) because this is a `const fn` and `From` isn't const-callable; both
38        // casts are lossless widenings (u8/u16 -> u32).
39        (
40            self.glyph_w as u32 * self.scale as u32,
41            self.glyph_h as u32 * self.scale as u32,
42        )
43    }
44
45    /// Surface size in physical pixels for a `cols x rows` grid: `(cols * cell_w, rows * cell_h)`.
46    #[must_use]
47    pub const fn surface_size(&self, cols: u16, rows: u16) -> (u32, u32) {
48        let (cell_w, cell_h) = self.cell_size();
49        (cols as u32 * cell_w, rows as u32 * cell_h)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::CellGeometry;
56
57    #[test]
58    fn cell_size_is_glyph_times_scale() {
59        assert_eq!(CellGeometry::new(8, 16, 1).cell_size(), (8, 16));
60        assert_eq!(CellGeometry::new(8, 16, 2).cell_size(), (16, 32));
61        assert_eq!(CellGeometry::new(6, 12, 3).cell_size(), (18, 36));
62    }
63
64    #[test]
65    fn surface_size_is_grid_times_cell() {
66        // 80x25 grid of 8x16 cells at scale 1, then scale 2.
67        assert_eq!(CellGeometry::new(8, 16, 1).surface_size(80, 25), (640, 400));
68        assert_eq!(
69            CellGeometry::new(8, 16, 2).surface_size(80, 25),
70            (1280, 800)
71        );
72    }
73
74    #[test]
75    fn zero_grid_is_zero_surface() {
76        assert_eq!(CellGeometry::new(8, 16, 2).surface_size(0, 0), (0, 0));
77    }
78}