Skip to main content

skrifa/outline/varc/
mod.rs

1//! Support for rendering variable composite glyphs from the VARC table.
2
3use read_fonts::{
4    tables::{
5        layout::Condition,
6        varc::{
7            DecomposedTransform, MultiItemVariationStore, SparseVariationRegionList, Varc,
8            VarcComponent, VarcFlags,
9        },
10        variations::NO_VARIATION_INDEX,
11    },
12    types::{F2Dot14, GlyphId, Matrix},
13    FontRef, ReadError, TableProvider,
14};
15
16use crate::{
17    collections::SmallVec,
18    instance::Size,
19    outline::{cff, glyf, metrics::GlyphHMetrics, pen::PathStyle, DrawError, OutlinePen},
20    provider::MetadataProvider,
21    GLYF_COMPOSITE_RECURSION_LIMIT,
22};
23
24#[cfg(feature = "libm")]
25#[allow(unused_imports)]
26use core_maths::CoreFloat;
27
28use super::OutlineKind;
29
30type GlyphStack = SmallVec<GlyphId, 8>;
31type CoordVec = SmallVec<F2Dot14, 64>;
32type AxisIndexVec = SmallVec<u16, 64>;
33type AxisValueVec = SmallVec<f32, 64>;
34type DeltaVec = SmallVec<f32, 64>;
35type ScalarCacheVec = SmallVec<f32, 128>;
36type Affine = Matrix<f32>;
37
38struct Scratchpad {
39    deltas: DeltaVec,
40    axis_indices: AxisIndexVec,
41    axis_values: AxisValueVec,
42}
43
44impl Scratchpad {
45    fn new() -> Self {
46        Self {
47            deltas: DeltaVec::new(),
48            axis_indices: AxisIndexVec::new(),
49            axis_values: AxisValueVec::new(),
50        }
51    }
52}
53
54struct VarcSharedContext<'a, 'b> {
55    font_coords: &'b [F2Dot14],
56    size: Size,
57    path_style: PathStyle,
58    coverage: &'b read_fonts::tables::layout::CoverageTable<'a>,
59    var_store: Option<&'b MultiItemVariationStore<'a>>,
60    store_regions: Option<(
61        &'b MultiItemVariationStore<'a>,
62        &'b SparseVariationRegionList<'a>,
63    )>,
64}
65
66#[derive(Clone)]
67enum BaseOutlines<'a> {
68    Glyf(glyf::Outlines<'a>),
69    Cff(cff::Outlines<'a>),
70}
71
72impl<'a> BaseOutlines<'a> {
73    fn glyph_count(&self) -> u32 {
74        match self {
75            Self::Glyf(glyf) => glyf.glyph_count() as u32,
76            Self::Cff(cff) => cff.glyph_count() as u32,
77        }
78    }
79
80    fn font(&self) -> &FontRef<'a> {
81        match self {
82            Self::Glyf(glyf) => &glyf.font,
83            Self::Cff(cff) => &cff.font,
84        }
85    }
86
87    fn base_outline_kind(&self, glyph_id: GlyphId) -> Option<OutlineKind<'a>> {
88        match self {
89            Self::Glyf(glyf) => Some(OutlineKind::Glyf(
90                glyf.clone(),
91                glyf.outline(glyph_id).ok()?,
92            )),
93            Self::Cff(cff) => Some(OutlineKind::Cff(
94                cff.clone(),
95                glyph_id,
96                cff.subfont_index(glyph_id),
97            )),
98        }
99    }
100
101    fn base_outline_memory(&self, glyph_id: GlyphId) -> usize {
102        match self {
103            Self::Glyf(glyf) => glyf
104                .outline(glyph_id)
105                .ok()
106                .map(|outline| outline.required_buffer_size(super::Hinting::None))
107                .unwrap_or(0),
108            Self::Cff(..) => 0,
109        }
110    }
111}
112
113#[derive(Clone)]
114pub(crate) struct Outlines<'a> {
115    varc: Varc<'a>,
116    coverage: read_fonts::tables::layout::CoverageTable<'a>,
117    var_store: Option<MultiItemVariationStore<'a>>,
118    regions: Option<SparseVariationRegionList<'a>>,
119    base: BaseOutlines<'a>,
120    glyph_metrics: GlyphHMetrics<'a>,
121    units_per_em: u16,
122    axis_count: usize,
123}
124
125#[derive(Clone, Copy)]
126pub(crate) struct Outline {
127    pub(crate) glyph_id: GlyphId,
128    pub(crate) coverage_index: u16,
129    max_component_memory: usize,
130}
131
132impl Outline {
133    pub fn required_buffer_size(&self) -> usize {
134        self.max_component_memory
135    }
136}
137
138impl<'a> Outlines<'a> {
139    pub fn new(font: &FontRef<'a>) -> Option<Self> {
140        let varc = font.varc().ok()?;
141        if let Some(glyf) = glyf::Outlines::new(font) {
142            return Self::from_base(font, varc, BaseOutlines::Glyf(glyf));
143        }
144        if let Some(cff) = cff::Outlines::new(font) {
145            return Self::from_base(font, varc, BaseOutlines::Cff(cff));
146        }
147        None
148    }
149
150    fn from_base(font: &FontRef<'a>, varc: Varc<'a>, base: BaseOutlines<'a>) -> Option<Self> {
151        let glyph_metrics = GlyphHMetrics::new(font)?;
152        let units_per_em = font.head().ok()?.units_per_em();
153        let axis_count = font.axes().len();
154        let coverage = varc.coverage().ok()?;
155        let var_store = varc.multi_var_store().transpose().ok()?;
156        let regions = var_store
157            .as_ref()
158            .map(|s| s.region_list())
159            .transpose()
160            .ok()?;
161        Some(Self {
162            varc,
163            coverage,
164            var_store,
165            regions,
166            base,
167            glyph_metrics,
168            units_per_em,
169            axis_count,
170        })
171    }
172
173    pub fn units_per_em(&self) -> u16 {
174        self.units_per_em
175    }
176
177    pub fn glyph_count(&self) -> u32 {
178        self.base.glyph_count()
179    }
180
181    pub fn prefer_interpreter(&self) -> bool {
182        false
183    }
184
185    pub fn fractional_size_hinting(&self) -> bool {
186        false
187    }
188
189    pub fn font(&self) -> &FontRef<'a> {
190        self.base.font()
191    }
192
193    pub(crate) fn fallback_outline_kind(&self, glyph_id: GlyphId) -> Option<OutlineKind<'a>> {
194        self.base.base_outline_kind(glyph_id)
195    }
196
197    pub fn outline(&self, glyph_id: GlyphId) -> Result<Option<Outline>, ReadError> {
198        let Some(coverage_index) = self.coverage.get(glyph_id) else {
199            return Ok(None);
200        };
201        let max_component_memory = self.compute_max_component_memory(glyph_id, coverage_index)?;
202        Ok(Some(Outline {
203            glyph_id,
204            coverage_index,
205            max_component_memory,
206        }))
207    }
208
209    /// Lightweight coverage lookup without computing max_component_memory.
210    fn coverage_index(&self, glyph_id: GlyphId) -> Result<Option<u16>, ReadError> {
211        Ok(self.coverage.get(glyph_id))
212    }
213
214    fn compute_max_component_memory(
215        &self,
216        glyph_id: GlyphId,
217        coverage_index: u16,
218    ) -> Result<usize, ReadError> {
219        let mut stack = GlyphStack::new();
220        self.max_component_memory_for_glyph(glyph_id, coverage_index, &mut stack)
221    }
222
223    fn max_component_memory_for_glyph(
224        &self,
225        glyph_id: GlyphId,
226        coverage_index: u16,
227        stack: &mut GlyphStack,
228    ) -> Result<usize, ReadError> {
229        if stack.contains(&glyph_id) {
230            return Ok(0);
231        }
232        if stack.len() >= GLYF_COMPOSITE_RECURSION_LIMIT {
233            return Ok(0);
234        }
235        stack.push(glyph_id);
236        let mut max_memory = 0usize;
237        let glyph = self.varc.glyph(coverage_index as usize)?;
238        for component in glyph.components() {
239            let component = component?;
240            let component_gid = component.gid();
241            let component_memory = if component_gid == glyph_id {
242                self.base.base_outline_memory(component_gid)
243            } else if let Some(coverage_index) = self.coverage_index(component_gid)? {
244                self.max_component_memory_for_glyph(component_gid, coverage_index, stack)?
245            } else {
246                self.base.base_outline_memory(component_gid)
247            };
248            max_memory = max_memory.max(component_memory);
249        }
250        stack.pop();
251        Ok(max_memory)
252    }
253
254    pub fn draw(
255        &self,
256        outline: &Outline,
257        buf: &mut [u8],
258        size: Size,
259        coords: &[F2Dot14],
260        path_style: PathStyle,
261        pen: &mut impl OutlinePen,
262    ) -> Result<(), DrawError> {
263        let mut font_coords = CoordVec::new();
264        expand_coords(&mut font_coords, self.axis_count, coords);
265        let mut stack = GlyphStack::new();
266        let pen: &mut dyn OutlinePen = pen;
267        let mut scalar_cache = self
268            .scalar_cache_from_store(self.var_store.as_ref())?
269            .unwrap();
270        let mut scratch = Scratchpad::new();
271        let ctx = VarcSharedContext {
272            font_coords: &font_coords,
273            size,
274            path_style,
275            coverage: &self.coverage,
276            var_store: self.var_store.as_ref(),
277            store_regions: self.var_store.as_ref().zip(self.regions.as_ref()),
278        };
279        self.draw_glyph(
280            outline.glyph_id,
281            outline.coverage_index,
282            &font_coords,
283            Affine::IDENTITY,
284            &ctx,
285            buf,
286            pen,
287            &mut stack,
288            &mut scalar_cache,
289            &mut scratch,
290        )
291    }
292
293    pub fn draw_unscaled(
294        &self,
295        outline: &Outline,
296        buf: &mut [u8],
297        coords: &[F2Dot14],
298        pen: &mut impl OutlinePen,
299    ) -> Result<i32, DrawError> {
300        let size = Size::unscaled();
301        self.draw(outline, buf, size, coords, PathStyle::default(), pen)?;
302        Ok(self.glyph_metrics.advance_width(outline.glyph_id, coords))
303    }
304
305    #[allow(clippy::too_many_arguments)]
306    fn draw_glyph(
307        &self,
308        glyph_id: GlyphId,
309        coverage_index: u16,
310        current_coords: &[F2Dot14],
311        parent_matrix: Affine,
312        ctx: &VarcSharedContext<'a, '_>,
313        buf: &mut [u8],
314        pen: &mut dyn OutlinePen,
315        stack: &mut GlyphStack,
316        scalar_cache: &mut ScalarCache,
317        scratch: &mut Scratchpad,
318    ) -> Result<(), DrawError> {
319        if stack.len() >= GLYF_COMPOSITE_RECURSION_LIMIT {
320            return Err(DrawError::RecursionLimitExceeded(glyph_id));
321        }
322        let glyph = self.varc.glyph(coverage_index as usize)?;
323        stack.push(glyph_id);
324        let coverage = ctx.coverage;
325        let store_regions = ctx.store_regions;
326        let mut component_coords_buffer = CoordVec::new();
327        let mut child_scalar_cache: Option<ScalarCache> = None;
328        for component in glyph.components() {
329            let component = component?;
330            if !self.component_condition_met(
331                &component,
332                current_coords,
333                scalar_cache,
334                scratch,
335                store_regions,
336            )? {
337                continue;
338            }
339            let component_gid = component.gid();
340            let flags = component.flags();
341
342            let coords_the_same = !flags.contains(VarcFlags::HAVE_AXES)
343                && !flags.contains(VarcFlags::RESET_UNSPECIFIED_AXES);
344
345            let component_coords = if coords_the_same {
346                current_coords
347            } else {
348                self.component_coords(
349                    &component,
350                    current_coords,
351                    &mut component_coords_buffer,
352                    scalar_cache,
353                    scratch,
354                    ctx.font_coords,
355                    store_regions,
356                )?;
357                component_coords_buffer.as_slice()
358            };
359
360            let mut transform = *component.transform();
361            self.apply_transform_variations(
362                &component,
363                current_coords,
364                &mut transform,
365                scalar_cache,
366                scratch,
367                store_regions,
368            )?;
369            let scale = ctx.size.linear_scale(self.units_per_em);
370            let matrix = parent_matrix * scale_matrix(transform.matrix(), scale);
371            if component_gid != glyph_id {
372                if let Some(coverage_index) = coverage.get(component_gid) {
373                    if !stack.contains(&component_gid) {
374                        // Optimization: if coordinates haven't changed, we can reuse the scalar cache.
375                        if coords_the_same {
376                            self.draw_glyph(
377                                component_gid,
378                                coverage_index,
379                                current_coords,
380                                matrix,
381                                ctx,
382                                buf,
383                                pen,
384                                stack,
385                                scalar_cache,
386                                scratch,
387                            )?;
388                        } else {
389                            if let Some(ref mut cache) = child_scalar_cache {
390                                cache.values.fill(ScalarCache::INVALID);
391                            } else {
392                                child_scalar_cache = self.scalar_cache_from_store(ctx.var_store)?;
393                            }
394                            self.draw_glyph(
395                                component_gid,
396                                coverage_index,
397                                component_coords,
398                                matrix,
399                                ctx,
400                                buf,
401                                pen,
402                                stack,
403                                child_scalar_cache.as_mut().unwrap(),
404                                scratch,
405                            )?;
406                        }
407                        continue;
408                    }
409                }
410            }
411            let mut transform_pen = TransformPen::new(pen, matrix);
412            self.draw_base_glyph(
413                component_gid,
414                component_coords,
415                ctx.size,
416                ctx.path_style,
417                buf,
418                &mut transform_pen,
419            )?;
420        }
421        stack.pop();
422        Ok(())
423    }
424
425    fn draw_base_glyph(
426        &self,
427        glyph_id: GlyphId,
428        coords: &[F2Dot14],
429        size: Size,
430        path_style: PathStyle,
431        buf: &mut [u8],
432        pen: &mut impl OutlinePen,
433    ) -> Result<(), DrawError> {
434        let Some(kind) = self.base.base_outline_kind(glyph_id) else {
435            return Err(DrawError::GlyphNotFound(glyph_id));
436        };
437        let settings =
438            crate::outline::DrawSettings::unhinted(size, crate::instance::LocationRef::new(coords))
439                .with_path_style(path_style)
440                .with_memory(Some(buf));
441        crate::outline::OutlineGlyph { kind }.draw(settings, pen)?;
442        Ok(())
443    }
444
445    #[allow(clippy::too_many_arguments)]
446    fn component_coords(
447        &self,
448        component: &VarcComponent<'_>,
449        current_coords: &[F2Dot14],
450        coords: &mut CoordVec,
451        scalar_cache: &mut ScalarCache,
452        scratch: &mut Scratchpad,
453        font_coords: &[F2Dot14],
454        store_regions: Option<(&MultiItemVariationStore<'a>, &SparseVariationRegionList<'a>)>,
455    ) -> Result<(), DrawError> {
456        let flags = component.flags();
457        if flags.contains(VarcFlags::RESET_UNSPECIFIED_AXES) {
458            expand_coords(coords, font_coords.len(), font_coords);
459        } else {
460            expand_coords(coords, current_coords.len(), current_coords);
461        }
462
463        if !flags.contains(VarcFlags::HAVE_AXES) {
464            return Ok(());
465        }
466
467        let axis_indices_index = component
468            .axis_indices_index()
469            .ok_or(ReadError::MalformedData("Missing axisIndicesIndex"))?;
470        let num_axes = self.axis_indices(axis_indices_index as usize, &mut scratch.axis_indices)?;
471
472        self.axis_values(component, num_axes, &mut scratch.axis_values)?;
473        if let Some(var_idx) = component.axis_values_var_index() {
474            let (store, regions) = store_regions.ok_or(ReadError::NullOffset)?;
475            compute_tuple_deltas(
476                store,
477                regions,
478                var_idx,
479                current_coords,
480                scratch.axis_indices.len(),
481                scalar_cache,
482                &mut scratch.deltas,
483            )?;
484            for (value, delta) in scratch.axis_values.iter_mut().zip(scratch.deltas.iter()) {
485                *value += *delta;
486            }
487        }
488
489        for (axis_index, value) in scratch
490            .axis_indices
491            .iter()
492            .zip(scratch.axis_values.iter().copied())
493        {
494            let Some(slot) = coords.get_mut(*axis_index as usize) else {
495                return Err(DrawError::Read(ReadError::OutOfBounds));
496            };
497            let raw = value.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
498            *slot = F2Dot14::from_bits(raw);
499        }
500        Ok(())
501    }
502
503    fn axis_indices(&self, nth: usize, out: &mut AxisIndexVec) -> Result<usize, DrawError> {
504        let packed = self.varc.axis_indices(nth)?;
505        out.clear();
506        for value in packed.iter() {
507            out.push(value as u16);
508        }
509        Ok(out.len())
510    }
511
512    fn axis_values(
513        &self,
514        component: &VarcComponent<'_>,
515        count: usize,
516        out: &mut AxisValueVec,
517    ) -> Result<(), DrawError> {
518        let Some(packed) = component.axis_values() else {
519            out.clear();
520            return Ok(());
521        };
522        out.resize_and_fill(count, 0.0);
523        for (slot, value) in out.iter_mut().zip(packed.iter().by_ref().take(count)) {
524            *slot = value as f32;
525        }
526        Ok(())
527    }
528
529    fn apply_transform_variations(
530        &self,
531        component: &VarcComponent<'_>,
532        coords: &[F2Dot14],
533        transform: &mut DecomposedTransform,
534        scalar_cache: &mut ScalarCache,
535        scratch: &mut Scratchpad,
536        store_regions: Option<(&MultiItemVariationStore<'a>, &SparseVariationRegionList<'a>)>,
537    ) -> Result<(), DrawError> {
538        let Some(var_idx) = component.transform_var_index() else {
539            return Ok(());
540        };
541
542        let flags = component.flags();
543
544        // Count transform fields using a mask + count_ones
545        const TRANSFORM_MASK: VarcFlags = VarcFlags::from_bits_truncate(
546            VarcFlags::HAVE_TRANSLATE_X.bits()
547                | VarcFlags::HAVE_TRANSLATE_Y.bits()
548                | VarcFlags::HAVE_ROTATION.bits()
549                | VarcFlags::HAVE_SCALE_X.bits()
550                | VarcFlags::HAVE_SCALE_Y.bits()
551                | VarcFlags::HAVE_SKEW_X.bits()
552                | VarcFlags::HAVE_SKEW_Y.bits()
553                | VarcFlags::HAVE_TCENTER_X.bits()
554                | VarcFlags::HAVE_TCENTER_Y.bits(),
555        );
556        let field_count = (flags.bits() & TRANSFORM_MASK.bits()).count_ones() as usize;
557        if field_count == 0 {
558            return Ok(());
559        }
560
561        let (store, regions) = store_regions.ok_or(ReadError::NullOffset)?;
562        compute_tuple_deltas(
563            store,
564            regions,
565            var_idx,
566            coords,
567            field_count,
568            scalar_cache,
569            &mut scratch.deltas,
570        )?;
571
572        // Apply deltas in flag order, consuming from iterator
573        let mut delta_iter = scratch.deltas.iter().copied();
574
575        if flags.contains(VarcFlags::HAVE_TRANSLATE_X) {
576            let delta = delta_iter.next().unwrap_or(0.0);
577            transform.set_translate_x(transform.translate_x() + delta);
578        }
579        if flags.contains(VarcFlags::HAVE_TRANSLATE_Y) {
580            let delta = delta_iter.next().unwrap_or(0.0);
581            transform.set_translate_y(transform.translate_y() + delta);
582        }
583        if flags.contains(VarcFlags::HAVE_ROTATION) {
584            let delta = delta_iter.next().unwrap_or(0.0);
585            transform.set_rotation(transform.rotation() + delta / 4096.0);
586        }
587        if flags.contains(VarcFlags::HAVE_SCALE_X) {
588            let delta = delta_iter.next().unwrap_or(0.0);
589            transform.set_scale_x(transform.scale_x() + delta / 1024.0);
590        }
591        if flags.contains(VarcFlags::HAVE_SCALE_Y) {
592            let delta = delta_iter.next().unwrap_or(0.0);
593            transform.set_scale_y(transform.scale_y() + delta / 1024.0);
594        }
595        const SKEW_OR_CENTER: VarcFlags = VarcFlags::from_bits_truncate(
596            VarcFlags::HAVE_SKEW_X.bits()
597                | VarcFlags::HAVE_SKEW_Y.bits()
598                | VarcFlags::HAVE_TCENTER_X.bits()
599                | VarcFlags::HAVE_TCENTER_Y.bits(),
600        );
601        if flags.intersects(SKEW_OR_CENTER) {
602            if flags.contains(VarcFlags::HAVE_SKEW_X) {
603                let delta = delta_iter.next().unwrap_or(0.0);
604                transform.set_skew_x(transform.skew_x() + delta / 4096.0);
605            }
606            if flags.contains(VarcFlags::HAVE_SKEW_Y) {
607                let delta = delta_iter.next().unwrap_or(0.0);
608                transform.set_skew_y(transform.skew_y() + delta / 4096.0);
609            }
610            if flags.contains(VarcFlags::HAVE_TCENTER_X) {
611                let delta = delta_iter.next().unwrap_or(0.0);
612                transform.set_center_x(transform.center_x() + delta);
613            }
614            if flags.contains(VarcFlags::HAVE_TCENTER_Y) {
615                let delta = delta_iter.next().unwrap_or(0.0);
616                transform.set_center_y(transform.center_y() + delta);
617            }
618        }
619
620        if !flags.contains(VarcFlags::HAVE_SCALE_Y) {
621            transform.set_scale_y(transform.scale_x());
622        }
623        Ok(())
624    }
625
626    fn component_condition_met(
627        &self,
628        component: &VarcComponent<'_>,
629        coords: &[F2Dot14],
630        scalar_cache: &mut ScalarCache,
631        scratch: &mut Scratchpad,
632        store_regions: Option<(&MultiItemVariationStore<'a>, &SparseVariationRegionList<'a>)>,
633    ) -> Result<bool, DrawError> {
634        let Some(condition_index) = component.condition_index() else {
635            return Ok(true);
636        };
637        let Some(condition_list) = self.varc.condition_list() else {
638            return Err(DrawError::Read(ReadError::NullOffset));
639        };
640        let condition_list = condition_list?;
641        let condition = condition_list.conditions().get(condition_index as usize)?;
642        let (store, regions) = store_regions.ok_or(ReadError::NullOffset)?;
643        Self::eval_condition(&condition, coords, store, regions, scalar_cache, scratch)
644    }
645
646    fn eval_condition(
647        condition: &Condition<'a>,
648        coords: &[F2Dot14],
649        var_store: &MultiItemVariationStore<'a>,
650        regions: &SparseVariationRegionList<'a>,
651        scalar_cache: &mut ScalarCache,
652        scratch: &mut Scratchpad,
653    ) -> Result<bool, DrawError> {
654        match condition {
655            Condition::Format1AxisRange(condition) => {
656                let axis_index = condition.axis_index() as usize;
657                let coord = coords.get(axis_index).copied().unwrap_or(F2Dot14::ZERO);
658                Ok(coord >= condition.filter_range_min_value()
659                    && coord <= condition.filter_range_max_value())
660            }
661            Condition::Format2VariableValue(condition) => {
662                let default_value = condition.default_value() as f32;
663                let var_idx = condition.var_index();
664                compute_tuple_deltas(
665                    var_store,
666                    regions,
667                    var_idx,
668                    coords,
669                    1,
670                    scalar_cache,
671                    &mut scratch.deltas,
672                )?;
673                let delta = scratch.deltas.first().copied().unwrap_or(0.0);
674                Ok(default_value + delta > 0.0)
675            }
676            Condition::Format3And(condition) => {
677                for nested in condition.conditions().iter() {
678                    let nested = nested?;
679                    if !Self::eval_condition(
680                        &nested,
681                        coords,
682                        var_store,
683                        regions,
684                        scalar_cache,
685                        scratch,
686                    )? {
687                        return Ok(false);
688                    }
689                }
690                Ok(true)
691            }
692            Condition::Format4Or(condition) => {
693                for nested in condition.conditions().iter() {
694                    let nested = nested?;
695                    if Self::eval_condition(
696                        &nested,
697                        coords,
698                        var_store,
699                        regions,
700                        scalar_cache,
701                        scratch,
702                    )? {
703                        return Ok(true);
704                    }
705                }
706                Ok(false)
707            }
708            Condition::Format5Negate(condition) => {
709                let nested = condition.condition()?;
710                Ok(!Self::eval_condition(
711                    &nested,
712                    coords,
713                    var_store,
714                    regions,
715                    scalar_cache,
716                    scratch,
717                )?)
718            }
719        }
720    }
721
722    fn scalar_cache_from_store(
723        &self,
724        store: Option<&MultiItemVariationStore<'a>>,
725    ) -> Result<Option<ScalarCache>, DrawError> {
726        let Some(store) = store else {
727            return Ok(None);
728        };
729        let region_count = store.region_list()?.region_count() as usize;
730        Ok(Some(ScalarCache::new(region_count)))
731    }
732}
733
734struct ScalarCache {
735    values: ScalarCacheVec,
736}
737
738impl ScalarCache {
739    const INVALID: f32 = 2.0; // Scalars are in [0,1], so 2.0 means "not cached"
740
741    fn new(count: usize) -> Self {
742        Self {
743            values: ScalarCacheVec::with_len(count, Self::INVALID),
744        }
745    }
746
747    fn get(&self, index: usize) -> f32 {
748        self.values.get(index).copied().unwrap_or(Self::INVALID)
749    }
750
751    fn set(&mut self, index: usize, value: f32) {
752        if let Some(slot) = self.values.get_mut(index) {
753            *slot = value;
754        }
755    }
756}
757
758fn expand_coords(out: &mut CoordVec, axis_count: usize, coords: &[F2Dot14]) {
759    out.resize_and_fill(axis_count, F2Dot14::ZERO);
760    for (slot, value) in out.iter_mut().zip(coords.iter().copied()) {
761        *slot = value;
762    }
763}
764
765fn compute_tuple_deltas(
766    store: &MultiItemVariationStore,
767    regions: &SparseVariationRegionList,
768    var_idx: u32,
769    coords: &[F2Dot14],
770    tuple_len: usize,
771    cache: &mut ScalarCache,
772    out: &mut DeltaVec,
773) -> Result<(), ReadError> {
774    out.resize_and_fill(tuple_len, 0.0);
775    if tuple_len == 0 || var_idx == NO_VARIATION_INDEX {
776        return Ok(());
777    }
778    let outer = (var_idx >> 16) as usize;
779    let inner = (var_idx & 0xFFFF) as usize;
780    let data = store
781        .variation_data()
782        .get(outer)
783        .map_err(|_| ReadError::InvalidCollectionIndex(outer as _))?;
784    let region_indices = data.region_indices();
785    let mut deltas = data.delta_set(inner)?.fetcher();
786    let regions = regions.regions();
787    let out_slice = out.as_mut_slice();
788
789    let mut skip = 0;
790    for region_index in region_indices.iter() {
791        let region_idx = region_index.get() as usize;
792        let mut scalar = cache.get(region_idx);
793        if scalar >= 2.0 {
794            scalar = regions.get(region_idx)?.compute_scalar_f32(coords);
795            cache.set(region_idx, scalar);
796        }
797        // We skip lazily. Reduces work at the tail end.
798        if scalar == 0.0 {
799            skip += out_slice.len();
800        } else {
801            if skip != 0 {
802                deltas.skip(skip)?;
803                skip = 0;
804            }
805            deltas.add_to_f32_scaled(out_slice, scalar)?;
806        }
807    }
808
809    Ok(())
810}
811
812#[inline(always)]
813fn scale_matrix(mut m: Affine, s: f32) -> Affine {
814    m.dx *= s;
815    m.dy *= s;
816    m
817}
818
819struct TransformPen<'a, P: OutlinePen + ?Sized> {
820    pen: &'a mut P,
821    matrix: Affine,
822}
823
824impl<'a, P: OutlinePen + ?Sized> TransformPen<'a, P> {
825    fn new(pen: &'a mut P, matrix: Affine) -> Self {
826        Self { pen, matrix }
827    }
828
829    #[inline(always)]
830    fn transform(&self, x: f32, y: f32) -> (f32, f32) {
831        self.matrix.transform(x, y)
832    }
833}
834
835impl<P: OutlinePen + ?Sized> OutlinePen for TransformPen<'_, P> {
836    fn move_to(&mut self, x: f32, y: f32) {
837        let (x, y) = self.transform(x, y);
838        self.pen.move_to(x, y);
839    }
840
841    fn line_to(&mut self, x: f32, y: f32) {
842        let (x, y) = self.transform(x, y);
843        self.pen.line_to(x, y);
844    }
845
846    fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
847        let (cx0, cy0) = self.transform(cx0, cy0);
848        let (x, y) = self.transform(x, y);
849        self.pen.quad_to(cx0, cy0, x, y);
850    }
851
852    fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
853        let (cx0, cy0) = self.transform(cx0, cy0);
854        let (cx1, cy1) = self.transform(cx1, cy1);
855        let (x, y) = self.transform(x, y);
856        self.pen.curve_to(cx0, cy0, cx1, cy1, x, y);
857    }
858
859    fn close(&mut self) {
860        self.pen.close();
861    }
862}
863
864#[cfg(test)]
865mod tests {
866    use super::*;
867    use crate::outline::pen::PathElement;
868    use read_fonts::{FontRef, TableProvider};
869
870    fn coord(value: f32) -> F2Dot14 {
871        F2Dot14::from_f32(value)
872    }
873
874    fn assert_close(actual: f32, expected: f32) {
875        let diff = (actual - expected).abs();
876        assert!(
877            diff <= 1e-6,
878            "expected {expected}, got {actual}, diff {diff}"
879        );
880    }
881
882    fn path_head_signature(path: &[PathElement], n: usize) -> Vec<String> {
883        path.iter()
884            .take(n)
885            .map(|el| match *el {
886                PathElement::MoveTo { x, y } => format!("M{:.2},{:.2}", x, y),
887                PathElement::LineTo { x, y } => format!("L{:.2},{:.2}", x, y),
888                PathElement::QuadTo { cx0, cy0, x, y } => {
889                    format!("Q{:.2},{:.2} {:.2},{:.2}", cx0, cy0, x, y)
890                }
891                PathElement::CurveTo {
892                    cx0,
893                    cy0,
894                    cx1,
895                    cy1,
896                    x,
897                    y,
898                } => format!(
899                    "C{:.2},{:.2} {:.2},{:.2} {:.2},{:.2}",
900                    cx0, cy0, cx1, cy1, x, y
901                ),
902                PathElement::Close => "Z".to_string(),
903            })
904            .collect()
905    }
906
907    #[test]
908    fn expand_coords_pads_and_truncates() {
909        let mut out = CoordVec::new();
910        expand_coords(&mut out, 4, &[coord(0.25), coord(-0.5)]);
911        assert_eq!(
912            out.as_slice(),
913            &[coord(0.25), coord(-0.5), F2Dot14::ZERO, F2Dot14::ZERO]
914        );
915
916        expand_coords(&mut out, 1, &[coord(0.25), coord(-0.5)]);
917        assert_eq!(out.as_slice(), &[coord(0.25)]);
918    }
919
920    #[test]
921    fn scale_matrix_only_scales_translation() {
922        let matrix = Matrix::from_elements([1.0, 2.0, 3.0, 4.0, 5.0, -6.0]);
923        assert_eq!(
924            scale_matrix(matrix, 10.0).elements(),
925            [1.0, 2.0, 3.0, 4.0, 50.0, -60.0]
926        );
927    }
928
929    #[test]
930    fn compute_tuple_deltas_no_variation_index_is_noop_after_resize() {
931        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
932        let varc = font.varc().unwrap();
933        let store = varc.multi_var_store().unwrap().unwrap();
934        let regions = store.region_list().unwrap();
935        let mut cache = ScalarCache::new(regions.region_count() as usize);
936        let mut out = DeltaVec::new();
937        out.push(42.0);
938
939        compute_tuple_deltas(
940            &store,
941            &regions,
942            NO_VARIATION_INDEX,
943            &[coord(0.0)],
944            3,
945            &mut cache,
946            &mut out,
947        )
948        .unwrap();
949        assert_eq!(out.as_slice(), &[0.0, 0.0, 0.0]);
950
951        compute_tuple_deltas(
952            &store,
953            &regions,
954            NO_VARIATION_INDEX,
955            &[coord(0.0)],
956            0,
957            &mut cache,
958            &mut out,
959        )
960        .unwrap();
961        assert!(out.is_empty());
962    }
963
964    #[test]
965    fn compute_tuple_deltas_invalid_outer_index_errors() {
966        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
967        let varc = font.varc().unwrap();
968        let store = varc.multi_var_store().unwrap().unwrap();
969        let regions = store.region_list().unwrap();
970        let mut cache = ScalarCache::new(regions.region_count() as usize);
971        let mut out = DeltaVec::new();
972
973        let err = compute_tuple_deltas(&store, &regions, 0xFFFF_0000, &[], 1, &mut cache, &mut out)
974            .unwrap_err();
975        assert!(matches!(err, ReadError::InvalidCollectionIndex(_)));
976    }
977
978    #[test]
979    fn compute_tuple_deltas_matches_manual_decode() {
980        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
981        let varc = font.varc().unwrap();
982        let store = varc.multi_var_store().unwrap().unwrap();
983        let regions = store.region_list().unwrap();
984        let region_list = regions.regions();
985        let coords = [coord(0.5); 8];
986
987        let mut tried = 0usize;
988        for (outer, data) in store.variation_data().iter().enumerate() {
989            let data = data.unwrap();
990            let region_count = data.region_indices().len();
991            if region_count == 0 {
992                continue;
993            }
994            let delta_set_count = data.delta_sets().unwrap().count() as usize;
995            for inner in 0..delta_set_count.min(3) {
996                let decoded = data.delta_set(inner).unwrap().iter().collect::<Vec<_>>();
997                if decoded.is_empty() || decoded.len() % region_count != 0 {
998                    continue;
999                }
1000                let tuple_len = decoded.len() / region_count;
1001                let var_idx = ((outer as u32) << 16) | inner as u32;
1002
1003                let mut cache = ScalarCache::new(regions.region_count() as usize);
1004                let mut actual = DeltaVec::new();
1005                compute_tuple_deltas(
1006                    &store,
1007                    &regions,
1008                    var_idx,
1009                    &coords,
1010                    tuple_len,
1011                    &mut cache,
1012                    &mut actual,
1013                )
1014                .unwrap();
1015                let first = actual.as_slice().to_vec();
1016
1017                // Same cache after population should not alter results.
1018                compute_tuple_deltas(
1019                    &store,
1020                    &regions,
1021                    var_idx,
1022                    &coords,
1023                    tuple_len,
1024                    &mut cache,
1025                    &mut actual,
1026                )
1027                .unwrap();
1028                assert_eq!(actual.as_slice(), first.as_slice());
1029
1030                let mut expected = vec![0.0f32; tuple_len];
1031                for (region_order, region_idx) in data.region_indices().iter().enumerate() {
1032                    let scalar = region_list
1033                        .get(region_idx.get() as usize)
1034                        .unwrap()
1035                        .compute_scalar_f32(&coords);
1036                    if scalar == 0.0 {
1037                        continue;
1038                    }
1039                    let base = region_order * tuple_len;
1040                    for (i, slot) in expected.iter_mut().enumerate() {
1041                        *slot += decoded[base + i] as f32 * scalar;
1042                    }
1043                }
1044                assert_eq!(actual.len(), expected.len());
1045                for (a, e) in actual.iter().zip(expected.iter()) {
1046                    assert_close(*a, *e);
1047                }
1048                tried += 1;
1049            }
1050        }
1051        assert!(tried > 0, "expected at least one tuple to be exercised");
1052    }
1053
1054    fn apply_transform_variations_reference(
1055        component: &VarcComponent<'_>,
1056        coords: &[F2Dot14],
1057        transform: &mut DecomposedTransform,
1058        var_store: Option<&MultiItemVariationStore<'_>>,
1059        regions: Option<&SparseVariationRegionList<'_>>,
1060        scalar_cache: &mut ScalarCache,
1061        deltas: &mut DeltaVec,
1062    ) -> Result<(), DrawError> {
1063        let Some(var_idx) = component.transform_var_index() else {
1064            return Ok(());
1065        };
1066        let flags = component.flags();
1067        const TRANSFORM_MASK: VarcFlags = VarcFlags::from_bits_truncate(
1068            VarcFlags::HAVE_TRANSLATE_X.bits()
1069                | VarcFlags::HAVE_TRANSLATE_Y.bits()
1070                | VarcFlags::HAVE_ROTATION.bits()
1071                | VarcFlags::HAVE_SCALE_X.bits()
1072                | VarcFlags::HAVE_SCALE_Y.bits()
1073                | VarcFlags::HAVE_SKEW_X.bits()
1074                | VarcFlags::HAVE_SKEW_Y.bits()
1075                | VarcFlags::HAVE_TCENTER_X.bits()
1076                | VarcFlags::HAVE_TCENTER_Y.bits(),
1077        );
1078        let field_count = (flags.bits() & TRANSFORM_MASK.bits()).count_ones() as usize;
1079        if field_count == 0 {
1080            return Ok(());
1081        }
1082
1083        let store = var_store.ok_or(ReadError::NullOffset)?;
1084        let regions = regions.ok_or(ReadError::NullOffset)?;
1085        compute_tuple_deltas(
1086            store,
1087            regions,
1088            var_idx,
1089            coords,
1090            field_count,
1091            scalar_cache,
1092            deltas,
1093        )?;
1094
1095        let mut delta_iter = deltas.iter().copied();
1096        if flags.contains(VarcFlags::HAVE_TRANSLATE_X) {
1097            transform.set_translate_x(transform.translate_x() + delta_iter.next().unwrap_or(0.0));
1098        }
1099        if flags.contains(VarcFlags::HAVE_TRANSLATE_Y) {
1100            transform.set_translate_y(transform.translate_y() + delta_iter.next().unwrap_or(0.0));
1101        }
1102        if flags.contains(VarcFlags::HAVE_ROTATION) {
1103            transform
1104                .set_rotation(transform.rotation() + delta_iter.next().unwrap_or(0.0) / 4096.0);
1105        }
1106        if flags.contains(VarcFlags::HAVE_SCALE_X) {
1107            transform.set_scale_x(transform.scale_x() + delta_iter.next().unwrap_or(0.0) / 1024.0);
1108        }
1109        if flags.contains(VarcFlags::HAVE_SCALE_Y) {
1110            transform.set_scale_y(transform.scale_y() + delta_iter.next().unwrap_or(0.0) / 1024.0);
1111        }
1112        const SKEW_OR_CENTER: VarcFlags = VarcFlags::from_bits_truncate(
1113            VarcFlags::HAVE_SKEW_X.bits()
1114                | VarcFlags::HAVE_SKEW_Y.bits()
1115                | VarcFlags::HAVE_TCENTER_X.bits()
1116                | VarcFlags::HAVE_TCENTER_Y.bits(),
1117        );
1118        if flags.intersects(SKEW_OR_CENTER) {
1119            if flags.contains(VarcFlags::HAVE_SKEW_X) {
1120                transform
1121                    .set_skew_x(transform.skew_x() + delta_iter.next().unwrap_or(0.0) / 4096.0);
1122            }
1123            if flags.contains(VarcFlags::HAVE_SKEW_Y) {
1124                transform
1125                    .set_skew_y(transform.skew_y() + delta_iter.next().unwrap_or(0.0) / 4096.0);
1126            }
1127            if flags.contains(VarcFlags::HAVE_TCENTER_X) {
1128                transform.set_center_x(transform.center_x() + delta_iter.next().unwrap_or(0.0));
1129            }
1130            if flags.contains(VarcFlags::HAVE_TCENTER_Y) {
1131                transform.set_center_y(transform.center_y() + delta_iter.next().unwrap_or(0.0));
1132            }
1133        }
1134        if !flags.contains(VarcFlags::HAVE_SCALE_Y) {
1135            transform.set_scale_y(transform.scale_x());
1136        }
1137        Ok(())
1138    }
1139
1140    #[test]
1141    fn apply_transform_variations_matches_reference_path() {
1142        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
1143        let outlines = Outlines::new(&font).unwrap();
1144        let coverage = outlines.varc.coverage().unwrap();
1145        let var_store = outlines.varc.multi_var_store().transpose().unwrap();
1146        let regions = var_store
1147            .as_ref()
1148            .map(|s| s.region_list())
1149            .transpose()
1150            .unwrap();
1151        let region_count = regions
1152            .as_ref()
1153            .map(|r| r.region_count() as usize)
1154            .unwrap_or(0);
1155
1156        let mut coords = CoordVec::new();
1157        coords.resize_and_fill(outlines.axis_count, F2Dot14::ZERO);
1158        for (i, c) in coords.iter_mut().enumerate() {
1159            *c = match i % 4 {
1160                0 => coord(0.5),
1161                1 => coord(-0.5),
1162                2 => coord(0.25),
1163                _ => coord(-0.25),
1164            };
1165        }
1166
1167        let mut tested = 0usize;
1168        for gid16 in coverage.iter() {
1169            let gid: GlyphId = gid16.into();
1170            let coverage_index = coverage.get(gid).unwrap() as usize;
1171            let glyph = outlines.varc.glyph(coverage_index).unwrap();
1172            for component in glyph.components() {
1173                let component = component.unwrap();
1174                if component.transform_var_index().is_none() {
1175                    continue;
1176                }
1177
1178                let mut transform_new = *component.transform();
1179                let mut transform_ref = *component.transform();
1180                let mut cache_new = ScalarCache::new(region_count);
1181                let mut cache_ref = ScalarCache::new(region_count);
1182                let mut deltas_ref = DeltaVec::new();
1183                let mut scratch = Scratchpad::new();
1184                let store_regions = var_store.as_ref().zip(regions.as_ref());
1185
1186                outlines
1187                    .apply_transform_variations(
1188                        &component,
1189                        &coords,
1190                        &mut transform_new,
1191                        &mut cache_new,
1192                        &mut scratch,
1193                        store_regions,
1194                    )
1195                    .unwrap();
1196                apply_transform_variations_reference(
1197                    &component,
1198                    &coords,
1199                    &mut transform_ref,
1200                    var_store.as_ref(),
1201                    regions.as_ref(),
1202                    &mut cache_ref,
1203                    &mut deltas_ref,
1204                )
1205                .unwrap();
1206
1207                assert_close(transform_new.translate_x(), transform_ref.translate_x());
1208                assert_close(transform_new.translate_y(), transform_ref.translate_y());
1209                assert_close(transform_new.rotation(), transform_ref.rotation());
1210                assert_close(transform_new.scale_x(), transform_ref.scale_x());
1211                assert_close(transform_new.scale_y(), transform_ref.scale_y());
1212                assert_close(transform_new.skew_x(), transform_ref.skew_x());
1213                assert_close(transform_new.skew_y(), transform_ref.skew_y());
1214                assert_close(transform_new.center_x(), transform_ref.center_x());
1215                assert_close(transform_new.center_y(), transform_ref.center_y());
1216                tested += 1;
1217                if tested >= 32 {
1218                    break;
1219                }
1220            }
1221            if tested >= 32 {
1222                break;
1223            }
1224        }
1225        assert!(tested > 0, "expected at least one transformed component");
1226    }
1227
1228    #[test]
1229    fn draw_varc_6868_freetype_path_head_snapshot() {
1230        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
1231        let outlines = Outlines::new(&font).unwrap();
1232        let gid = font.cmap().unwrap().map_codepoint(0x6868_u32).unwrap();
1233        let outline = outlines.outline(gid).unwrap().unwrap();
1234        let mut memory = vec![0u8; outline.required_buffer_size()];
1235        let mut pen = Vec::<PathElement>::new();
1236        outlines
1237            .draw(
1238                &outline,
1239                &mut memory,
1240                Size::unscaled(),
1241                &[],
1242                PathStyle::FreeType,
1243                &mut pen,
1244            )
1245            .unwrap();
1246
1247        let head = path_head_signature(&pen, 8);
1248        assert_eq!(
1249            head,
1250            vec![
1251                "M454.56,574.77".to_string(),
1252                "Q477.58,585.56 499.80,598.25".to_string(),
1253                "Q522.02,610.95 543.36,625.16".to_string(),
1254                "Q564.71,639.37 584.54,655.19".to_string(),
1255                "Q604.38,671.02 623.03,688.41".to_string(),
1256                "Q641.67,705.80 658.41,724.46".to_string(),
1257                "Q675.14,743.12 689.79,763.21".to_string(),
1258                "Q704.43,783.31 717.03,804.56".to_string(),
1259            ]
1260        );
1261    }
1262}