@@ -18,7 +18,7 @@ use crate::{
1818 instance:: Size ,
1919 outline:: { cff, glyf, metrics:: GlyphHMetrics , pen:: PathStyle , DrawError , OutlinePen } ,
2020 provider:: MetadataProvider ,
21- GLYF_COMPOSITE_RECURSION_LIMIT ,
21+ GLYF_COMPOSITE_RECURSION_LIMIT , MAX_GRAPH_EDGES ,
2222} ;
2323
2424#[ cfg( feature = "libm" ) ]
@@ -39,6 +39,7 @@ struct Scratchpad {
3939 deltas : DeltaVec ,
4040 axis_indices : AxisIndexVec ,
4141 axis_values : AxisValueVec ,
42+ edges_left : usize ,
4243}
4344
4445impl Scratchpad {
@@ -47,6 +48,7 @@ impl Scratchpad {
4748 deltas : DeltaVec :: new ( ) ,
4849 axis_indices : AxisIndexVec :: new ( ) ,
4950 axis_values : AxisValueVec :: new ( ) ,
51+ edges_left : MAX_GRAPH_EDGES ,
5052 }
5153 }
5254}
@@ -217,21 +219,29 @@ impl<'a> Outlines<'a> {
217219 coverage_index : u16 ,
218220 ) -> Result < usize , ReadError > {
219221 let mut stack = GlyphStack :: new ( ) ;
220- self . max_component_memory_for_glyph ( glyph_id, coverage_index, & mut stack)
222+ let mut edges_left = MAX_GRAPH_EDGES ;
223+ self . max_component_memory_for_glyph ( glyph_id, coverage_index, & mut stack, & mut edges_left)
221224 }
222225
223226 fn max_component_memory_for_glyph (
224227 & self ,
225228 glyph_id : GlyphId ,
226229 coverage_index : u16 ,
227230 stack : & mut GlyphStack ,
231+ edges_left : & mut usize ,
228232 ) -> Result < usize , ReadError > {
229233 if stack. contains ( & glyph_id) {
230234 return Ok ( 0 ) ;
231235 }
236+ // HB returns success for both recursion and edge limits, so we do the same.
237+ // See <https://github.com/harfbuzz/harfbuzz/blob/0fef675a5ea4973ce49d6dd00c02e70ec409eee3/src/OT/Var/VARC/VARC.cc#L386>
232238 if stack. len ( ) >= GLYF_COMPOSITE_RECURSION_LIMIT {
233239 return Ok ( 0 ) ;
234240 }
241+ if * edges_left == 0 {
242+ return Ok ( 0 ) ;
243+ }
244+ * edges_left -= 1 ;
235245 stack. push ( glyph_id) ;
236246 let mut max_memory = 0usize ;
237247 let glyph = self . varc . glyph ( coverage_index as usize ) ?;
@@ -241,7 +251,12 @@ impl<'a> Outlines<'a> {
241251 let component_memory = if component_gid == glyph_id {
242252 self . base . base_outline_memory ( component_gid)
243253 } else if let Some ( coverage_index) = self . coverage_index ( component_gid) ? {
244- self . max_component_memory_for_glyph ( component_gid, coverage_index, stack) ?
254+ self . max_component_memory_for_glyph (
255+ component_gid,
256+ coverage_index,
257+ stack,
258+ edges_left,
259+ ) ?
245260 } else {
246261 self . base . base_outline_memory ( component_gid)
247262 } ;
@@ -317,6 +332,10 @@ impl<'a> Outlines<'a> {
317332 if stack. len ( ) >= GLYF_COMPOSITE_RECURSION_LIMIT {
318333 return Err ( DrawError :: RecursionLimitExceeded ( glyph_id) ) ;
319334 }
335+ if scratch. edges_left == 0 {
336+ return Err ( DrawError :: RecursionLimitExceeded ( glyph_id) ) ;
337+ }
338+ scratch. edges_left -= 1 ;
320339 let glyph = self . varc . glyph ( coverage_index as usize ) ?;
321340 stack. push ( glyph_id) ;
322341 let coverage = ctx. coverage ;
@@ -1406,4 +1425,84 @@ mod tests {
14061425 & mut pen,
14071426 ) ;
14081427 }
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+ // Pick an unconditional child edge so the traversal always attempts
1437+ // to recurse for this component.
1438+ 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+ // Budget of one edge allows entering the root glyph only; any recursive
1474+ // component edge must be rejected as over budget.
1475+ 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+ }
14091508}
0 commit comments