use crate::schema::GenericFamily;
use std::rc::Rc;
pub const FONT_REF_LEN: usize = 12;
pub const FONT_REF_KIND_AT: usize = 0;
pub const FONT_REF_VALUE_AT: usize = 4;
pub const FONT_REF_LENGTH_AT: usize = 8;
pub const FONT_REF_ASSET: u32 = 1;
pub const FONT_REF_SYSTEM: u32 = 2;
pub const FONT_REF_GENERIC: u32 = 3;
pub const MAX_FONT_REFS: usize = 8;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FontRef {
Asset(u32),
System(String),
Generic(GenericFamily),
}
#[derive(Clone, Default, PartialEq)]
pub struct FontRefs {
bytes: Option<Rc<[u8]>>,
}
impl std::fmt::Debug for FontRefs {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_list().entries(self.refs()).finish()
}
}
impl FontRefs {
pub fn new(refs: impl IntoIterator<Item = FontRef>) -> Self {
let refs: Vec<FontRef> = refs.into_iter().take(MAX_FONT_REFS).collect();
if refs.is_empty() {
return Self::default();
}
let mut bytes = Vec::with_capacity(4 + refs.len() * FONT_REF_LEN);
bytes.extend_from_slice(&(refs.len() as u32).to_le_bytes());
let names_at = 4 + refs.len() * FONT_REF_LEN;
write_records(&refs, &mut bytes, names_at as u32);
Self {
bytes: Some(bytes.into()),
}
}
pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
let bytes = bytes.into();
Self {
bytes: (!bytes.is_empty()).then_some(bytes),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.bytes.as_deref().unwrap_or(&[])
}
pub fn is_empty(&self) -> bool {
self.bytes.is_none()
}
pub fn len(&self) -> usize {
let bytes = self.as_bytes();
if bytes.len() < 4 {
return 0;
}
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize
}
pub fn refs(&self) -> Vec<FontRef> {
let bytes = self.as_bytes();
(0..self.len())
.filter_map(|index| decode_record(bytes, 4 + index * FONT_REF_LEN))
.collect()
}
pub(crate) fn write_relocated(&self, out: &mut Vec<u8>, base: u32) {
write_records(&self.refs(), out, base);
}
}
fn write_records(refs: &[FontRef], out: &mut Vec<u8>, names_base: u32) {
let mut names: Vec<u8> = Vec::new();
for font in refs {
let (kind, value, length) = match font {
FontRef::Asset(id) => (FONT_REF_ASSET, *id, 0),
FontRef::System(name) => {
let at = names_base + names.len() as u32;
names.extend_from_slice(name.as_bytes());
(FONT_REF_SYSTEM, at, name.len() as u32)
}
FontRef::Generic(family) => (FONT_REF_GENERIC, u32::from(u16::from(*family)), 0),
};
out.extend_from_slice(&kind.to_le_bytes());
out.extend_from_slice(&value.to_le_bytes());
out.extend_from_slice(&length.to_le_bytes());
}
out.extend_from_slice(&names);
}
pub(crate) fn decode_record(bytes: &[u8], at: usize) -> Option<FontRef> {
let word = |offset: usize| -> Option<u32> {
let slice = bytes.get(at + offset..at + offset + 4)?;
Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
};
let value = word(FONT_REF_VALUE_AT)?;
match word(FONT_REF_KIND_AT)? {
FONT_REF_ASSET => Some(FontRef::Asset(value)),
FONT_REF_SYSTEM => {
let length = word(FONT_REF_LENGTH_AT)? as usize;
let name = bytes.get(value as usize..value as usize + length)?;
std::str::from_utf8(name)
.ok()
.map(|name| FontRef::System(name.to_owned()))
}
FONT_REF_GENERIC => u16::try_from(value)
.ok()
.and_then(|tag| GenericFamily::try_from(tag).ok())
.map(FontRef::Generic),
_ => None,
}
}
pub const FONT_STACK_MAGIC: [u8; 4] = *b"CRFS";
pub const FONT_STACK_VERSION: u32 = 1;
pub const MAX_FONT_STACK_FAMILIES: usize = 8;
pub const MAX_FONT_STACK_FACES: usize = 18;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FontStack {
families: Vec<Vec<u32>>,
}
impl FontStack {
pub fn new() -> Self {
Self::default()
}
pub fn family(mut self, faces: impl IntoIterator<Item = u32>) -> Self {
let faces: Vec<u32> = faces.into_iter().take(MAX_FONT_STACK_FACES).collect();
if !faces.is_empty() && self.families.len() < MAX_FONT_STACK_FAMILIES {
self.families.push(faces);
}
self
}
pub fn families(&self) -> &[Vec<u32>] {
&self.families
}
pub fn to_bytes(&self) -> Vec<u8> {
let faces: usize = self.families.iter().map(Vec::len).sum();
let mut out = Vec::with_capacity(12 + 4 * (self.families.len() + faces));
out.extend_from_slice(&FONT_STACK_MAGIC);
out.extend_from_slice(&FONT_STACK_VERSION.to_le_bytes());
out.extend_from_slice(&(self.families.len() as u32).to_le_bytes());
for family in &self.families {
out.extend_from_slice(&(family.len() as u32).to_le_bytes());
for face in family {
out.extend_from_slice(&face.to_le_bytes());
}
}
out
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
let word = |at: usize| -> Option<u32> {
let slice = bytes.get(at..at + 4)?;
Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
};
if bytes.get(..4)? != FONT_STACK_MAGIC || word(4)? != FONT_STACK_VERSION {
return None;
}
let count = word(8)? as usize;
if count > MAX_FONT_STACK_FAMILIES {
return None;
}
let mut at = 12;
let mut families = Vec::with_capacity(count);
for _ in 0..count {
let faces = word(at)? as usize;
if faces == 0 || faces > MAX_FONT_STACK_FACES {
return None;
}
at += 4;
let mut family = Vec::with_capacity(faces);
for _ in 0..faces {
family.push(word(at)?);
at += 4;
}
families.push(family);
}
(at == bytes.len()).then_some(Self { families })
}
pub fn register(&self) -> u32 {
crate::asset::register_owned(crate::schema::AssetKind::Font, self.to_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fr40_a_font_list_round_trips_in_order() {
let list = FontRefs::new([
FontRef::Asset(7),
FontRef::System("JetBrains Mono".to_owned()),
FontRef::Generic(GenericFamily::Monospace),
]);
assert_eq!(
list.refs(),
vec![
FontRef::Asset(7),
FontRef::System("JetBrains Mono".to_owned()),
FontRef::Generic(GenericFamily::Monospace),
]
);
assert_eq!(list.len(), 3);
}
#[test]
fn fr40_a_font_list_holds_at_most_eight() {
let list = FontRefs::new((0..12).map(FontRef::Asset));
assert_eq!(list.len(), MAX_FONT_REFS);
assert_eq!(list.refs().last(), Some(&FontRef::Asset(7)));
}
#[test]
fn fr40_an_empty_font_list_travels_as_nothing() {
assert!(FontRefs::new([]).is_empty());
assert!(FontRefs::new([]).as_bytes().is_empty());
}
#[test]
fn fr52_a_font_stack_round_trips_in_order() {
let stack = FontStack::new().family([1, 2, 3]).family([4]).family([]);
assert_eq!(stack.families(), &[vec![1, 2, 3], vec![4]]);
let bytes = stack.to_bytes();
assert_eq!(&bytes[..4], b"CRFS");
assert_eq!(FontStack::from_bytes(&bytes), Some(stack));
}
#[test]
fn fr52_font_stack_bytes_are_never_a_font_file() {
let fonts: [&[u8]; 6] = [&[0, 1, 0, 0], b"OTTO", b"true", b"ttcf", b"wOFF", b"wOF2"];
for font in fonts {
assert_ne!(font, &FONT_STACK_MAGIC[..]);
assert_eq!(FontStack::from_bytes(font), None);
}
let mut bytes = FontStack::new().family([1]).to_bytes();
bytes.push(0);
assert_eq!(
FontStack::from_bytes(&bytes),
None,
"trailing bytes are not a stack"
);
}
#[test]
fn fr52_a_font_stack_holds_at_most_eight_families() {
let stack = (0..12).fold(FontStack::new(), |stack, id| stack.family([id]));
assert_eq!(stack.families().len(), MAX_FONT_STACK_FAMILIES);
let wide = FontStack::new().family(0..40);
assert_eq!(wide.families()[0].len(), MAX_FONT_STACK_FACES);
}
#[test]
fn fr52_the_same_font_stack_registers_once() {
crate::asset::reset_assets();
let stack = FontStack::new().family([1, 2]).family([3]);
let first = stack.register();
assert_eq!(stack.register(), first);
assert_ne!(FontStack::new().family([3]).register(), first);
crate::asset::reset_assets();
}
}