Skip to main content

read_fonts/tables/colr/
closure.rs

1//! computing closure for the colr table
2use font_types::{GlyphId, GlyphId16};
3
4use crate::{collections::IntSet, tables::variations::NO_VARIATION_INDEX, ResolveOffset};
5
6use super::{
7    Clip, ClipBox, ClipBoxFormat2, ClipList, ColorLine, ColorStop, Colr, Paint, PaintColrGlyph,
8    PaintColrLayers, PaintComposite, PaintGlyph, PaintLinearGradient, PaintRadialGradient,
9    PaintRotate, PaintRotateAroundCenter, PaintScale, PaintScaleAroundCenter, PaintScaleUniform,
10    PaintScaleUniformAroundCenter, PaintSkew, PaintSkewAroundCenter, PaintSolid,
11    PaintSweepGradient, PaintTransform, PaintTranslate, PaintVarLinearGradient,
12    PaintVarRadialGradient, PaintVarRotate, PaintVarRotateAroundCenter, PaintVarScale,
13    PaintVarScaleAroundCenter, PaintVarScaleUniform, PaintVarScaleUniformAroundCenter,
14    PaintVarSkew, PaintVarSkewAroundCenter, PaintVarSolid, PaintVarSweepGradient,
15    PaintVarTransform, PaintVarTranslate, VarAffine2x3, VarColorLine, VarColorStop,
16};
17
18impl Colr<'_> {
19    //Collect the transitive closure of V0 palette indices needed for all of the input glyphs set
20    //It's similar to closure glyphs but in a separate fn, because v1 closure might adds more v0 glyphs, so this fn needs to be called after v1 closure
21    pub fn v0_closure_palette_indices(
22        &self,
23        glyph_set: &IntSet<GlyphId>,
24        palette_indices: &mut IntSet<u16>,
25    ) {
26        let Some(Ok(records)) = self.base_glyph_records() else {
27            return;
28        };
29
30        let Some(Ok(layers)) = self.layer_records() else {
31            return;
32        };
33        let num_records = records.len() as u32;
34        let bit_storage = u32::BITS - num_records.leading_zeros();
35        if glyph_set.is_inverted() || num_records <= glyph_set.len() as u32 * bit_storage {
36            for record in records {
37                if !glyph_set.contains(GlyphId::from(record.glyph_id())) {
38                    continue;
39                }
40                let start = record.first_layer_index() as usize;
41                let end = start + record.num_layers() as usize;
42                for layer_index in start..end {
43                    if let Some(layer) = layers.get(layer_index) {
44                        palette_indices.insert(layer.palette_index());
45                    }
46                }
47            }
48        } else {
49            for glyph_id in glyph_set.iter() {
50                let Ok(glyph_id) = glyph_id.try_into() else {
51                    continue;
52                };
53                let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
54                    Ok(idx) => records[idx],
55                    _ => continue,
56                };
57                let start = record.first_layer_index() as usize;
58                let end = start + record.num_layers() as usize;
59                for layer_index in start..end {
60                    if let Some(layer) = layers.get(layer_index) {
61                        palette_indices.insert(layer.palette_index());
62                    }
63                }
64            }
65        }
66    }
67
68    /// Collect the transitive closure of v1 glyphs,layer/paletted indices and variation/delta set indices for COLRv1
69    pub fn v1_closure(
70        &self,
71        glyph_set: &mut IntSet<GlyphId>,
72        layer_indices: &mut IntSet<u32>,
73        palette_indices: &mut IntSet<u16>,
74        variation_indices: &mut IntSet<u32>,
75    ) {
76        if self.version() < 1 {
77            return;
78        }
79
80        let mut c =
81            Colrv1ClosureContext::new(layer_indices, palette_indices, variation_indices, self);
82        if let Some(Ok(base_glyph_list)) = self.base_glyph_list() {
83            let base_glyph_records = base_glyph_list.base_glyph_paint_records();
84            let offset_data = base_glyph_list.offset_data();
85            let num_records = base_glyph_records.len() as u32;
86            let bit_storage = u32::BITS - num_records.leading_zeros();
87            if glyph_set.is_inverted() || num_records <= glyph_set.len() as u32 * bit_storage {
88                for record in base_glyph_records {
89                    let gid = record.glyph_id();
90                    if !glyph_set.contains(GlyphId::from(gid)) {
91                        continue;
92                    }
93                    if let Ok(paint) = record.paint(offset_data) {
94                        c.dispatch(&paint);
95                    }
96                }
97            } else {
98                for glyph_id in glyph_set.iter() {
99                    let Ok(glyph_id) = glyph_id.try_into() else {
100                        continue;
101                    };
102                    let record = match base_glyph_records
103                        .binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id))
104                    {
105                        Ok(idx) => &base_glyph_records[idx],
106                        _ => continue,
107                    };
108                    if record.paint_offset().is_null() {
109                        continue;
110                    }
111                    if let Ok(paint) = record.paint(offset_data) {
112                        c.dispatch(&paint);
113                    }
114                }
115            }
116            glyph_set.union(&c.glyph_set);
117        }
118
119        if let Some(Ok(clip_list)) = self.clip_list() {
120            c.glyph_set.union(glyph_set);
121            for clip_record in clip_list.clips() {
122                clip_record.v1_closure(&mut c, &clip_list);
123            }
124        }
125    }
126
127    /// Collect the transitive closure of V0 glyphs needed for all of the input glyphs set
128    pub fn v0_closure_glyphs(
129        &self,
130        glyph_set: &IntSet<GlyphId>,
131        glyphset_colrv0: &mut IntSet<GlyphId>,
132    ) {
133        glyphset_colrv0.union(glyph_set);
134        let Some(Ok(records)) = self.base_glyph_records() else {
135            return;
136        };
137        let Some(Ok(layers)) = self.layer_records() else {
138            return;
139        };
140        let num_records = records.len() as u32;
141        let bit_storage = u32::BITS - num_records.leading_zeros();
142        if glyph_set.is_inverted() || num_records <= glyph_set.len() as u32 * bit_storage {
143            for record in records {
144                if !glyph_set.contains(GlyphId::from(record.glyph_id())) {
145                    continue;
146                }
147                let start = record.first_layer_index() as usize;
148                let end = start + record.num_layers() as usize;
149                for layer_index in start..end {
150                    if let Some(layer) = layers.get(layer_index) {
151                        glyphset_colrv0.insert(GlyphId::from(layer.glyph_id()));
152                    }
153                }
154            }
155        } else {
156            for glyph_id in glyph_set.iter() {
157                let Ok(glyph_id) = glyph_id.try_into() else {
158                    continue;
159                };
160                let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
161                    Ok(idx) => records[idx],
162                    _ => continue,
163                };
164                let start = record.first_layer_index() as usize;
165                let end = start + record.num_layers() as usize;
166                for layer_index in start..end {
167                    if let Some(layer) = layers.get(layer_index) {
168                        glyphset_colrv0.insert(GlyphId::from(layer.glyph_id()));
169                    }
170                }
171            }
172        }
173    }
174}
175
176struct Colrv1ClosureContext<'a> {
177    glyph_set: IntSet<GlyphId>,
178    layer_indices: &'a mut IntSet<u32>,
179    palette_indices: &'a mut IntSet<u16>,
180    variation_indices: &'a mut IntSet<u32>,
181    colr: &'a Colr<'a>,
182    nesting_level_left: u8,
183    visited_paints: IntSet<u32>,
184    colr_head: usize,
185}
186
187impl<'a> Colrv1ClosureContext<'a> {
188    pub fn new(
189        layer_indices: &'a mut IntSet<u32>,
190        palette_indices: &'a mut IntSet<u16>,
191        variation_indices: &'a mut IntSet<u32>,
192        colr: &'a Colr,
193    ) -> Self {
194        let colr_head = colr.offset_data().as_bytes().as_ptr() as usize;
195        Self {
196            glyph_set: IntSet::empty(),
197            layer_indices,
198            palette_indices,
199            variation_indices,
200            colr,
201            nesting_level_left: 64,
202            visited_paints: IntSet::empty(),
203            colr_head,
204        }
205    }
206
207    fn dispatch(&mut self, paint: &Paint) {
208        if self.nesting_level_left == 0 {
209            return;
210        }
211
212        if self.paint_visited(paint) {
213            return;
214        }
215        self.nesting_level_left -= 1;
216        paint.v1_closure(self);
217        self.nesting_level_left += 1;
218    }
219
220    fn paint_visited(&mut self, paint: &Paint) -> bool {
221        let offset = (paint.offset_data().as_bytes().as_ptr() as usize - self.colr_head) as u32;
222        if self.visited_paints.contains(offset) {
223            return true;
224        }
225
226        self.visited_paints.insert(offset);
227        false
228    }
229
230    fn add_layer_index(&mut self, layer_index: u32) {
231        self.layer_indices.insert(layer_index);
232    }
233
234    fn add_palette_index(&mut self, palette_index: u16) {
235        self.palette_indices.insert(palette_index);
236    }
237
238    fn add_variation_indices(&mut self, var_index_base: u32, num_vars: u8) {
239        if num_vars == 0 || var_index_base == NO_VARIATION_INDEX {
240            return;
241        }
242        let Some(last_var_index) = compute_inclusive_end(var_index_base, num_vars as u32) else {
243            return;
244        };
245        self.variation_indices
246            .insert_range(var_index_base..=last_var_index);
247    }
248
249    fn add_glyph_id(&mut self, gid: GlyphId16) {
250        self.glyph_set.insert(GlyphId::from(gid));
251    }
252}
253
254impl ColorStop {
255    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
256        c.add_palette_index(self.palette_index());
257    }
258}
259
260impl VarColorStop {
261    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
262        c.add_palette_index(self.palette_index());
263        c.add_variation_indices(self.var_index_base(), 2);
264    }
265}
266
267impl ColorLine<'_> {
268    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
269        for colorstop in self.color_stops() {
270            colorstop.v1_closure(c);
271        }
272    }
273}
274
275impl VarColorLine<'_> {
276    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
277        for var_colorstop in self.color_stops() {
278            var_colorstop.v1_closure(c);
279        }
280    }
281}
282
283impl Paint<'_> {
284    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
285        match self {
286            Self::ColrLayers(item) => item.v1_closure(c),
287            Self::Solid(item) => item.v1_closure(c),
288            Self::VarSolid(item) => item.v1_closure(c),
289            Self::LinearGradient(item) => item.v1_closure(c),
290            Self::VarLinearGradient(item) => item.v1_closure(c),
291            Self::RadialGradient(item) => item.v1_closure(c),
292            Self::VarRadialGradient(item) => item.v1_closure(c),
293            Self::SweepGradient(item) => item.v1_closure(c),
294            Self::VarSweepGradient(item) => item.v1_closure(c),
295            Self::Glyph(item) => item.v1_closure(c),
296            Self::ColrGlyph(item) => item.v1_closure(c),
297            Self::Transform(item) => item.v1_closure(c),
298            Self::VarTransform(item) => item.v1_closure(c),
299            Self::Translate(item) => item.v1_closure(c),
300            Self::VarTranslate(item) => item.v1_closure(c),
301            Self::Scale(item) => item.v1_closure(c),
302            Self::VarScale(item) => item.v1_closure(c),
303            Self::ScaleAroundCenter(item) => item.v1_closure(c),
304            Self::VarScaleAroundCenter(item) => item.v1_closure(c),
305            Self::ScaleUniform(item) => item.v1_closure(c),
306            Self::VarScaleUniform(item) => item.v1_closure(c),
307            Self::ScaleUniformAroundCenter(item) => item.v1_closure(c),
308            Self::VarScaleUniformAroundCenter(item) => item.v1_closure(c),
309            Self::Rotate(item) => item.v1_closure(c),
310            Self::VarRotate(item) => item.v1_closure(c),
311            Self::RotateAroundCenter(item) => item.v1_closure(c),
312            Self::VarRotateAroundCenter(item) => item.v1_closure(c),
313            Self::Skew(item) => item.v1_closure(c),
314            Self::VarSkew(item) => item.v1_closure(c),
315            Self::SkewAroundCenter(item) => item.v1_closure(c),
316            Self::VarSkewAroundCenter(item) => item.v1_closure(c),
317            Self::Composite(item) => item.v1_closure(c),
318        }
319    }
320}
321
322impl PaintColrLayers<'_> {
323    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
324        let num_layers = self.num_layers();
325        if num_layers == 0 {
326            return;
327        }
328
329        let Some(Ok(layer_list)) = c.colr.layer_list() else {
330            return;
331        };
332
333        let first_layer_index = self.first_layer_index();
334        let Some(last_layer_index) = compute_inclusive_end(first_layer_index, num_layers as u32)
335        else {
336            return;
337        };
338
339        let offset_data = layer_list.offset_data();
340        let paint_offsets = layer_list.paint_offsets();
341        for layer_index in first_layer_index..=last_layer_index {
342            let Some(paint_offset) = paint_offsets.get(layer_index as usize) else {
343                break;
344            };
345            if let Ok(paint) = paint_offset.get().resolve::<Paint>(offset_data) {
346                c.add_layer_index(layer_index);
347                c.dispatch(&paint);
348            }
349        }
350    }
351}
352
353impl PaintSolid<'_> {
354    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
355        c.add_palette_index(self.palette_index());
356    }
357}
358
359impl PaintVarSolid<'_> {
360    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
361        c.add_palette_index(self.palette_index());
362        c.add_variation_indices(self.var_index_base(), 1);
363    }
364}
365
366impl PaintLinearGradient<'_> {
367    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
368        if let Ok(colorline) = self.color_line() {
369            colorline.v1_closure(c);
370        }
371    }
372}
373
374impl PaintVarLinearGradient<'_> {
375    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
376        if let Ok(var_colorline) = self.color_line() {
377            var_colorline.v1_closure(c);
378            c.add_variation_indices(self.var_index_base(), 6);
379        }
380    }
381}
382
383impl PaintRadialGradient<'_> {
384    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
385        if let Ok(colorline) = self.color_line() {
386            colorline.v1_closure(c);
387        }
388    }
389}
390
391impl PaintVarRadialGradient<'_> {
392    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
393        if let Ok(var_colorline) = self.color_line() {
394            var_colorline.v1_closure(c);
395            c.add_variation_indices(self.var_index_base(), 6);
396        }
397    }
398}
399
400impl PaintSweepGradient<'_> {
401    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
402        if let Ok(colorline) = self.color_line() {
403            colorline.v1_closure(c);
404        }
405    }
406}
407
408impl PaintVarSweepGradient<'_> {
409    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
410        if let Ok(var_colorline) = self.color_line() {
411            var_colorline.v1_closure(c);
412            c.add_variation_indices(self.var_index_base(), 4);
413        }
414    }
415}
416
417impl PaintGlyph<'_> {
418    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
419        if let Ok(paint) = self.paint() {
420            c.add_glyph_id(self.glyph_id());
421            c.dispatch(&paint);
422        }
423    }
424}
425
426impl PaintColrGlyph<'_> {
427    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
428        let glyph_id = self.glyph_id();
429        let Some(Ok(list)) = c.colr.base_glyph_list() else {
430            return;
431        };
432        let records = list.base_glyph_paint_records();
433        let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
434            Ok(ix) => &records[ix],
435            _ => return,
436        };
437        if let Ok(paint) = record.paint(list.offset_data()) {
438            c.add_glyph_id(glyph_id);
439            c.dispatch(&paint);
440        }
441    }
442}
443
444impl PaintTransform<'_> {
445    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
446        if let Ok(paint) = self.paint() {
447            c.dispatch(&paint);
448        }
449    }
450}
451
452impl VarAffine2x3<'_> {
453    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
454        c.add_variation_indices(self.var_index_base(), 6);
455    }
456}
457
458impl PaintVarTransform<'_> {
459    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
460        if let Ok(paint) = self.paint() {
461            if let Ok(affine2x3) = self.transform() {
462                c.dispatch(&paint);
463                affine2x3.v1_closure(c);
464            }
465        }
466    }
467}
468
469impl PaintTranslate<'_> {
470    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
471        if let Ok(paint) = self.paint() {
472            c.dispatch(&paint);
473        }
474    }
475}
476
477impl PaintVarTranslate<'_> {
478    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
479        if let Ok(paint) = self.paint() {
480            c.dispatch(&paint);
481            c.add_variation_indices(self.var_index_base(), 2);
482        }
483    }
484}
485
486impl PaintScale<'_> {
487    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
488        if let Ok(paint) = self.paint() {
489            c.dispatch(&paint);
490        }
491    }
492}
493
494impl PaintVarScale<'_> {
495    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
496        if let Ok(paint) = self.paint() {
497            c.dispatch(&paint);
498            c.add_variation_indices(self.var_index_base(), 2);
499        }
500    }
501}
502
503impl PaintScaleAroundCenter<'_> {
504    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
505        if let Ok(paint) = self.paint() {
506            c.dispatch(&paint);
507        }
508    }
509}
510
511impl PaintVarScaleAroundCenter<'_> {
512    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
513        if let Ok(paint) = self.paint() {
514            c.dispatch(&paint);
515            c.add_variation_indices(self.var_index_base(), 4);
516        }
517    }
518}
519
520impl PaintScaleUniform<'_> {
521    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
522        if let Ok(paint) = self.paint() {
523            c.dispatch(&paint);
524        }
525    }
526}
527
528impl PaintVarScaleUniform<'_> {
529    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
530        if let Ok(paint) = self.paint() {
531            c.dispatch(&paint);
532            c.add_variation_indices(self.var_index_base(), 1);
533        }
534    }
535}
536
537impl PaintScaleUniformAroundCenter<'_> {
538    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
539        if let Ok(paint) = self.paint() {
540            c.dispatch(&paint);
541        }
542    }
543}
544
545impl PaintVarScaleUniformAroundCenter<'_> {
546    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
547        if let Ok(paint) = self.paint() {
548            c.dispatch(&paint);
549            c.add_variation_indices(self.var_index_base(), 3);
550        }
551    }
552}
553
554impl PaintRotate<'_> {
555    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
556        if let Ok(paint) = self.paint() {
557            c.dispatch(&paint);
558        }
559    }
560}
561
562impl PaintVarRotate<'_> {
563    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
564        if let Ok(paint) = self.paint() {
565            c.dispatch(&paint);
566            c.add_variation_indices(self.var_index_base(), 1);
567        }
568    }
569}
570
571impl PaintRotateAroundCenter<'_> {
572    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
573        if let Ok(paint) = self.paint() {
574            c.dispatch(&paint);
575        }
576    }
577}
578
579impl PaintVarRotateAroundCenter<'_> {
580    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
581        if let Ok(paint) = self.paint() {
582            c.dispatch(&paint);
583            c.add_variation_indices(self.var_index_base(), 3);
584        }
585    }
586}
587
588impl PaintSkew<'_> {
589    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
590        if let Ok(paint) = self.paint() {
591            c.dispatch(&paint);
592        }
593    }
594}
595
596impl PaintVarSkew<'_> {
597    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
598        if let Ok(paint) = self.paint() {
599            c.dispatch(&paint);
600            c.add_variation_indices(self.var_index_base(), 2);
601        }
602    }
603}
604
605impl PaintSkewAroundCenter<'_> {
606    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
607        if let Ok(paint) = self.paint() {
608            c.dispatch(&paint);
609        }
610    }
611}
612
613impl PaintVarSkewAroundCenter<'_> {
614    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
615        if let Ok(paint) = self.paint() {
616            c.dispatch(&paint);
617            c.add_variation_indices(self.var_index_base(), 4);
618        }
619    }
620}
621
622impl PaintComposite<'_> {
623    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
624        if let Ok(source_paint) = self.source_paint() {
625            c.dispatch(&source_paint);
626        }
627
628        if let Ok(backdrop_paint) = self.backdrop_paint() {
629            c.dispatch(&backdrop_paint);
630        }
631    }
632}
633
634impl Clip {
635    fn v1_closure(&self, c: &mut Colrv1ClosureContext, clip_list: &ClipList) {
636        let Ok(clip_box) = self.clip_box(clip_list.offset_data()) else {
637            return;
638        };
639        let start_id = GlyphId::from(self.start_glyph_id());
640        let end_id = GlyphId::from(self.end_glyph_id());
641        if c.glyph_set.intersects_range(start_id..=end_id) {
642            clip_box.v1_closure(c);
643        }
644    }
645}
646
647impl ClipBox<'_> {
648    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
649        if let Self::Format2(item) = self {
650            item.v1_closure(c)
651        }
652    }
653}
654
655impl ClipBoxFormat2<'_> {
656    fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
657        c.add_variation_indices(self.var_index_base(), 4);
658    }
659}
660
661/// Helper to compute the inclusive end of a range given a start and length,
662/// returning None if len == 0 or the computation would overflow.
663fn compute_inclusive_end(start: u32, len: u32) -> Option<u32> {
664    if len == 0 {
665        return None;
666    }
667    start.checked_add(len).and_then(|v| v.checked_sub(1))
668}
669
670#[cfg(test)]
671mod tests {
672    use super::*;
673    use crate::{FontRef, GlyphId, TableProvider};
674
675    #[test]
676    fn test_colr_v0_closure() {
677        let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
678        let colr = font.colr().unwrap();
679
680        let mut input_glyph_set = IntSet::empty();
681        input_glyph_set.insert(GlyphId::new(168));
682
683        let mut glyph_set_colred = IntSet::empty();
684
685        colr.v0_closure_glyphs(&input_glyph_set, &mut glyph_set_colred);
686        assert_eq!(glyph_set_colred.len(), 9);
687        assert!(glyph_set_colred.contains(GlyphId::new(5)));
688        assert!(glyph_set_colred.contains(GlyphId::new(168)));
689        assert!(glyph_set_colred.contains(GlyphId::new(170)));
690        assert!(glyph_set_colred.contains(GlyphId::new(171)));
691        assert!(glyph_set_colred.contains(GlyphId::new(172)));
692        assert!(glyph_set_colred.contains(GlyphId::new(173)));
693        assert!(glyph_set_colred.contains(GlyphId::new(174)));
694        assert!(glyph_set_colred.contains(GlyphId::new(175)));
695        assert!(glyph_set_colred.contains(GlyphId::new(176)));
696
697        let mut palette_indices = IntSet::empty();
698        colr.v0_closure_palette_indices(&glyph_set_colred, &mut palette_indices);
699        assert_eq!(palette_indices.len(), 8);
700        assert!(palette_indices.contains(0));
701        assert!(palette_indices.contains(1));
702        assert!(palette_indices.contains(2));
703        assert!(palette_indices.contains(3));
704        assert!(palette_indices.contains(4));
705        assert!(palette_indices.contains(5));
706        assert!(palette_indices.contains(6));
707        assert!(palette_indices.contains(10));
708    }
709
710    #[test]
711    fn test_colr_v0_closure_not_found() {
712        let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
713        let colr = font.colr().unwrap();
714
715        let mut input_glyph_set = IntSet::empty();
716        input_glyph_set.insert(GlyphId::new(8));
717
718        let mut glyph_set_colred = IntSet::empty();
719
720        colr.v0_closure_glyphs(&input_glyph_set, &mut glyph_set_colred);
721        assert_eq!(glyph_set_colred.len(), 1);
722        assert!(glyph_set_colred.contains(GlyphId::new(8)));
723    }
724
725    #[test]
726    fn test_colr_v1_closure_no_var() {
727        let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
728        let colr = font.colr().unwrap();
729
730        let mut glyph_set = IntSet::empty();
731        glyph_set.insert(GlyphId::new(220));
732        glyph_set.insert(GlyphId::new(120));
733
734        let mut layer_indices = IntSet::empty();
735        let mut palette_indices = IntSet::empty();
736        let mut variation_indices = IntSet::empty();
737
738        colr.v1_closure(
739            &mut glyph_set,
740            &mut layer_indices,
741            &mut palette_indices,
742            &mut variation_indices,
743        );
744
745        assert_eq!(glyph_set.len(), 6);
746        assert!(glyph_set.contains(GlyphId::new(6)));
747        assert!(glyph_set.contains(GlyphId::new(7)));
748        assert!(glyph_set.contains(GlyphId::new(220)));
749        assert!(glyph_set.contains(GlyphId::new(3)));
750        assert!(glyph_set.contains(GlyphId::new(2)));
751        assert!(glyph_set.contains(GlyphId::new(120)));
752
753        assert_eq!(palette_indices.len(), 5);
754        assert!(palette_indices.contains(0));
755        assert!(palette_indices.contains(4));
756        assert!(palette_indices.contains(10));
757        assert!(palette_indices.contains(11));
758        assert!(palette_indices.contains(12));
759
760        assert_eq!(layer_indices.len(), 2);
761        assert!(layer_indices.contains(0));
762        assert!(layer_indices.contains(1));
763
764        assert!(variation_indices.is_empty());
765    }
766
767    #[test]
768    fn test_colr_v1_closure_w_var() {
769        let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
770        let colr = font.colr().unwrap();
771
772        let mut glyph_set = IntSet::empty();
773        glyph_set.insert(GlyphId::new(109));
774
775        let mut layer_indices = IntSet::empty();
776        let mut palette_indices = IntSet::empty();
777        let mut variation_indices = IntSet::empty();
778
779        colr.v1_closure(
780            &mut glyph_set,
781            &mut layer_indices,
782            &mut palette_indices,
783            &mut variation_indices,
784        );
785
786        assert_eq!(glyph_set.len(), 2);
787        assert!(glyph_set.contains(GlyphId::new(3)));
788        assert!(glyph_set.contains(GlyphId::new(109)));
789
790        assert_eq!(palette_indices.len(), 2);
791        assert!(palette_indices.contains(1));
792        assert!(palette_indices.contains(4));
793
794        assert!(layer_indices.is_empty());
795
796        assert_eq!(variation_indices.len(), 6);
797        assert!(variation_indices.contains(51));
798        assert!(variation_indices.contains(52));
799        assert!(variation_indices.contains(53));
800        assert!(variation_indices.contains(54));
801        assert!(variation_indices.contains(55));
802        assert!(variation_indices.contains(56));
803    }
804
805    #[test]
806    fn test_compute_inclusive_end() {
807        assert_eq!(compute_inclusive_end(0, 0), None);
808        assert_eq!(compute_inclusive_end(0, 1), Some(0));
809        assert_eq!(compute_inclusive_end(0, 2), Some(1));
810        assert_eq!(compute_inclusive_end(1, 1), Some(1));
811        assert_eq!(compute_inclusive_end(1, 2), Some(2));
812        assert_eq!(compute_inclusive_end(u32::MAX, 0), None);
813        assert_eq!(compute_inclusive_end(u32::MAX, 1), None);
814        assert_eq!(compute_inclusive_end(u32::MAX - 1, 1), Some(u32::MAX - 1));
815        assert_eq!(compute_inclusive_end(u32::MAX - 1, 3), None);
816    }
817}