srs2dge 0.1.1

Simple Rust 2D Game Engine
Documentation
use super::{
    packer2d::{Packer, Rect},
    TexturePosition,
};
use font_loader::system_fonts::{self, FontProperty};
use fontdue::{Font, FontSettings};
use glium::{
    backend::Facade,
    texture::{
        MipmapsOption, RawImage2d, Texture2dDataSink, TextureCreationError, UncompressedFloatFormat,
    },
    uniforms::Sampler,
    Texture2d,
};
use std::{borrow::Cow, collections::HashMap};

//

type GlyphHash = usize;

//

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
struct Glyph {
    index: u16,
    scale: u16,
    font: usize,
}

pub struct Glyphs {
    texture: Texture2d,
    packer: Packer,

    fonts: HashMap<usize, Font>,
    glyphs: HashMap<Glyph, (GlyphHash, TexturePosition)>,

    queue: Vec<Glyph>,
}

//

impl Glyphs {
    pub fn new<F: Facade>(facade: &F, rect: Rect) -> Result<Self, TextureCreationError> {
        let texture = Texture2d::empty_with_format(
            facade,
            UncompressedFloatFormat::F32,
            MipmapsOption::AutoGeneratedMipmaps,
            rect.width,
            rect.height,
        )?;
        let packer = Packer::new(rect);

        let fonts = HashMap::new();
        let glyphs = HashMap::new();

        let queue = Vec::new();

        Ok(Self {
            texture,
            packer,

            fonts,
            glyphs,

            queue,
        })
    }

    pub fn add_font(&mut self, font: Font) -> usize {
        let id = font.file_hash();
        self.fonts.insert(id, font);
        id
    }

    pub fn add_font_bytes(&mut self, font: &[u8]) -> Result<usize, &'static str> {
        Ok(self.add_font(Font::from_bytes(font, FontSettings::default())?))
    }

    pub fn add_font_property(&mut self, font: FontProperty) -> Result<usize, &'static str> {
        self.add_font_bytes(&system_fonts::get(&font).ok_or("Font not found")?.0[..])
    }

    pub fn queue(&mut self, c: char, scale: u16, font: usize) {
        self.queue.push(Glyph {
            index: self.fonts[&font].lookup_glyph_index(c),
            scale,
            font,
        });
    }

    pub fn flush(&mut self) {
        let mut tmp_queue = vec![];
        std::mem::swap(&mut tmp_queue, &mut self.queue);

        for queued in tmp_queue.drain(..) {
            if self.get_glyph(&queued).is_some() {
                continue;
            }

            let (metrics, data) =
                self.fonts[&queued.font].rasterize_indexed(queued.index, queued.scale as f32);

            let rect = self
                .packer
                .push(Rect::new(metrics.width as u32, metrics.height as u32))
                .unwrap(); // TODO:

            let data = RawImage2d::from_raw(Cow::from(&data[..]), rect.width, rect.height);

            self.texture.write(
                glium::Rect {
                    left: rect.x,
                    bottom: rect.y,
                    width: rect.width,
                    height: rect.height,
                },
                data,
            );

            let dim = Rect {
                width: self.texture.dimensions().0,
                height: self.texture.dimensions().1,
            };

            self.glyphs
                .insert(queued, (0, TexturePosition::new(dim, rect)));
        }
    }

    fn get_glyph(&self, glyph: &Glyph) -> Option<TexturePosition> {
        Some(self.glyphs.get(glyph)?.1)
    }

    pub fn get(&self, c: char, scale: u16, font: usize) -> Option<TexturePosition> {
        self.get_glyph(&Glyph {
            index: self.fonts[&font].lookup_glyph_index(c),
            scale,
            font,
        })
    }

    pub fn get_indexed(&self, index: u16, scale: u16, font: usize) -> Option<TexturePosition> {
        self.get_glyph(&Glyph { index, scale, font })
    }

    pub fn sampled(&self) -> Sampler<'_, Texture2d> {
        self.texture.sampled()
    }

    pub fn font(&self, font: usize) -> Option<&'_ Font> {
        self.fonts
            .get(&font)
            .or_else(|| self.fonts.iter().next().map(|(_, font)| font))
    }
}