compose_rust/
font_features.rs1use crate::runtime::composer::OwnedAttr;
12use std::sync::RwLock;
13
14pub const FONT_FEATURE_LEN: usize = 8;
16pub const FONT_FEATURE_TAG_AT: usize = 0;
18pub const FONT_FEATURE_VALUE_AT: usize = 4;
19pub const MAX_FONT_FEATURES: usize = 16;
22
23#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
30pub struct FontFeature {
31 pub tag: [u8; 4],
32 pub value: u32,
33}
34
35impl FontFeature {
36 pub const TABULAR_NUMS: Self = Self::new(b"tnum", 1);
38 pub const PROPORTIONAL_NUMS: Self = Self::new(b"pnum", 1);
40 pub const NO_LIGATURES: Self = Self::new(b"liga", 0);
42 pub const SLASHED_ZERO: Self = Self::new(b"zero", 1);
44
45 pub const fn new(tag: &[u8; 4], value: u32) -> Self {
48 Self { tag: *tag, value }
49 }
50
51 pub const fn to_bytes(self) -> [u8; FONT_FEATURE_LEN] {
53 let value = self.value.to_le_bytes();
54 [
55 self.tag[0],
56 self.tag[1],
57 self.tag[2],
58 self.tag[3],
59 value[0],
60 value[1],
61 value[2],
62 value[3],
63 ]
64 }
65}
66
67const MAX_KEPT_LISTS: usize = 1024;
71
72static KEPT: RwLock<Vec<&'static [u8]>> = RwLock::new(Vec::new());
73
74pub(crate) fn font_feature_attr(features: &[FontFeature]) -> OwnedAttr {
80 if features.is_empty() {
81 return OwnedAttr::None;
82 }
83 let mut stack = [0_u8; MAX_FONT_FEATURES * FONT_FEATURE_LEN];
84 let heap;
85 let bytes: &[u8] = if features.len() <= MAX_FONT_FEATURES {
87 for (index, feature) in features.iter().enumerate() {
88 let at = index * FONT_FEATURE_LEN;
89 stack[at..at + FONT_FEATURE_LEN].copy_from_slice(&feature.to_bytes());
90 }
91 &stack[..features.len() * FONT_FEATURE_LEN]
92 } else {
93 heap = font_feature_bytes(features);
94 &heap
95 };
96 {
97 let kept = KEPT.read().unwrap_or_else(|p| p.into_inner());
98 if let Some(found) = kept.iter().find(|list| **list == bytes) {
99 return OwnedAttr::Blob(found);
100 }
101 }
102 let mut kept = KEPT.write().unwrap_or_else(|p| p.into_inner());
103 if let Some(found) = kept.iter().find(|list| **list == bytes) {
104 return OwnedAttr::Blob(found);
105 }
106 if kept.len() >= MAX_KEPT_LISTS {
107 return OwnedAttr::Bytes(bytes.to_vec());
108 }
109 let leaked: &'static [u8] = Box::leak(bytes.to_vec().into_boxed_slice());
110 kept.push(leaked);
111 OwnedAttr::Blob(leaked)
112}
113
114pub fn font_feature_bytes(features: &[FontFeature]) -> Vec<u8> {
116 let mut bytes = Vec::with_capacity(features.len() * FONT_FEATURE_LEN);
117 for feature in features {
118 bytes.extend_from_slice(&feature.to_bytes());
119 }
120 bytes
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn fr83_a_feature_record_is_its_tag_then_its_value() {
129 assert_eq!(FontFeature::TABULAR_NUMS.to_bytes(), *b"tnum\x01\0\0\0");
130 assert_eq!(
131 FontFeature::new(b"ss02", 0x0102_0304).to_bytes(),
132 [b's', b's', b'0', b'2', 4, 3, 2, 1]
133 );
134 }
135
136 #[test]
137 fn fr83_the_same_list_is_kept_once() {
138 let list = [FontFeature::new(b"cv11", 7), FontFeature::NO_LIGATURES];
139 let (OwnedAttr::Blob(first), OwnedAttr::Blob(second)) =
140 (font_feature_attr(&list), font_feature_attr(&list))
141 else {
142 panic!("a short list is kept");
143 };
144 assert!(
145 std::ptr::eq(first, second),
146 "the second write found the first"
147 );
148 assert_eq!(first, font_feature_bytes(&list).as_slice());
149 assert_eq!(font_feature_attr(&[]), OwnedAttr::None);
150 }
151}