1use super::{load, BufferLengths, Buffers, Hinter, OutlineContext, OutlineRef, Scale};
4use crate::model::Error;
5use crate::{
6 limits::{MAX_COMPOSITE_EDGES, MAX_OUTLINE_POINTS, MAX_RECURSION_DEPTH},
7 tables::glyf::{CompositeGlyphFlags, Glyph, PHANTOM_POINT_COUNT},
8 types::GlyphId,
9};
10
11#[derive(Clone, Default)]
17pub struct OutlinePlan<'a> {
18 pub glyph: GlyphId,
20 pub glyph_data: Option<Glyph<'a>>,
26 pub num_points: u32,
29 pub num_contours: u32,
31 pub max_simple_points: u32,
34 pub max_hinted_composite_points: u32,
41 pub max_component_delta_stack: u32,
44 pub has_hinting: bool,
46 pub has_overlaps: bool,
49 pub applies_variations: bool,
55}
56
57impl core::fmt::Debug for OutlinePlan<'_> {
58 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59 f.debug_struct("OutlinePlan")
62 .field("glyph", &self.glyph)
63 .field("is_empty", &self.glyph_data.is_none())
64 .field("num_points", &self.num_points)
65 .field("num_contours", &self.num_contours)
66 .field("max_simple_points", &self.max_simple_points)
67 .field(
68 "max_hinted_composite_points",
69 &self.max_hinted_composite_points,
70 )
71 .field("max_component_delta_stack", &self.max_component_delta_stack)
72 .field("has_hinting", &self.has_hinting)
73 .field("has_overlaps", &self.has_overlaps)
74 .field("applies_variations", &self.applies_variations)
75 .finish()
76 }
77}
78
79impl<'a> OutlinePlan<'a> {
80 pub fn new(context: &dyn OutlineContext<'a>, glyph: GlyphId) -> Result<Self, Error> {
83 let mut plan = Self {
84 glyph,
85 applies_variations: context.has_gvar() && !context.coords().is_empty(),
86 ..Default::default()
87 };
88 let glyph_data = context
89 .glyph(glyph)
90 .ok_or(Error::MissingResource)?
91 .into_glyph();
92 if let Some(data) = glyph_data.as_ref() {
93 let mut components = 0;
94 plan.walk(context, data, 0, 0, &mut components)?;
95 }
96 plan.num_points += PHANTOM_POINT_COUNT as u32;
99 plan.glyph_data = glyph_data;
100 Ok(plan)
101 }
102
103 pub fn buffer_lengths<S: Scale>(&self) -> BufferLengths {
105 let applies_variations = self.applies_variations;
106 BufferLengths {
107 points: self.num_points,
108 contours: self.num_contours,
109 unscaled: if S::NEEDS_UNSCALED {
110 self.max_simple_points.max(self.max_hinted_composite_points)
111 } else {
112 0
113 },
114 deltas: if applies_variations {
115 self.max_simple_points
116 } else {
117 0
118 },
119 iup: if applies_variations {
120 self.max_simple_points
121 } else {
122 0
123 },
124 composite_deltas: if applies_variations {
125 self.max_component_delta_stack
126 } else {
127 0
128 },
129 }
130 }
131
132 pub fn load<'s, 'buf, S: Scale>(
176 &'s self,
177 context: &'a dyn OutlineContext<'a>,
178 scale: &'s S,
179 buffers: Buffers<'buf, S>,
180 hinter: Option<&mut dyn Hinter<S::Coord>>,
181 ) -> Result<OutlineRef<'buf, S::Coord>, Error> {
182 load::load(self, context, scale, buffers, hinter)
183 }
184
185 fn walk(
186 &mut self,
187 context: &dyn OutlineContext<'a>,
188 glyph: &Glyph<'a>,
189 component_depth: u32,
190 recurse_depth: usize,
191 components: &mut usize,
192 ) -> Result<(), Error> {
193 if recurse_depth > MAX_RECURSION_DEPTH {
194 return Err(Error::LimitExceeded);
195 }
196 match glyph {
197 Glyph::Simple(simple) => {
198 let point_count = simple.num_points() as u32;
199 let with_phantom = point_count + PHANTOM_POINT_COUNT as u32;
200 self.max_simple_points = self.max_simple_points.max(with_phantom);
201 self.num_points += point_count;
202 if self.num_points as usize > MAX_OUTLINE_POINTS {
203 return Err(Error::LimitExceeded);
204 }
205 self.num_contours += simple.end_pts_of_contours().len() as u32;
206 self.has_hinting |= simple.instruction_length() != 0;
207 self.has_overlaps |= simple.has_overlapping_contours();
208 }
209 Glyph::Composite(composite) => {
210 let (component_count, instructions) = composite.count_and_instructions();
211 let level_size = component_count as u32 + PHANTOM_POINT_COUNT as u32;
214 let point_base = self.num_points;
215 for (component, flags) in composite.component_glyphs_and_flags() {
216 self.has_overlaps |= flags.contains(CompositeGlyphFlags::OVERLAP_COMPOUND);
217 let Some(component_glyph) = context
218 .glyph(component)
219 .ok_or(Error::MissingResource)?
220 .into_glyph()
221 else {
222 continue;
223 };
224 *components += 1;
225 if *components > MAX_COMPOSITE_EDGES {
226 return Err(Error::LimitExceeded);
227 }
228 self.walk(
229 context,
230 &component_glyph,
231 component_depth + level_size,
232 recurse_depth + 1,
233 components,
234 )?;
235 }
236 if !instructions.unwrap_or_default().is_empty() {
237 let loaded = self.num_points - point_base + PHANTOM_POINT_COUNT as u32;
240 self.max_hinted_composite_points = self.max_hinted_composite_points.max(loaded);
241 self.has_hinting = true;
242 }
243 self.max_component_delta_stack = self
244 .max_component_delta_stack
245 .max(component_depth + level_size);
246 }
247 }
248 Ok(())
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::super::{OutlineTables, Scale26Dot6, ScaleF32};
255 use super::*;
256 use crate::{
257 limits::{MAX_COMPOSITE_EDGES, MAX_RECURSION_DEPTH},
258 tables::{glyf::Glyf, loca::Loca},
259 FontData, FontRead,
260 };
261 use crate::{FontRef, TableProvider};
262 use alloc::{vec, vec::Vec};
263
264 fn components_font() -> FontRef<'static> {
269 FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap()
270 }
271
272 fn info_for(font: &FontRef<'static>, gid: u32) -> OutlinePlan<'static> {
273 let context = OutlineTables::new(font).unwrap();
274 OutlinePlan::new(&context, GlyphId::new(gid)).unwrap()
275 }
276
277 #[test]
278 fn empty_glyph_has_only_phantom_points() {
279 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
280 let context = OutlineTables::new(&font).unwrap();
281 let plan = OutlinePlan::new(&context, GlyphId::new(0)).unwrap();
283 assert!(plan.glyph_data.is_none());
284 assert_eq!(plan.num_points, PHANTOM_POINT_COUNT as u32);
285 assert_eq!(plan.num_contours, 0);
286 assert_eq!(plan.max_simple_points, 0);
287 assert_eq!(plan.max_component_delta_stack, 0);
288 assert!(!plan.has_hinting);
289 }
290
291 #[test]
292 fn simple_glyph() {
293 let font = components_font();
294 let plan = info_for(&font, 1);
296 assert_eq!(plan.num_points, 4 + PHANTOM_POINT_COUNT as u32);
297 assert_eq!(plan.num_contours, 1);
298 assert_eq!(plan.max_simple_points, 4 + PHANTOM_POINT_COUNT as u32);
299 assert_eq!(plan.max_hinted_composite_points, 0);
300 assert_eq!(plan.max_component_delta_stack, 0);
301 assert!(!plan.has_hinting);
302 }
303
304 #[test]
305 fn simple_glyph_with_two_contours() {
306 let font = components_font();
307 let plan = info_for(&font, 0);
309 assert_eq!(plan.num_points, 8 + PHANTOM_POINT_COUNT as u32);
310 assert_eq!(plan.num_contours, 2);
311 assert_eq!(plan.max_simple_points, 8 + PHANTOM_POINT_COUNT as u32);
312 }
313
314 #[test]
315 fn composite_with_one_component() {
316 let font = components_font();
317 let plan = info_for(&font, 2);
319 assert_eq!(plan.num_points, 4 + PHANTOM_POINT_COUNT as u32);
320 assert_eq!(plan.num_contours, 1);
321 assert_eq!(plan.max_simple_points, 4 + PHANTOM_POINT_COUNT as u32);
322 assert_eq!(
324 plan.max_component_delta_stack,
325 1 + PHANTOM_POINT_COUNT as u32
326 );
327 assert_eq!(plan.max_hinted_composite_points, 0);
329 assert!(!plan.has_hinting);
330 }
331
332 #[test]
333 fn composite_with_two_components() {
334 let font = components_font();
335 let plan = info_for(&font, 5);
337 assert_eq!(plan.num_points, 8 + PHANTOM_POINT_COUNT as u32);
338 assert_eq!(plan.num_contours, 2);
339 assert_eq!(plan.max_simple_points, 4 + PHANTOM_POINT_COUNT as u32);
341 assert_eq!(
342 plan.max_component_delta_stack,
343 2 + PHANTOM_POINT_COUNT as u32
344 );
345 }
346
347 #[test]
350 fn nested_composite_deepens_the_delta_stack() {
351 let font = components_font();
352 let plan = info_for(&font, 7);
354 assert_eq!(plan.num_points, 4 + PHANTOM_POINT_COUNT as u32);
355 assert_eq!(plan.num_contours, 1);
356 assert_eq!(
358 plan.max_component_delta_stack,
359 2 * (1 + PHANTOM_POINT_COUNT as u32)
360 );
361 }
362
363 #[test]
364 fn buffer_lengths_follow_the_walk() {
365 let font = components_font();
366 let plan = info_for(&font, 5);
367
368 let plain = plan.buffer_lengths::<ScaleF32>();
370 assert_eq!(plain.points, plan.num_points);
371 assert_eq!(plain.contours, plan.num_contours);
372 assert_eq!(plain.unscaled, 0);
373 assert_eq!(plain.deltas, 0);
374 assert_eq!(plain.iup, 0);
375 assert_eq!(plain.composite_deltas, 0);
376
377 let unscaled = plan.buffer_lengths::<Scale26Dot6>();
379 assert_eq!(unscaled.unscaled, plan.max_simple_points);
380 assert_eq!(unscaled.deltas, 0);
381
382 let mut plan = plan;
385 plan.applies_variations = true;
386 let varied = plan.buffer_lengths::<ScaleF32>();
387 assert_eq!(varied.unscaled, 0);
388 assert_eq!(varied.deltas, plan.max_simple_points);
389 assert_eq!(varied.iup, plan.max_simple_points);
390 assert_eq!(varied.composite_deltas, plan.max_component_delta_stack);
391 }
392
393 #[test]
396 fn buffer_lengths_unscaled_takes_the_larger() {
397 let mut plan = OutlinePlan {
398 max_simple_points: 10,
399 max_hinted_composite_points: 40,
400 ..Default::default()
401 };
402 assert_eq!(plan.buffer_lengths::<Scale26Dot6>().unscaled, 40);
403 plan.max_hinted_composite_points = 4;
404 assert_eq!(plan.buffer_lengths::<Scale26Dot6>().unscaled, 10);
405 }
406
407 #[test]
408 fn detects_hinting() {
409 let font = FontRef::new(font_test_data::COUSINE_HINT_SUBSET).unwrap();
410 let context = OutlineTables::new(&font).unwrap();
411 let n = font.maxp().unwrap().num_glyphs();
412 let hinted = (0..n)
413 .filter(|gid| {
414 OutlinePlan::new(&context, GlyphId::from(*gid))
415 .map(|plan| plan.has_hinting)
416 .unwrap_or(false)
417 })
418 .count();
419 assert!(hinted > 0, "expected some instructed glyphs");
420 }
421
422 fn tables_from_glyphs(glyphs: &[Vec<u8>]) -> (Vec<u8>, Vec<u8>) {
424 let mut glyf = Vec::new();
425 let mut loca: Vec<u8> = Vec::new();
426 for glyph in glyphs {
427 loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
428 glyf.extend_from_slice(glyph);
429 }
430 loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
431 (glyf, loca)
432 }
433
434 fn composite(components: &[u16]) -> Vec<u8> {
436 const ARGS_ARE_WORDS: u16 = 0x0001;
437 const MORE_COMPONENTS: u16 = 0x0020;
438 let mut out = Vec::new();
439 out.extend_from_slice(&(-1i16).to_be_bytes()); out.extend_from_slice(&[0u8; 8]); for (i, &gid) in components.iter().enumerate() {
442 let mut flags = ARGS_ARE_WORDS;
443 if i + 1 < components.len() {
444 flags |= MORE_COMPONENTS;
445 }
446 out.extend_from_slice(&flags.to_be_bytes());
447 out.extend_from_slice(&gid.to_be_bytes());
448 out.extend_from_slice(&0i16.to_be_bytes()); out.extend_from_slice(&0i16.to_be_bytes()); }
451 out
452 }
453
454 fn empty_simple() -> Vec<u8> {
456 let mut out = Vec::new();
457 out.extend_from_slice(&0i16.to_be_bytes()); out.extend_from_slice(&[0u8; 8]); out.extend_from_slice(&0u16.to_be_bytes()); out
461 }
462
463 fn info_from_raw(glyf_bytes: &[u8], loca_bytes: &[u8], gid: u32) -> Result<(), Error> {
464 let glyf = Glyf::read(FontData::new(glyf_bytes)).unwrap();
465 let loca = Loca::read(FontData::new(loca_bytes), true).unwrap();
466 let context = OutlineTables {
467 glyf,
468 loca,
469 gvar: None,
470 hmtx: None,
471 os2: None,
472 hvar: None,
473 units_per_em: 1000,
474 coords: &[],
475 gvar_scalars: &[],
476 };
477 OutlinePlan::new(&context, GlyphId::new(gid)).map(|_| ())
478 }
479
480 #[test]
483 fn self_referencing_composite_hits_the_recursion_limit() {
484 let (glyf, loca) = tables_from_glyphs(&[composite(&[0])]);
485 assert_eq!(info_from_raw(&glyf, &loca, 0), Err(Error::LimitExceeded));
486 }
487
488 #[test]
490 fn recursion_limit_boundary() {
491 let chain = |depth: usize| -> (Vec<u8>, Vec<u8>) {
494 let mut glyphs: Vec<Vec<u8>> =
495 (0..depth).map(|i| composite(&[(i + 1) as u16])).collect();
496 glyphs.push(empty_simple());
497 tables_from_glyphs(&glyphs)
498 };
499 let (glyf, loca) = chain(MAX_RECURSION_DEPTH);
500 assert!(info_from_raw(&glyf, &loca, 0).is_ok());
501 let (glyf, loca) = chain(MAX_RECURSION_DEPTH + 2);
502 assert_eq!(info_from_raw(&glyf, &loca, 0), Err(Error::LimitExceeded));
503 }
504
505 #[test]
507 fn wide_composite_hits_the_component_limit() {
508 let within: Vec<u16> = vec![1; MAX_COMPOSITE_EDGES];
509 let (glyf, loca) = tables_from_glyphs(&[composite(&within), empty_simple()]);
510 assert!(info_from_raw(&glyf, &loca, 0).is_ok());
511
512 let beyond: Vec<u16> = vec![1; MAX_COMPOSITE_EDGES + 1];
513 let (glyf, loca) = tables_from_glyphs(&[composite(&beyond), empty_simple()]);
514 assert_eq!(info_from_raw(&glyf, &loca, 0), Err(Error::LimitExceeded));
515 }
516
517 #[test]
520 fn empty_components_are_skipped() {
521 let mut glyf = Vec::new();
523 let mut loca: Vec<u8> = Vec::new();
524 let root = composite(&[1]);
525 loca.extend_from_slice(&0u32.to_be_bytes());
526 glyf.extend_from_slice(&root);
527 loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
529 loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
530 assert!(info_from_raw(&glyf, &loca, 0).is_ok());
531 }
532}