First pass.

API seems reasonable-ish.  It probably works just fine, right?  Who
needs unit tests?
1 files changed, 184 insertions(+), 0 deletions(-)

M src/lib.rs
M src/lib.rs +184 -0
@@ 1,5 1,189 @@ 
+//! A simple glyph cache for video games and other programs.
+//!
+//! Does no actual rendering, drawing or anything else, simply mongles resources.  You provide
+//! callbacks for it to talk to a rasterizer such as TODO, and interact with a drawing API such as
+//! OpenGL or Vulkan.
+//!
+//! This also sure as heck does not handle RTL or top-to-bottom text nicely, I bet.  Let alone
+//! ligatures.
+//!
+//! TODO: How do we handle pairs of glyphs correctly, a la ligatures?  Things to check:
+//! fi ffi rn rm fj ll ...
+//! We might just step through characters by pairs instead of single things
+
+use std::collections::HashMap;
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub struct Rect {
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
+}
+
+/// A trait for the user to implement that defines how to interact with where their glyphs are
+/// stored.
+pub trait Storage {
+    fn write_bytes_to(&mut self, rect: Rect, bytes: &[u8]) -> Result<(), ()>;
+    fn resize(&mut self, new_w: f32, new_h: f32);
+}
+
+pub trait Rasterizer {
+    fn glyph_dimensions(&self, c: char) -> (f32, f32);
+    fn rasterize_glyph(&self, c: char) -> Vec<u8>;
+}
+
+/// The `Storage` type is the backing store that the glyphs are actually cached in, such as a
+/// texture.
+#[derive(Clone, Debug)]
+pub struct Glif<S>
+where
+    S: 'static + Storage,
+{
+    /// The backing storage, ie a texture
+    storage: S,
+    /// Where various characters are in the texture.
+    locations: HashMap<char, Rect>,
+    /// We keep the width and height together.
+    /// TODO: Assert somewhere that they're > 0.0
+    storage_dims: f32,
+    col_left: f32,
+    row_top: f32,
+    row_height: f32,
+}
+
+impl<S> Glif<S>
+where
+    S: 'static + Storage,
+{
+    pub fn new(storage: S, initial_size: f32) -> Self {
+        Self {
+            storage,
+            locations: HashMap::new(),
+            storage_dims: initial_size,
+            col_left: 0.0,
+            row_top: 0.0,
+            row_height: 0.0,
+        }
+    }
+
+    /// TODO: Context type
+    /// TODO: Glyph parameters like size, font, bold/italic, etc
+    pub fn draw_str<F, R>(&mut self, s: &str, mut draw_glyph: F, r: &R) -> Result<(), ()>
+    where
+        F: FnMut(&S, Rect),
+        R: Rasterizer,
+    {
+        for c in s.chars() {
+            if let Some(rect) = self.locations.get(&c) {
+                // The character alrleady exists, ezpz
+                draw_glyph(&self.storage, *rect);
+            } else {
+                // The character does not exist, rasterize it and find a place to draw it
+                let (w, h) = r.glyph_dimensions(c);
+                let rect = self.make_new_rect(w, h);
+                let bytes = r.rasterize_glyph(c);
+                self.storage.write_bytes_to(rect, &bytes)?;
+                self.locations.insert(c, rect);
+                draw_glyph(&self.storage, rect);
+            }
+        }
+        Ok(())
+    }
+
+    /// Chooses the best spot in the storage to put a rect of the given dimensions, or returns `None` if it can't fit.
+    fn find_new_rect(&mut self, width: f32, height: f32) -> Option<Rect> {
+        // Ohai an easy case
+        if width >= self.storage_dims || height >= self.storage_dims {
+            return None;
+        }
+
+        // TODO: Solve the knapsack problem.
+        // Currently we just fill the glyph cache from left to right and top to bottom.
+        // As long as glyphs are mostly the same size this might theoretically not be the worst
+        // thing ever maybe kinda
+        let mut target_x = self.col_left;
+        let mut target_y = self.row_top;
+        let mut target_height = f32::max(self.row_height, height);
+        if target_x + width >= self.storage_dims {
+            // Scroll to next line
+            target_x = 0.0;
+            target_y += target_height;
+            target_height = height;
+        }
+        if target_y + target_height >= self.storage_dims {
+            // We've walked off the bottom of the storage.
+            return None;
+        }
+        // Yay we've found a spot
+        self.col_left = target_x + width;
+        self.row_top = target_y;
+        self.row_height = target_height;
+        Some(Rect {
+            x: target_x,
+            y: target_y,
+            w: width,
+            h: height,
+        })
+    }
+
+    /// Gets a rect in the given storage of the given storage.  If it can't fit,
+    /// make a new storage, copy the old bytes into it, and
+    ///
+    /// TODO: Is resizing the existing storage practical?  If Vulkan or OpenGL can't
+    /// do it practically, it's not worth pretending and we should just replace the
+    /// old one.
+    fn make_new_rect(&mut self, width: f32, height: f32) -> Rect {
+        self.find_new_rect(width, height).unwrap_or_else(|| {
+            assert!(self.storage_dims > 0.0);
+            assert!(width > 0.0);
+            assert!(width < f32::INFINITY);
+            assert!(height > 0.0);
+            assert!(height < f32::INFINITY);
+
+            let mut new_dims = self.storage_dims * 2.0;
+            // Crudely figure out how much bigger we need to make the texture.
+            // Just make it huger until it's guarenteed bigger than we need.
+            while new_dims < (f32::max(width, height) * 2.0) {
+                new_dims *= 2.0;
+            }
+            assert!(new_dims < f32::INFINITY);
+            self.storage_dims = new_dims;
+            self.storage.resize(self.storage_dims, self.storage_dims);
+            self.find_new_rect(width, height)
+                .expect("Made storage bigger but still can't hold glyph, should never happen")
+        })
+    }
+}
+
 #[cfg(test)]
 mod tests {
+    use crate::*;
+
+    struct St {
+        contents: Vec<u8>,
+        size: u32,
+    }
+
+    impl Storage for St {
+        fn write_bytes_to(&mut self, _rect: Rect, _bytes: &[u8]) -> Result<(), ()> {
+            todo!()
+        }
+        fn resize(&mut self, _new_w: f32, _new_h: f32) {
+            todo!()
+        }
+    }
+
+    struct Ra {}
+    impl Rasterizer for Ra {
+        fn glyph_dimensions(&self, _c: char) -> (f32, f32) {
+            todo!()
+        }
+        fn rasterize_glyph(&self, _c: char) -> Vec<u8> {
+            todo!()
+        }
+    }
+
     #[test]
     fn it_works() {
         assert_eq!(2 + 2, 4);