use std::io::{Cursor, SeekFrom};
use crate::mibl::Mibl;
use crate::{parse_offset32_count32, parse_ptr32, parse_vec};
use binrw::file_ptr::FilePtrArgs;
use binrw::{BinRead, BinResult, binread};
use xc3_write::{Xc3Write, Xc3WriteOffsets};
const VERSION: u32 = 10001;
#[binread]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Xc3Write, PartialEq, Clone)]
#[br(magic = b"LAFT")]
#[xc3(magic(b"LAFT"))]
pub struct Laft {
#[br(assert(version == VERSION), pad_size_to(8))]
#[xc3(pad_size_to(8))]
version: u32,
#[br(parse_with = parse_offset32_glyph_count)]
#[xc3(offset(u32))]
pub font_info: Vec<GlyphFontInfo>,
#[br(parse_with = parse_offset32_count32)]
#[xc3(offset_count(u32, u32))]
pub offsets: Vec<u16>,
#[br(temp, restore_position)]
offset: u32,
#[br(parse_with = parse_offset32_count32)]
#[xc3(offset_count(u32, u32))]
pub mappings: Vec<GlyphClass>,
pub glyph_class_mask: u32,
#[br(parse_with = parse_opt_ptr32_limited)]
#[xc3(offset_size(u32, u32), align(4096))]
pub texture: Option<Mibl>,
#[br(parse_with = parse_ptr32)]
#[xc3(offset(u32))]
pub settings: FontSettings,
pub global_width_reduction: u32,
pub line_height: u32,
#[br(count = (offset - 56) / 4)]
pub unks: Vec<u32>,
}
#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct FontSettings {
pub texture_width: u32,
pub texture_height: u32,
pub glyph_area_width: u32,
pub glyph_area_height: u32,
pub glyphs_per_row: u32,
pub num_rows: u32,
}
#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone, Copy, Default)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct GlyphClass {
pub representative_offset: u16,
pub size: u16,
}
#[derive(Debug, BinRead, Xc3Write, Xc3WriteOffsets, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct GlyphFontInfo {
pub codepoint: u16,
pub left_x: u8,
pub width: u8,
}
impl Laft {
pub fn new(settings: FontSettings, max_mappings: usize) -> Self {
assert!(max_mappings != 0 && max_mappings.is_power_of_two());
Self {
version: VERSION,
glyph_class_mask: (max_mappings - 1).try_into().unwrap(),
global_width_reduction: 0,
line_height: settings.glyph_area_height / 2,
mappings: vec![Default::default(); max_mappings],
offsets: Vec::new(),
font_info: Vec::new(),
settings,
texture: None,
unks: vec![0; 7],
}
}
pub fn get_glyph(&self, codepoint: u16) -> Option<(usize, GlyphFontInfo)> {
let mapping = self.mappings[(codepoint as u32 & self.glyph_class_mask) as usize];
if mapping.size == 0 {
return None;
}
let offset = mapping.representative_offset as usize;
let offset = offset
+ self.offsets[offset..offset + mapping.size as usize]
.binary_search_by_key(&codepoint, |ofs| self.font_info[*ofs as usize].codepoint)
.ok()?;
let grid_pos = self.offsets[offset] as usize;
Some((grid_pos, self.font_info.get(grid_pos).copied()?))
}
pub fn register_glyph(&mut self, font_info: GlyphFontInfo) {
let mapping =
&mut self.mappings[(font_info.codepoint as u32 & self.glyph_class_mask) as usize];
let font_offset: u16 = self.font_info.len().try_into().unwrap();
self.font_info.push(font_info);
mapping.size += 1;
if mapping.size > 1 {
let old_offset = mapping.representative_offset as usize;
let next_idx = old_offset
+ self.offsets[old_offset..old_offset + (mapping.size - 1) as usize]
.binary_search_by_key(&font_info.codepoint, |ofs| {
self.font_info[*ofs as usize].codepoint
})
.expect_err("glyph already registered");
self.offsets.insert(next_idx, font_offset);
for mapping in &mut self.mappings {
if mapping.representative_offset as usize > old_offset {
mapping.representative_offset += 1;
}
}
} else {
mapping.representative_offset = self.offsets.len().try_into().unwrap();
self.offsets.push(font_offset);
}
}
}
fn parse_opt_ptr32_limited<T, R, Args>(
reader: &mut R,
endian: binrw::Endian,
args: Args,
) -> BinResult<Option<T>>
where
for<'a> T: BinRead<Args<'a> = Args> + 'static,
R: std::io::Read + std::io::Seek,
Args: Clone,
{
let offset = u32::read_options(reader, endian, ())?;
let size: usize = u32::read_options(reader, endian, ())?.try_into().unwrap();
if offset == 0 || size == 0 {
return Ok(None);
}
let pos = reader.stream_position()?;
let mut buf = vec![0; size];
reader.seek(SeekFrom::Start(offset.into()))?;
reader.read_exact(&mut buf)?;
reader.seek(SeekFrom::Start(pos))?;
T::read_options(&mut Cursor::new(buf), endian, args).map(Some)
}
fn parse_offset32_glyph_count<T, R, Args>(
reader: &mut R,
endian: binrw::Endian,
args: FilePtrArgs<Args>,
) -> BinResult<Vec<T>>
where
for<'a> T: BinRead<Args<'a> = Args> + 'static,
R: std::io::Read + std::io::Seek,
Args: Clone,
{
let pos = reader.stream_position()?;
let offset = u32::read_options(reader, endian, ())?;
reader.seek(SeekFrom::Current(4))?;
let count = u32::read_options(reader, endian, ())?;
reader.seek(SeekFrom::Current(-8))?;
if offset == 0 && count != 0 {
return Err(binrw::Error::AssertFail {
pos,
message: format!("unexpected null offset for count {count}"),
});
}
parse_vec(reader, endian, args, offset as u64, count as usize)
}
impl Xc3WriteOffsets for LaftOffsets<'_> {
type Args = ();
fn write_offsets<W: std::io::Write + std::io::Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
endian: xc3_write::Endian,
_args: Self::Args,
) -> xc3_write::Xc3Result<()> {
self.mappings
.write_full(writer, base_offset, data_ptr, endian, ())?;
self.offsets
.write_full(writer, base_offset, data_ptr, endian, ())?;
self.font_info
.write_full(writer, base_offset, data_ptr, endian, ())?;
self.settings
.write_full(writer, base_offset, data_ptr, endian, ())?;
self.texture
.write_full(writer, base_offset, data_ptr, endian, ())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{FontSettings, GlyphFontInfo, Laft};
const MAX_CODE: u16 = u16::MAX;
const KEY: u16 = 0xCAFE;
#[test]
fn glyph_register() {
let mut wifnt = Laft::new(
FontSettings {
texture_width: 0,
texture_height: 0,
glyph_area_width: 0,
glyph_area_height: 0,
glyphs_per_row: 0,
num_rows: 0,
},
512,
);
for code in (0..MAX_CODE).map(|c| c ^ KEY) {
wifnt.register_glyph(GlyphFontInfo {
codepoint: code,
left_x: 0,
width: 0,
});
}
for (i, code) in (0..MAX_CODE).map(|c| c ^ KEY).enumerate() {
let (pos, font) = wifnt.get_glyph(code).unwrap();
assert_eq!(pos, i);
assert_eq!(font.codepoint, code);
}
}
}