bevy_pxtxt 0.2.0

Create fonts and render text in bevy based on a bitmap source image.
Documentation
use std::{ops::RangeInclusive, path::PathBuf};

use serde::{Deserialize, Serialize};

const fn zerozero() -> (u32, u32) { (0, 0) }
const fn one() -> u32 { 1 }

#[derive(Debug, Serialize, Deserialize)]
pub struct PxFontData {
    pub name: String,
    pub image: PathBuf,
    pub glyph_width: GlyphWidth,
    pub ascender: u32,
    pub descender: u32,
    #[serde(default)]
    pub char_layout: CharLayout,
    #[serde(default = "one")]
    pub spacing: u32,
    #[serde(default = "zerozero")]
    pub padding: (u32, u32),
}

#[derive(Debug, Serialize, Deserialize)]
pub enum GlyphWidth {
    Varied {
        max: u32,
        min: u32,
    },
    Monospace(u32),
}

#[derive(Debug, Serialize, Deserialize)]
pub enum CharLayout {
    StartingAt(char),
    Ranges(Vec<RangeInclusive<char>>),
    Listed(Vec<char>),
}

impl Default for CharLayout {
    fn default() -> Self {
        Self::StartingAt(' ')
    }
}

impl CharLayout {
    pub fn get(&self, index: u32) -> Option<char> {
        match &self {
            Self::StartingAt(c) => {
                let code: u32 = (*c).into();
                let new = code + index;
                char::from_u32(new)
            }
            _ => todo!(),
        }
    }
}

impl IntoIterator for CharLayout {
    type IntoIter = CharLayoutIter;
    type Item = char;

    fn into_iter(self) -> Self::IntoIter {
        let idx = match &self {
            CharLayout::StartingAt(c) => (*c).into(),
            CharLayout::Ranges(vec) => (*vec[0].start()).into(),
            CharLayout::Listed(vec) => vec[0].into(),
        };

        CharLayoutIter {
            layout: self,
            idx,
            started: false,
        }
    }
}

pub struct CharLayoutIter {
    layout: CharLayout,
    idx: char,
    started: bool,
}

impl Iterator for CharLayoutIter {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        if !self.started {
            self.started = true;
            return Some(self.idx)
        }

        match &self.layout {
            CharLayout::StartingAt(_) => {
                let mut cursor = self.idx as u32;
                while cursor < char::MAX as u32 {
                    cursor += 1;
                    if let Some(new) = char::from_u32(cursor) {
                        self.idx = new;
                        return Some(self.idx);
                    }
                }
                None
            },
            CharLayout::Ranges(vec) => {
                if let Some((idx, range)) = vec
                    .iter()
                    .enumerate()
                    .find(|(_, v)| v.contains(&self.idx)) {
                    if *range.end() == self.idx {
                        if let Some(next) = vec.get(idx + 1) {
                            self.idx = *next.start();
                            Some(self.idx)
                        } else {
                            None
                        }
                    } else {
                        let mut cursor = self.idx as u32;
                        while cursor < char::MAX as u32 {
                            cursor += 1;
                            if let Some(new) = char::from_u32(cursor) {
                                self.idx = new;
                                return Some(self.idx);
                            }
                        }
                        None
                    }
                } else {
                    None
                }
            }
            CharLayout::Listed(vec) => {
                if let Some((i, _)) = vec
                    .iter()
                    .enumerate()
                    .find(|(_, v)| **v == self.idx) {
                    vec.get(i + 1).cloned()
                } else {
                    None
                }
            }
        }
    }
}