1use super::Font;
4use crate::{
5 tables::{
6 avar::Avar,
7 fvar::Fvar,
8 layout::{Condition, FeatureVariations},
9 },
10 TableProvider,
11};
12use alloc::vec::Vec;
13use core::{
14 str::FromStr,
15 sync::atomic::{self, AtomicU32},
16};
17use types::{Fixed, Tag};
18
19pub struct FontInstance {
21 font: Font,
22 size: Option<f32>,
23 coords: CoordStorage,
24 feature_vars: FeatureVarsStorage,
25}
26
27impl FontInstance {
28 pub fn builder(font: &Font) -> FontInstanceBuilder {
30 FontInstanceBuilder {
31 instance: Self {
32 font: font.clone(),
33 size: None,
34 coords: CoordStorage::default(),
35 feature_vars: FeatureVarsStorage::new(),
36 },
37 }
38 }
39
40 pub fn font(&self) -> &Font {
42 &self.font
43 }
44
45 pub fn size(&self) -> Option<f32> {
47 self.size
48 }
49
50 pub fn normalized_coords(&self) -> &[NormalizedCoord] {
52 self.coords.as_slice()
53 }
54
55 pub fn feature_variations(&self) -> FontFeatureVariations {
57 self.feature_vars.load(&self.font, self.coords.as_slice())
58 }
59}
60
61impl core::ops::Deref for FontInstance {
62 type Target = Font;
63 fn deref(&self) -> &Font {
64 self.font()
65 }
66}
67
68pub struct FontInstanceBuilder {
70 instance: FontInstance,
71}
72
73impl FontInstanceBuilder {
74 pub fn size(mut self, size: Option<f32>) -> Self {
78 self.instance.size = size;
79 self
80 }
81
82 pub fn variations<V>(mut self, variations: V) -> Self
90 where
91 V: IntoIterator,
92 V::Item: Into<FontVariation>,
93 {
94 self.set_variations(variations);
95 self
96 }
97
98 pub fn normalized_coords(mut self, coords: impl IntoIterator<Item = NormalizedCoord>) -> Self {
108 self.set_coords(coords);
109 self
110 }
111
112 pub fn named_instance(mut self, index: usize) -> Self {
119 self.set_named_instance(index);
120 self
121 }
122
123 pub fn named_instance_with_overrides<V>(mut self, index: usize, overrides: V) -> Self
131 where
132 V: IntoIterator,
133 V::Item: Into<FontVariation>,
134 {
135 self.set_named_instance_with_overrides(index, overrides);
136 self
137 }
138
139 pub fn build(self) -> FontInstance {
141 self.instance
142 }
143}
144
145impl FontInstanceBuilder {
146 fn set_variations<V>(&mut self, variations: V)
147 where
148 V: IntoIterator,
149 V::Item: Into<FontVariation>,
150 {
151 let tables = self.instance.font.tables();
152 if let Ok(fvar) = tables.fvar() {
153 set_variations(
154 &fvar,
155 tables.avar().ok(),
156 &mut self.instance.coords,
157 variations,
158 );
159 } else {
160 self.instance.coords.resize(0);
161 }
162 }
163
164 fn set_coords(&mut self, coords: impl IntoIterator<Item = NormalizedCoord>) {
165 if let Ok(fvar) = self.instance.font.tables().fvar() {
166 let count = fvar.axis_count() as usize;
167 self.instance.coords.resize(count);
168 for (dst, src) in self.instance.coords.as_mut_slice().iter_mut().zip(
169 coords
170 .into_iter()
171 .chain(core::iter::repeat(NormalizedCoord::ZERO)),
172 ) {
173 *dst = src;
174 }
175 self.instance.coords.clear_if_all_zeroes();
176 } else {
177 self.instance.coords.resize(0);
178 }
179 }
180
181 fn set_named_instance(&mut self, index: usize) {
182 let tables = self.instance.font.tables();
183 if let Ok(fvar) = tables.fvar() {
184 set_variations(
185 &fvar,
186 tables.avar().ok(),
187 &mut self.instance.coords,
188 named_instance_variations(&fvar, index),
189 );
190 } else {
191 self.instance.coords.resize(0);
192 }
193 }
194
195 fn set_named_instance_with_overrides<V>(&mut self, index: usize, overrides: V)
196 where
197 V: IntoIterator,
198 V::Item: Into<FontVariation>,
199 {
200 let tables = self.instance.font.tables();
201 if let Ok(fvar) = tables.fvar() {
202 set_variations(
203 &fvar,
204 tables.avar().ok(),
205 &mut self.instance.coords,
206 named_instance_variations(&fvar, index)
207 .chain(overrides.into_iter().map(Into::into)),
208 );
209 } else {
210 self.instance.coords.resize(0);
211 }
212 }
213}
214
215fn named_instance_variations<'a>(
217 fvar: &'a Fvar,
218 index: usize,
219) -> impl Iterator<Item = FontVariation> + 'a {
220 fvar.axis_instance_arrays()
221 .ok()
222 .and_then(|arrays| {
223 let axes = arrays.axes();
224 arrays.instances().get(index).ok().map(|instance| {
225 axes.iter()
226 .zip(instance.coordinates)
227 .map(|(axis, coord)| FontVariation::new(axis.axis_tag(), coord.get().to_f32()))
228 })
229 })
230 .into_iter()
231 .flatten()
232}
233
234fn set_variations<V>(fvar: &Fvar, avar: Option<Avar>, coords: &mut CoordStorage, variations: V)
238where
239 V: IntoIterator,
240 V::Item: Into<FontVariation>,
241{
242 coords.resize(fvar.axis_count() as usize);
243 fvar.user_to_normalized(
244 avar.as_ref(),
245 variations
246 .into_iter()
247 .map(Into::into)
248 .map(|var| (var.tag, Fixed::from_f64(var.value as _))),
249 coords.as_mut_slice(),
250 );
251 coords.clear_if_all_zeroes();
252}
253
254pub type NormalizedCoord = types::F2Dot14;
257
258#[derive(Copy, Clone, PartialEq, Debug)]
263pub struct FontVariation {
264 pub tag: Tag,
266 pub value: f32,
268}
269
270impl FontVariation {
271 pub fn new(tag: Tag, value: f32) -> Self {
273 Self { tag, value }
274 }
275}
276
277impl From<&'_ FontVariation> for FontVariation {
283 fn from(value: &'_ FontVariation) -> Self {
284 *value
285 }
286}
287
288impl From<(Tag, f32)> for FontVariation {
289 fn from(value: (Tag, f32)) -> Self {
290 Self::new(value.0, value.1)
291 }
292}
293
294impl From<&(Tag, f32)> for FontVariation {
295 fn from(value: &(Tag, f32)) -> Self {
296 Self::new(value.0, value.1)
297 }
298}
299
300impl From<(&str, f32)> for FontVariation {
301 fn from(value: (&str, f32)) -> Self {
302 Self::new(Tag::from_str(value.0).unwrap_or_default(), value.1)
303 }
304}
305
306impl From<&(&str, f32)> for FontVariation {
307 fn from(value: &(&str, f32)) -> Self {
308 Self::new(Tag::from_str(value.0).unwrap_or_default(), value.1)
309 }
310}
311
312const MAX_INLINE_COORDS: usize = 15;
315
316enum CoordStorage {
317 None,
318 Inline([NormalizedCoord; MAX_INLINE_COORDS], u8),
319 Heap(Vec<NormalizedCoord>),
320}
321
322impl Default for CoordStorage {
323 fn default() -> Self {
324 Self::None
325 }
326}
327
328impl CoordStorage {
329 fn clear_if_all_zeroes(&mut self) {
333 match self {
334 Self::None => {}
335 Self::Inline(coords, len) => {
336 if coords[..*len as usize]
337 .iter()
338 .all(|&c| c == NormalizedCoord::ZERO)
339 {
340 *len = 0;
341 }
342 }
343 Self::Heap(heap) => {
344 if heap.iter().all(|&c| c == NormalizedCoord::ZERO) {
345 heap.clear();
346 }
347 }
348 }
349 }
350
351 fn resize(&mut self, new_len: usize) {
352 match self {
353 Self::None => {
354 if new_len > MAX_INLINE_COORDS {
355 let mut heap = Vec::with_capacity(new_len);
356 heap.resize(new_len, NormalizedCoord::ZERO);
357 *self = Self::Heap(heap);
358 } else {
359 *self = Self::Inline([NormalizedCoord::ZERO; MAX_INLINE_COORDS], new_len as u8);
360 }
361 }
362 Self::Inline(_, len) => {
363 if new_len > MAX_INLINE_COORDS {
364 let mut heap = Vec::with_capacity(new_len);
365 heap.resize(new_len, NormalizedCoord::ZERO);
366 *self = Self::Heap(heap);
367 } else {
368 *len = new_len as u8;
369 }
370 }
371 Self::Heap(heap) => {
372 heap.resize(new_len, NormalizedCoord::ZERO);
373 }
374 }
375 }
376
377 fn as_slice(&self) -> &[NormalizedCoord] {
378 match self {
379 Self::None => &[],
380 Self::Inline(coords, len) => &coords[..*len as usize],
381 Self::Heap(heap) => heap.as_slice(),
382 }
383 }
384
385 fn as_mut_slice(&mut self) -> &mut [NormalizedCoord] {
386 match self {
387 Self::None => &mut [],
388 Self::Inline(coords, len) => &mut coords[..*len as usize],
389 Self::Heap(heap) => heap.as_mut_slice(),
390 }
391 }
392}
393
394#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
396pub struct FontFeatureVariations {
397 gsub: Option<u32>,
398 gpos: Option<u32>,
399}
400
401impl FontFeatureVariations {
402 pub fn gsub(&self) -> Option<u32> {
404 self.gsub
405 }
406
407 pub fn gpos(&self) -> Option<u32> {
409 self.gpos
410 }
411}
412
413struct FeatureVarsStorage {
417 status: AtomicU32,
418 gsub: AtomicU32,
419 gpos: AtomicU32,
420}
421
422impl Default for FeatureVarsStorage {
423 fn default() -> Self {
424 Self::new()
425 }
426}
427
428impl FeatureVarsStorage {
429 const UNCHECKED: u32 = 0;
431 const PRESENT: u32 = 1;
433 const ABSENT: u32 = 2;
435 const BOTH_ABSENT: u32 = Self::ABSENT | (Self::ABSENT << Self::GPOS_SHIFT);
437 const GPOS_SHIFT: u32 = 16;
440
441 fn new() -> Self {
442 Self {
443 status: AtomicU32::new(Self::UNCHECKED),
444 gsub: AtomicU32::new(0),
445 gpos: AtomicU32::new(0),
446 }
447 }
448
449 fn load(&self, font: &Font, coords: &[NormalizedCoord]) -> FontFeatureVariations {
450 let mut status = self.status.load(atomic::Ordering::Acquire);
451 if status == Self::UNCHECKED {
452 let tables = font.tables();
453 let feature_var_tables = [
454 tables
455 .gsub()
456 .ok()
457 .and_then(|gsub| gsub.feature_variations().transpose().ok().flatten()),
458 tables
459 .gpos()
460 .ok()
461 .and_then(|gpos| gpos.feature_variations().transpose().ok().flatten()),
462 ];
463 for (i, (table, state)) in feature_var_tables
464 .iter()
465 .zip([&self.gsub, &self.gpos])
466 .enumerate()
467 {
468 let mut table_status = Self::ABSENT;
469 if let Some(table) = table {
470 if let Some(index) = feature_variation_index(table, coords) {
471 state.store(index, atomic::Ordering::Release);
472 table_status = Self::PRESENT;
473 }
474 }
475 status |= table_status << (i * Self::GPOS_SHIFT as usize);
476 }
477 self.status.store(status, atomic::Ordering::Release);
478 }
479 if status != Self::BOTH_ABSENT {
480 let gsub_status = status & 0xFFFF;
481 let gpos_status = (status >> Self::GPOS_SHIFT) & 0xFFFF;
482 FontFeatureVariations {
483 gsub: (gsub_status == Self::PRESENT)
484 .then(|| self.gsub.load(atomic::Ordering::Acquire)),
485 gpos: (gpos_status == Self::PRESENT)
486 .then(|| self.gpos.load(atomic::Ordering::Acquire)),
487 }
488 } else {
489 FontFeatureVariations::default()
490 }
491 }
492}
493
494pub(crate) fn feature_variation_index(
495 feature_vars: &FeatureVariations,
496 coords: &[NormalizedCoord],
497) -> Option<u32> {
498 for (index, rec) in feature_vars.feature_variation_records().iter().enumerate() {
499 if rec.condition_set_offset().is_null() {
502 return Some(index as u32);
503 }
504 let Some(Ok(condition_set)) = rec.condition_set(feature_vars.offset_data()) else {
505 continue;
506 };
507 if condition_set
509 .conditions()
510 .iter()
511 .filter_map(Result::ok)
513 .all(|cond| match cond {
514 Condition::Format1AxisRange(format1) => {
515 let coord = coords
516 .get(format1.axis_index() as usize)
517 .copied()
518 .unwrap_or_default();
519 coord >= format1.filter_range_min_value()
520 && coord <= format1.filter_range_max_value()
521 }
522 _ => false,
523 })
524 {
525 return Some(index as u32);
526 }
527 }
528 None
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 use core::sync::atomic::Ordering;
535
536 #[test]
537 fn named_instances() {
538 let font = Font::new(font_test_data::CANTARELL_VF_TRIMMED, 0).unwrap();
539 let cases = [
540 (0, 100.0),
542 (1, 300.0),
543 (2, 400.0),
544 (3, 700.0),
545 (4, 800.0),
546 ];
547 for (index, weight) in cases {
548 let named_instance = FontInstance::builder(&font).named_instance(index).build();
549 let var_instance = FontInstance::builder(&font)
550 .variations([("wght", weight)])
551 .build();
552 assert_eq!(
553 named_instance.normalized_coords(),
554 var_instance.normalized_coords(),
555 "index={index}"
556 );
557 }
558 let invalid_instance = FontInstance::builder(&font).named_instance(5).build();
560 assert!(
561 invalid_instance.normalized_coords().is_empty(),
562 "out of bounds index should give default instance"
563 );
564 }
565
566 #[test]
567 fn named_instance_with_overrides_override_named_value() {
568 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
569 let actual = FontInstance::builder(&font)
570 .named_instance_with_overrides(3, [("FILL", 1.0)])
571 .build();
572 let expected = FontInstance::builder(&font)
573 .variations([
574 ("FILL", 1.0),
575 ("GRAD", 0.0),
576 ("opsz", 24.0),
577 ("wght", 400.0),
578 ])
579 .build();
580 assert_eq!(actual.normalized_coords(), expected.normalized_coords());
581 }
582
583 #[test]
584 fn named_instance_with_overrides_invalid_index_uses_overrides_only() {
585 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
586 let actual = FontInstance::builder(&font)
587 .named_instance_with_overrides(999, [("FILL", 1.0), ("ZZZZ", 123.0)])
588 .build();
589 let expected = FontInstance::builder(&font)
590 .variations([("FILL", 1.0), ("ZZZZ", 123.0)])
591 .build();
592 assert_eq!(actual.normalized_coords(), expected.normalized_coords());
593 assert_eq!(actual.normalized_coords().len(), 4);
594 }
595
596 #[test]
597 fn named_instance_with_overrides_overwrites_previous_settings() {
598 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
599 let actual = FontInstance::builder(&font)
600 .variations([("FILL", 0.0), ("wght", 100.0)])
601 .named_instance_with_overrides(5, [("GRAD", -25.0)])
602 .build();
603 let expected = FontInstance::builder(&font)
604 .variations([
605 ("FILL", 0.0),
606 ("GRAD", -25.0),
607 ("opsz", 24.0),
608 ("wght", 600.0),
609 ])
610 .build();
611 assert_eq!(actual.normalized_coords(), expected.normalized_coords());
612 }
613
614 #[test]
615 fn feature_variations() {
616 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
617 let cases = [
618 (0.0, [None, None]),
620 (0.5, [None, None]),
621 (0.98, [None, None]),
622 (0.99, [Some(0), None]),
623 (1.0, [Some(0), None]),
624 ];
625 for (fill, [gsub, gpos]) in cases {
626 let instance = FontInstance::builder(&font)
627 .variations([("FILL", fill)])
628 .build();
629 let feature_vars = instance.feature_variations();
630 let actual = [feature_vars.gsub(), feature_vars.gpos()];
631 assert_eq!(actual, [gsub, gpos], "fill={fill}");
632 }
633 }
634
635 #[test]
636 fn feature_variation_cache_marks_both_absent() {
637 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
638 let instance = FontInstance::builder(&font)
639 .variations([("FILL", 0.5)])
640 .build();
641 assert_eq!(instance.feature_vars.status.load(Ordering::Acquire), 0);
642 assert_eq!(
643 instance.feature_variations(),
644 FontFeatureVariations::default()
645 );
646 assert_eq!(
647 instance.feature_vars.status.load(Ordering::Acquire),
648 FeatureVarsStorage::BOTH_ABSENT
649 );
650 }
651
652 #[test]
653 fn feature_variation_cache_is_thread_safe_and_stable() {
654 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
655 let instance = FontInstance::builder(&font)
656 .variations([("FILL", 1.0)])
657 .build();
658 std::thread::scope(|scope| {
659 for _ in 0..8 {
660 scope.spawn(|| {
661 for _ in 0..64 {
662 let vars = instance.feature_variations();
663 assert_eq!(
664 vars,
665 FontFeatureVariations {
666 gsub: Some(0),
667 gpos: None
668 }
669 );
670 }
671 });
672 }
673 });
674 let status = instance.feature_vars.status.load(Ordering::Acquire);
675 assert_eq!(status & 0xFFFF, FeatureVarsStorage::PRESENT);
676 assert_eq!(
677 (status >> FeatureVarsStorage::GPOS_SHIFT) & 0xFFFF,
678 FeatureVarsStorage::ABSENT
679 );
680 assert_eq!(instance.feature_vars.gsub.load(Ordering::Acquire), 0);
681 }
682
683 #[test]
684 fn variations_last_value_wins_and_unknown_axis_ignored() {
685 let font = Font::new(font_test_data::CANTARELL_VF_TRIMMED, 0).unwrap();
686 let expected = FontInstance::builder(&font)
687 .variations([("wght", 700.0)])
688 .build();
689 let repeated_axis = FontInstance::builder(&font)
690 .variations([("wght", 100.0), ("wght", 700.0)])
691 .build();
692 assert_eq!(
693 repeated_axis.normalized_coords(),
694 expected.normalized_coords()
695 );
696 let unknown_axis = FontInstance::builder(&font)
697 .variations([("wght", 700.0), ("ZZZZ", 123.0)])
698 .build();
699 assert_eq!(
700 unknown_axis.normalized_coords(),
701 expected.normalized_coords()
702 );
703 }
704
705 #[test]
706 fn later_variation_call_overwrites_previous() {
707 let font = Font::new(font_test_data::CANTARELL_VF_TRIMMED, 0).unwrap();
708 let overwritten = FontInstance::builder(&font)
709 .variations([("wght", 700.0)])
710 .variations([("wght", 100.0)])
711 .build();
712 let expected = FontInstance::builder(&font)
713 .variations([("wght", 100.0)])
714 .build();
715 assert_eq!(
716 overwritten.normalized_coords(),
717 expected.normalized_coords()
718 );
719 }
720
721 #[test]
722 fn normalized_coords_empty_resets_to_default_instance() {
723 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
724 let instance = FontInstance::builder(&font).normalized_coords([]).build();
725 assert!(instance.normalized_coords().is_empty());
726 }
727
728 #[test]
729 fn normalized_coords_truncates_and_pads() {
730 let font = Font::new(font_test_data::MATERIAL_SYMBOLS_SUBSET, 0).unwrap();
731 let axis_count = font.tables().fvar().unwrap().axis_count() as usize;
732 let values = [0.25, -0.5, 1.0, 0.75, -0.25].map(NormalizedCoord::from_f32);
733 let instance = FontInstance::builder(&font)
734 .normalized_coords(values)
735 .build();
736 let coords = instance.normalized_coords();
737 assert_eq!(coords.len(), axis_count);
738 let copied = values.len().min(axis_count);
739 assert_eq!(&coords[..copied], &values[..copied]);
740 assert!(coords[copied..]
741 .iter()
742 .all(|&coord| coord == NormalizedCoord::ZERO));
743 }
744}