mod glyph;
mod kerning;
pub use glyph::Glyph;
pub use kerning::Kerning;
use macros::named_list_chunk;
use crate::prelude::*;
use crate::wad::deserialize::reader::DataReader;
use crate::wad::elements::GMElement;
use crate::wad::elements::texture_page_item::GMTexturePageItem;
use crate::wad::reference::GMRef;
use crate::wad::serialize::builder::DataBuilder;
use crate::wad::version::LTSBranch;
#[named_list_chunk("FONT")]
pub struct GMFonts {
pub fonts: Vec<GMFont>,
pub exists: bool,
}
impl GMElement for GMFonts {
fn deserialize(reader: &mut DataReader) -> Result<Self> {
let fonts: Vec<GMFont> = reader.read_pointer_list()?;
if !reader.general_info.is_version_at_least((2024, 14)) {
let verify: bool = reader.options.verify_constants;
let padding: &[u8; 512] = reader.read_bytes_const().context("Reading FONT padding")?;
if verify {
verify_padding(padding)?;
}
}
Ok(Self { fonts, exists: true })
}
fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
builder.write_pointer_list(&self.fonts)?;
if !builder.is_version_at_least((2024, 14)) {
let padding: [u8; 512] = generate_padding();
builder.write_bytes(&padding);
}
Ok(())
}
}
fn verify_padding(padding: &[u8; 512]) -> Result<()> {
padding.iter().enumerate().try_for_each(|(i, &byte)| {
let expected = match i {
0..256 if i & 1 == 0 => (i >> 1) as u8,
256..512 if i & 1 == 0 => 63,
_ => 0,
};
if byte == expected {
Ok(())
} else {
bail!("Invalid FONT padding at byte #{i}: expected 0x{expected:02X}, got 0x{byte:02X}")
}
})
}
#[must_use]
const fn generate_padding() -> [u8; 512] {
let mut padding = [0u8; 512];
let mut i = 0;
while i < 256 {
padding[i] = if i & 1 == 0 { (i >> 1) as u8 } else { 0 };
i += 1;
}
while i < 512 {
padding[i] = if i & 1 == 0 { 63 } else { 0 };
i += 1;
}
padding
}
#[derive(Debug, Clone, PartialEq)]
pub struct GMFont {
pub name: String,
pub display_name: Option<String>,
pub em_size: FontSize,
pub bold: bool,
pub italic: bool,
pub range_start: u16,
pub charset: u8,
pub anti_alias: u8,
pub range_end: u32,
pub texture: GMRef<GMTexturePageItem>,
pub scale_x: f32,
pub scale_y: f32,
pub ascender_offset: Option<i32>,
pub ascender: Option<u32>,
pub sdf_spread: Option<u32>,
pub line_height: Option<u32>,
pub glyphs: Vec<Glyph>,
}
impl GMElement for GMFont {
fn deserialize(reader: &mut DataReader) -> Result<Self> {
let name: String = reader.read_gm_string()?;
let display_name: Option<String> = reader.read_gm_string_opt()?;
let em_size = reader.read_u32()?; let em_size: FontSize = if em_size & (1 << 31) != 0 {
FontSize::Float(-f32::from_bits(em_size))
} else {
FontSize::Int(em_size)
};
let bold = reader.read_bool32()?;
let italic = reader.read_bool32()?;
let range_start = reader.read_u16()?;
let charset = reader.read_u8()?;
let anti_alias = reader.read_u8()?;
let range_end = reader.read_u32()?;
let texture: GMRef<GMTexturePageItem> = reader.read_gm_texture()?;
let scale_x: f32 = reader.read_f32()?;
let scale_y: f32 = reader.read_f32()?;
let ascender_offset: Option<i32> = reader.deserialize_if_wad_version(17)?;
let ascender: Option<u32> = reader.deserialize_if_gm_version((2022, 2))?;
let sdf_spread: Option<u32> =
reader.deserialize_if_gm_version((2023, 2, LTSBranch::PostLTS))?;
let line_height: Option<u32> = reader.deserialize_if_gm_version((2023, 6))?;
let glyphs: Vec<Glyph> = reader.read_pointer_list()?;
if reader.general_info.is_version_at_least((2024, 14)) {
reader.align(4)?;
}
Ok(Self {
name,
display_name,
em_size,
bold,
italic,
range_start,
charset,
anti_alias,
range_end,
texture,
scale_x,
scale_y,
ascender_offset,
ascender,
sdf_spread,
line_height,
glyphs,
})
}
fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
builder.write_gm_string(&self.name);
builder.write_gm_string_opt(&self.display_name);
match self.em_size {
FontSize::Float(value) => builder.write_f32(-value),
FontSize::Int(value) => builder.write_u32(value),
}
builder.write_bool32(self.bold);
builder.write_bool32(self.italic);
builder.write_u16(self.range_start);
builder.write_u8(self.charset);
builder.write_u8(self.anti_alias);
builder.write_u32(self.range_end);
builder.write_gm_texture(self.texture)?;
builder.write_f32(self.scale_x);
builder.write_f32(self.scale_y);
builder.write_if_wad_ver(&self.ascender_offset, "Ascender Offset", 17)?;
builder.write_if_ver(&self.ascender, "Ascender", (2022, 2))?;
builder.write_if_ver(
&self.sdf_spread,
"SDF Spread",
(2023, 2, LTSBranch::PostLTS),
)?;
builder.write_if_ver(&self.line_height, "Line Height", (2023, 6))?;
builder.write_pointer_list(&self.glyphs)?;
if builder.is_version_at_least((2024, 14)) {
builder.align(4);
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FontSize {
Float(f32),
Int(u32),
}