nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use crate::render::wgpu::font_atlas::{AtlasPacker, DEFAULT_FONT_DATA};
use crate::render::wgpu::sprite_texture_atlas::SpriteTextureAtlas;
use nalgebra_glm::Vec2;
use std::collections::HashMap;

const RASTERIZATION_SCALE: f32 = 48.0;
const FONT_BITMAP_SIZE: u32 = 512;
const FONT_ATLAS_SLOT: u32 = 127;

#[derive(Debug, Clone)]
pub struct SpriteGlyphInfo {
    pub uv_min: Vec2,
    pub uv_max: Vec2,
    pub size: Vec2,
    pub bearing: Vec2,
    pub advance: f32,
}

pub struct SpriteFontAtlas {
    pub glyphs: HashMap<char, SpriteGlyphInfo>,
    pub atlas_slot: u32,
    pub rasterization_scale: f32,
    pub line_height: f32,
}

pub fn rasterize_sprite_font(
    queue: &wgpu::Queue,
    atlas: &mut SpriteTextureAtlas,
) -> SpriteFontAtlas {
    rasterize_sprite_font_from_bytes(queue, atlas, DEFAULT_FONT_DATA, FONT_ATLAS_SLOT)
}

pub fn rasterize_sprite_font_from_bytes(
    queue: &wgpu::Queue,
    atlas: &mut SpriteTextureAtlas,
    font_data: &[u8],
    slot: u32,
) -> SpriteFontAtlas {
    use swash::FontRef;
    use swash::scale::{Render, ScaleContext, Source};
    use swash::zeno::Format;

    let font_ref =
        FontRef::from_index(font_data, 0).expect("Failed to parse font data for sprite font atlas");
    let mut context = ScaleContext::new();
    let charmap = font_ref.charmap();
    let metrics = font_ref.metrics(&[]).scale(RASTERIZATION_SCALE);
    let glyph_metrics = font_ref.glyph_metrics(&[]).scale(RASTERIZATION_SCALE);
    let line_height = metrics.ascent - metrics.descent + metrics.leading;

    let bitmap_size = FONT_BITMAP_SIZE;
    let mut rgba_bitmap = vec![0u8; (bitmap_size * bitmap_size * 4) as usize];
    let mut packer = AtlasPacker::new(bitmap_size, bitmap_size);
    let mut glyphs = HashMap::new();

    for code in 32u8..127u8 {
        let character = code as char;
        let glyph_id = charmap.map(character);
        let advance = glyph_metrics.advance_width(glyph_id);

        let mut scaler = context
            .builder(font_ref)
            .size(RASTERIZATION_SCALE)
            .hint(true)
            .build();

        let image = Render::new(&[Source::Outline])
            .format(Format::Alpha)
            .render(&mut scaler, glyph_id);

        let Some(image) = image else {
            glyphs.insert(
                character,
                SpriteGlyphInfo {
                    uv_min: Vec2::new(0.0, 0.0),
                    uv_max: Vec2::new(0.0, 0.0),
                    size: Vec2::new(0.0, 0.0),
                    bearing: Vec2::new(0.0, 0.0),
                    advance,
                },
            );
            continue;
        };

        let glyph_width = image.placement.width;
        let glyph_height = image.placement.height;

        if glyph_width == 0 || glyph_height == 0 {
            glyphs.insert(
                character,
                SpriteGlyphInfo {
                    uv_min: Vec2::new(0.0, 0.0),
                    uv_max: Vec2::new(0.0, 0.0),
                    size: Vec2::new(0.0, 0.0),
                    bearing: Vec2::new(0.0, 0.0),
                    advance,
                },
            );
            continue;
        }

        let Some((pack_x, pack_y)) = packer.pack(glyph_width + 1, glyph_height + 1) else {
            continue;
        };

        for row in 0..glyph_height as usize {
            for col in 0..glyph_width as usize {
                let source_index = row * glyph_width as usize + col;
                let coverage = image.data[source_index];

                let destination_x = pack_x as usize + col;
                let destination_y = pack_y as usize + row;
                let destination_index = (destination_y * bitmap_size as usize + destination_x) * 4;

                rgba_bitmap[destination_index] = 255;
                rgba_bitmap[destination_index + 1] = 255;
                rgba_bitmap[destination_index + 2] = 255;
                rgba_bitmap[destination_index + 3] = coverage;
            }
        }

        let uv_min = Vec2::new(
            pack_x as f32 / bitmap_size as f32,
            pack_y as f32 / bitmap_size as f32,
        );
        let uv_max = Vec2::new(
            (pack_x + glyph_width) as f32 / bitmap_size as f32,
            (pack_y + glyph_height) as f32 / bitmap_size as f32,
        );

        glyphs.insert(
            character,
            SpriteGlyphInfo {
                uv_min,
                uv_max,
                size: Vec2::new(glyph_width as f32, glyph_height as f32),
                bearing: Vec2::new(image.placement.left as f32, image.placement.top as f32),
                advance,
            },
        );
    }

    atlas.upload_texture(queue, slot, &rgba_bitmap, bitmap_size, bitmap_size);

    SpriteFontAtlas {
        glyphs,
        atlas_slot: slot,
        rasterization_scale: RASTERIZATION_SCALE,
        line_height,
    }
}