use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::{validate_dimensions, FontProvenance, FormatError, RenderMode, StyleFlags, MAX_GLYPHS};
pub const FONT_ATLAS_VERSION: u8 = 2;
pub const FONT_ATLAS_MIN_SUPPORTED: u8 = 1;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FontAtlas {
pub version: u8,
pub meta: FontMeta,
pub glyphs: BTreeMap<char, GlyphVariants>,
pub line_height: u32,
pub mode: RenderMode,
pub available_styles: StyleFlags,
}
impl FontAtlas {
pub fn validate(&self) -> Result<(), FormatError> {
if self.glyphs.len() > MAX_GLYPHS {
return Err(FormatError::GlyphCountExceeded {
count: self.glyphs.len(),
max: MAX_GLYPHS,
});
}
if self.line_height == 0 {
return Err(FormatError::ZeroLineHeight);
}
for variants in self.glyphs.values() {
variants.validate()?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct FontMeta {
pub font_name: String,
pub font_size: f32,
pub created_at: u64,
pub editor_version: String,
#[serde(default)]
pub provenance: Option<FontProvenance>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GlyphVariants {
pub base: GlyphData,
pub bold: Option<GlyphData>,
pub italic: Option<GlyphData>,
pub bold_italic: Option<GlyphData>,
pub reverse: Option<GlyphData>,
}
impl GlyphVariants {
pub fn validate(&self) -> Result<(), FormatError> {
self.base.validate()?;
if let Some(glyph) = &self.bold {
glyph.validate()?;
}
if let Some(glyph) = &self.italic {
glyph.validate()?;
}
if let Some(glyph) = &self.bold_italic {
glyph.validate()?;
}
if let Some(glyph) = &self.reverse {
glyph.validate()?;
}
Ok(())
}
pub fn select(&self, bold: bool, italic: bool, reverse: bool) -> &GlyphData {
if reverse {
if let Some(glyph) = &self.reverse {
return glyph;
}
}
if bold && italic {
if let Some(glyph) = &self.bold_italic {
return glyph;
}
}
if bold {
if let Some(glyph) = &self.bold {
return glyph;
}
}
if italic {
if let Some(glyph) = &self.italic {
return glyph;
}
}
&self.base
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GlyphData {
pub chars: String,
pub width: u32,
pub height: u32,
pub opacity: Option<Vec<u8>>,
}
impl GlyphData {
pub fn validate(&self) -> Result<(), FormatError> {
let cells = validate_dimensions(self.width, self.height)?;
let char_count = self.chars.chars().count();
if char_count != cells {
return Err(FormatError::GlyphCharCountMismatch {
actual: char_count,
expected: cells,
width: self.width,
height: self.height,
});
}
if let Some(opacity) = &self.opacity {
if opacity.len() != cells {
return Err(FormatError::GlyphOpacityMismatch {
actual: opacity.len(),
expected: cells,
width: self.width,
height: self.height,
});
}
}
Ok(())
}
}