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