use crate::runtime::composer::OwnedAttr;
use std::sync::RwLock;
pub const FONT_FEATURE_LEN: usize = 8;
pub const FONT_FEATURE_TAG_AT: usize = 0;
pub const FONT_FEATURE_VALUE_AT: usize = 4;
pub const MAX_FONT_FEATURES: usize = 16;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct FontFeature {
pub tag: [u8; 4],
pub value: u32,
}
impl FontFeature {
pub const TABULAR_NUMS: Self = Self::new(b"tnum", 1);
pub const PROPORTIONAL_NUMS: Self = Self::new(b"pnum", 1);
pub const NO_LIGATURES: Self = Self::new(b"liga", 0);
pub const SLASHED_ZERO: Self = Self::new(b"zero", 1);
pub const fn new(tag: &[u8; 4], value: u32) -> Self {
Self { tag: *tag, value }
}
pub const fn to_bytes(self) -> [u8; FONT_FEATURE_LEN] {
let value = self.value.to_le_bytes();
[
self.tag[0],
self.tag[1],
self.tag[2],
self.tag[3],
value[0],
value[1],
value[2],
value[3],
]
}
}
const MAX_KEPT_LISTS: usize = 1024;
static KEPT: RwLock<Vec<&'static [u8]>> = RwLock::new(Vec::new());
pub(crate) fn font_feature_attr(features: &[FontFeature]) -> OwnedAttr {
if features.is_empty() {
return OwnedAttr::None;
}
let mut stack = [0_u8; MAX_FONT_FEATURES * FONT_FEATURE_LEN];
let heap;
let bytes: &[u8] = if features.len() <= MAX_FONT_FEATURES {
for (index, feature) in features.iter().enumerate() {
let at = index * FONT_FEATURE_LEN;
stack[at..at + FONT_FEATURE_LEN].copy_from_slice(&feature.to_bytes());
}
&stack[..features.len() * FONT_FEATURE_LEN]
} else {
heap = font_feature_bytes(features);
&heap
};
{
let kept = KEPT.read().unwrap_or_else(|p| p.into_inner());
if let Some(found) = kept.iter().find(|list| **list == bytes) {
return OwnedAttr::Blob(found);
}
}
let mut kept = KEPT.write().unwrap_or_else(|p| p.into_inner());
if let Some(found) = kept.iter().find(|list| **list == bytes) {
return OwnedAttr::Blob(found);
}
if kept.len() >= MAX_KEPT_LISTS {
return OwnedAttr::Bytes(bytes.to_vec());
}
let leaked: &'static [u8] = Box::leak(bytes.to_vec().into_boxed_slice());
kept.push(leaked);
OwnedAttr::Blob(leaked)
}
pub fn font_feature_bytes(features: &[FontFeature]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(features.len() * FONT_FEATURE_LEN);
for feature in features {
bytes.extend_from_slice(&feature.to_bytes());
}
bytes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fr83_a_feature_record_is_its_tag_then_its_value() {
assert_eq!(FontFeature::TABULAR_NUMS.to_bytes(), *b"tnum\x01\0\0\0");
assert_eq!(
FontFeature::new(b"ss02", 0x0102_0304).to_bytes(),
[b's', b's', b'0', b'2', 4, 3, 2, 1]
);
}
#[test]
fn fr83_the_same_list_is_kept_once() {
let list = [FontFeature::new(b"cv11", 7), FontFeature::NO_LIGATURES];
let (OwnedAttr::Blob(first), OwnedAttr::Blob(second)) =
(font_feature_attr(&list), font_feature_attr(&list))
else {
panic!("a short list is kept");
};
assert!(
std::ptr::eq(first, second),
"the second write found the first"
);
assert_eq!(first, font_feature_bytes(&list).as_slice());
assert_eq!(font_feature_attr(&[]), OwnedAttr::None);
}
}