write_fonts/tables/
fvar.rs1#[path = "./instance_record.rs"]
4mod instance_record;
5
6pub use instance_record::InstanceRecord;
7
8include!("../../generated/generated_fvar.rs");
9
10impl Fvar {
11 fn check_instances(&self, ctx: &mut ValidationCtx) {
13 let sum: i32 = self
14 .axis_instance_arrays
15 .instances
16 .iter()
17 .map(|ir| ir.post_script_name_id.map(|_| 1).unwrap_or(-1))
18 .sum();
19 if sum.unsigned_abs() as usize != self.axis_instance_arrays.instances.len() {
20 ctx.report("All or none of the instances must have post_script_name_id set. Use Some(0xFFFF) if you need to set it where you have no value.");
21 }
22
23 let axis_count = self.axis_count();
24 let uncoordinated_instances = self
25 .axis_instance_arrays
26 .instances
27 .iter()
28 .filter(|ir| ir.coordinates.len() != axis_count as usize)
29 .count();
30 if uncoordinated_instances > 0 {
31 ctx.report(format!(
32 "{uncoordinated_instances} instances do not have axis_count ({axis_count}) coordinates",
33 ));
34 }
35 }
36
37 fn axis_count(&self) -> u16 {
38 self.axis_instance_arrays.axes.len().try_into().unwrap()
39 }
40
41 fn instance_count(&self) -> u16 {
42 self.axis_instance_arrays
43 .instances
44 .len()
45 .try_into()
46 .unwrap()
47 }
48
49 fn instance_size(&self) -> u16 {
50 let mut instance_size = self.axis_count() * Fixed::RAW_BYTE_LEN as u16 + 4;
52 if self
53 .axis_instance_arrays
54 .instances
55 .iter()
56 .any(|i| i.post_script_name_id.is_some())
57 {
58 instance_size += 2;
59 }
60 instance_size
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use read_fonts::{FontRef, TableProvider};
67
68 use super::*;
69
70 fn wdth_wght_fvar() -> Fvar {
71 let mut fvar = Fvar::default();
72
73 fvar.axis_instance_arrays.axes.push(VariationAxisRecord {
74 axis_tag: Tag::new(b"wght"),
75 min_value: Fixed::from_i32(300),
76 default_value: Fixed::from_i32(400),
77 max_value: Fixed::from_i32(700),
78 ..Default::default()
79 });
80 fvar.axis_instance_arrays.axes.push(VariationAxisRecord {
81 axis_tag: Tag::new(b"wdth"),
82 min_value: Fixed::from_f64(75.0),
83 default_value: Fixed::from_f64(100.0),
84 max_value: Fixed::from_f64(125.0),
85 ..Default::default()
86 });
87 fvar
88 }
89
90 fn assert_wdth_wght_test_values(fvar: &read_fonts::tables::fvar::Fvar) {
91 assert_eq!(fvar.version(), MajorMinor::VERSION_1_0);
92 assert_eq!(fvar.axis_count(), 2);
93 assert_eq!(
94 vec![
95 (Tag::new(b"wght"), 300.0, 400.0, 700.0),
96 (Tag::new(b"wdth"), 75.0, 100.0, 125.0),
97 ],
98 fvar.axis_instance_arrays()
99 .unwrap()
100 .axes()
101 .iter()
102 .map(|var| (
103 var.axis_tag.get(),
104 var.min_value().to_f64(),
105 var.default_value().to_f64(),
106 var.max_value().to_f64()
107 ))
108 .collect::<Vec<_>>()
109 );
110 }
111
112 fn get_only_instance(
113 fvar: read_fonts::tables::fvar::Fvar,
114 ) -> read_fonts::tables::fvar::InstanceRecord {
115 let instances = fvar.axis_instance_arrays().unwrap().instances();
116 assert_eq!(1, instances.len());
117 instances.get(0).unwrap()
118 }
119
120 fn nameless_instance_record(coordinates: Vec<Fixed>) -> InstanceRecord {
121 InstanceRecord {
122 subfamily_name_id: NameId::TYPOGRAPHIC_SUBFAMILY_NAME,
123 coordinates,
124 ..Default::default()
125 }
126 }
127
128 fn named_instance_record(coordinates: Vec<Fixed>, name_id: u16) -> InstanceRecord {
129 let mut rec = nameless_instance_record(coordinates);
130 rec.post_script_name_id = Some(NameId::new(name_id));
131 rec
132 }
133
134 #[test]
135 fn write_read_no_instances() {
136 let fvar = wdth_wght_fvar();
137 let bytes = crate::write::dump_table(&fvar).unwrap();
138 let loaded = read_fonts::tables::fvar::Fvar::read(FontData::new(&bytes)).unwrap();
139 assert_wdth_wght_test_values(&loaded);
140 }
141
142 #[test]
143 fn write_read_short_instance() {
144 let mut fvar = wdth_wght_fvar();
145 let coordinates = vec![Fixed::from_i32(420), Fixed::from_f64(101.5)];
146 fvar.axis_instance_arrays
147 .instances
148 .push(nameless_instance_record(coordinates.clone()));
149 assert_eq!(2 * 4 + 4, fvar.instance_size());
150
151 let bytes = crate::write::dump_table(&fvar).unwrap();
152 let loaded = read_fonts::tables::fvar::Fvar::read(FontData::new(&bytes)).unwrap();
153 assert_wdth_wght_test_values(&loaded);
154 assert_eq!(fvar.instance_size(), loaded.instance_size());
155
156 let instance = get_only_instance(loaded);
157 assert_eq!(None, instance.post_script_name_id);
158 assert_eq!(
159 coordinates,
160 instance
161 .coordinates
162 .iter()
163 .map(|v| v.get())
164 .collect::<Vec<_>>()
165 );
166 }
167
168 #[test]
169 fn write_read_long_instance() {
170 let mut fvar = wdth_wght_fvar();
171 let coordinates = vec![Fixed::from_i32(650), Fixed::from_i32(420)];
172 fvar.axis_instance_arrays
173 .instances
174 .push(named_instance_record(coordinates.clone(), 256));
175 assert_eq!(2 * 4 + 6, fvar.instance_size());
176
177 let bytes = crate::write::dump_table(&fvar).unwrap();
178 let loaded = read_fonts::tables::fvar::Fvar::read(FontData::new(&bytes)).unwrap();
179 assert_wdth_wght_test_values(&loaded);
180 assert_eq!(fvar.instance_size(), loaded.instance_size());
181
182 let instance = get_only_instance(loaded);
183 assert_eq!(Some(NameId::new(256)), instance.post_script_name_id);
184 assert_eq!(
185 coordinates,
186 instance
187 .coordinates
188 .iter()
189 .map(|v| v.get())
190 .collect::<Vec<_>>()
191 );
192 }
193
194 #[test]
195 fn round_trip() {
196 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
197 let read_testdata = font.fvar().unwrap();
198
199 let fvar = Fvar::from_table_ref(&read_testdata);
200 let bytes = crate::write::dump_table(&fvar).unwrap();
201 let loaded = read_fonts::tables::fvar::Fvar::read(FontData::new(&bytes)).unwrap();
202
203 assert_eq!(read_testdata.version(), loaded.version());
204 assert_eq!(read_testdata.axis_count(), loaded.axis_count());
205 }
206
207 #[test]
208 fn inconsistent_instance_size_fails() {
209 let mut fvar = wdth_wght_fvar();
210 let coordinates = vec![Fixed::from_i32(650), Fixed::from_i32(420)];
211 fvar.axis_instance_arrays
213 .instances
214 .push(nameless_instance_record(coordinates.clone()));
215 fvar.axis_instance_arrays
216 .instances
217 .push(named_instance_record(coordinates, 256));
218 assert!(fvar.validate().is_err());
219 }
220
221 #[test]
222 fn wrong_number_of_coordinates_fails() {
223 let mut fvar = wdth_wght_fvar();
224 let coordinates = vec![Fixed::from_i32(650)];
225 fvar.axis_instance_arrays
226 .instances
227 .push(nameless_instance_record(coordinates));
228 assert!(fvar.validate().is_err());
229 }
230}