kickoff 0.4.5

Fast and minimal program launcher
use crate::color::Color;

use std::collections::HashMap;

use font_loader::system_fonts;
use font_loader::system_fonts::FontPropertyBuilder;

use fontdue::{layout::GlyphRasterConfig, FontSettings, Metrics};

use image::{Pixel, RgbaImage};

pub struct Font {
    font: fontdue::Font,
    scale: f32,
    glyph_cache: HashMap<GlyphRasterConfig, (Metrics, Vec<u8>)>,
}

impl Font {
    pub fn new(name: &str, size: f32) -> Font {
        let font_builder = FontPropertyBuilder::new().family(name).build();
        let (font_data, _) = system_fonts::get(&font_builder).unwrap();
        Font {
            font: fontdue::Font::from_bytes(font_data, FontSettings::default())
                .expect("Failed to parse Font"),
            scale: size,
            glyph_cache: HashMap::new(),
        }
    }

    fn render_glyph(&mut self, conf: GlyphRasterConfig) -> (Metrics, Vec<u8>) {
        if let Some(bitmap) = self.glyph_cache.get(&conf) {
            bitmap.clone()
        } else {
            self.glyph_cache
                .insert(conf, self.font.rasterize_config(conf));
            self.glyph_cache.get(&conf).unwrap().clone()
        }
    }

    pub fn render(
        &mut self,
        text: &str,
        color: &Color,
        image: &mut RgbaImage,
        x_offset: u32,
        y_offset: u32,
    ) -> (u32, u32) {
        let mut width = 0.;
        for letter in text.chars() {
            let (meta, bitmap) = self.render_glyph(GlyphRasterConfig {
                glyph_index: self.font.lookup_glyph_index(letter) as u16,
                px: self.scale,
                font_index: 0,
            });
            for (i, alpha) in bitmap.iter().enumerate() {
                if alpha != &0 {
                    let x = ((i % meta.width) as f32 + width + x_offset as f32 + meta.xmin as f32)
                        as u32;
                    let y = ((i as f32 / meta.width as f32) + y_offset as f32 + self.scale
                        - meta.height as f32
                        - meta.ymin as f32) as u32;
                    if x < image.width() && y < image.height() {
                        if alpha == &255 {
                            image.put_pixel(x, y, image::Rgba([color.0, color.1, color.2, 255]));
                        } else {
                            image
                                .get_pixel_mut(x, y)
                                .blend(&image::Rgba([color.0, color.1, color.2, *alpha]));
                        }
                    }
                }
            }
            width += meta.advance_width;
        }

        (width as u32, self.scale as u32)
    }
}