From e8205503350cc0649a642b7a3a012e75039f3391 Mon Sep 17 00:00:00 2001 From: rsheeter Date: Mon, 2 Dec 2024 15:12:43 -0800 Subject: [PATCH] Exploratory --- fontir/src/glyph.rs | 157 +++++++++----------------------------------- 1 file changed, 30 insertions(+), 127 deletions(-) diff --git a/fontir/src/glyph.rs b/fontir/src/glyph.rs index 19149f24..2c95bed4 100644 --- a/fontir/src/glyph.rs +++ b/fontir/src/glyph.rs @@ -186,84 +186,42 @@ enum GlyphOp { /// to be handled first. fn resolve_inconsistencies( context: &Context, - todo: Vec<(GlyphOp, Arc)>, + mut todo: VecDeque<(GlyphOp, Arc)>, // TODO: VecDeque? order: &mut GlyphOrder, ) -> Result<(), BadGlyph> { - let items = todo - .into_iter() - .map(|(op, glyph)| (glyph.name.clone(), (op, glyph))) - .collect::>(); + let mut pending = todo + .iter() + .map(|(_, g)| g.name.clone()) + .collect::>(); + let mut curr_components = Vec::with_capacity(8); // avoid inner reallocs + 'next_todo: while let Some((op, glyph)) = todo.pop_front() { + curr_components.clear(); + curr_components.extend(glyph.component_names().cloned()); + + // We can process curr if none of it's dependencies are pending + while let Some(component_name) = curr_components.pop() { + if pending.contains(&component_name) { + // We can't got yet + todo.push_back((op, glyph)); + break 'next_todo; + } + let glyph = context.glyphs.get(&WorkId::Glyph(component_name)); + curr_components.extend(glyph.component_names().cloned()); + } - for name in component_ordering_within_glyphs(items.values().map(|(_, g)| g.clone())) { - let (op, glyph) = items.get(&name).unwrap(); + // Our component graph doesn't reach any pending component, we are a go! match op { - GlyphOp::ConvertToContour => convert_components_to_contours(context, glyph)?, + GlyphOp::ConvertToContour => convert_components_to_contours(context, &glyph)?, GlyphOp::MoveContoursToComponent => { - move_contours_to_new_component(context, order, glyph)? + move_contours_to_new_component(context, order, &glyph)? } } - } - Ok(()) -} - -// given a sequence of glyphs, return an ordering such that any glyph that -// is a component of another glyph in the input set occurs before it. -// -// this has a funny signature so I can unit test it. -fn component_ordering_within_glyphs( - glyphs: impl Iterator>, -) -> impl Iterator { - // first figure out our order: - let items = glyphs - .map(|glyph| (glyph.name.clone(), glyph)) - .collect::>(); - - // depth of components within the todo list only. - let mut depths = HashMap::new(); - let mut depth_work_queue = Vec::new(); - - // an initial pass to find if any glyphs here use any others as components - for (name, glyph) in &items { - if glyph.component_names().any(|name| items.contains_key(name)) { - depth_work_queue.push(name.clone()); - } else { - depths.insert(name.clone(), 0); - } - } - - let mut progress = depth_work_queue.len(); - while progress > 0 { - progress = depth_work_queue.len(); - depth_work_queue.retain(|next| { - let glyph = items.get(next).unwrap(); - let deepest_component = glyph - .component_names() - .filter(|comp| items.contains_key(*comp)) - .map(|comp| depths.get(comp).copied()) - .try_fold(0, |acc, e| e.map(|e| acc.max(e))); - match deepest_component { - Some(depth) => { - depths.insert(next.clone(), depth + 1); - false - } - None => true, - } - }); - progress -= depth_work_queue.len(); - } - if !depth_work_queue.is_empty() { - log::warn!("component cycle involving glyphs {depth_work_queue:?}"); - // keep going for now, we'll error out later - depths.extend(depth_work_queue.into_iter().map(|g| (g, i32::MAX))); + // I ain't a-gonna pend no more + pending.remove(&glyph.name); } - let mut work_order = depths - .into_iter() - .map(|(name, depth)| (depth, name)) - .collect::>(); - work_order.sort(); - work_order.into_iter().map(|(_, name)| name) + Ok(()) } /// Convert a glyph with contours and components to a contour-only, aka simple, glyph @@ -527,7 +485,7 @@ impl Work for GlyphOrderWork { // 1) need to push their paths to a new glyph that is a component // 2) collapse such glyphs into a simple (contour-only) glyph // fontmake (Python) prefers option 2. - let mut todo = Vec::new(); + let mut todo = VecDeque::new(); for glyph_name in new_glyph_order.iter() { let glyph = original_glyphs.get(glyph_name).unwrap(); if !glyph.has_consistent_components() { @@ -535,16 +493,16 @@ impl Work for GlyphOrderWork { "Coalescing '{glyph_name}' into a simple glyph because \ component 2x2s vary across the designspace" ); - todo.push((GlyphOp::ConvertToContour, glyph.clone())); + todo.push_back((GlyphOp::ConvertToContour, glyph.clone())); } else if has_components_and_contours(glyph) { if context.flags.contains(Flags::PREFER_SIMPLE_GLYPHS) { - todo.push((GlyphOp::ConvertToContour, glyph.clone())); + todo.push_back((GlyphOp::ConvertToContour, glyph.clone())); debug!( "Coalescing '{glyph_name} into a simple glyph; it has \ contours and components and prefer simple glyphs is set", ); } else { - todo.push((GlyphOp::MoveContoursToComponent, glyph.clone())); + todo.push_back((GlyphOp::MoveContoursToComponent, glyph.clone())); } } } @@ -1082,59 +1040,4 @@ mod tests { // infected the deep_component and caused it to be decomposed. assert_is_flattened_component(&context, test_data.deep_component.name); } - - #[derive(Default)] - struct GlyphOrderBuilder(Vec>); - - impl GlyphOrderBuilder { - fn add_glyph(&mut self, name: &str, components: [&str; N]) { - let instance = GlyphInstance { - components: components - .into_iter() - .map(|name| Component { - base: name.into(), - transform: Default::default(), - }) - .collect(), - ..Default::default() - }; - let loc = NormalizedLocation::for_pos(&[("axis", 0.0)]); - let glyph = Glyph::new( - name.into(), - true, - Default::default(), - HashMap::from([(loc, instance)]), - ) - .unwrap(); - self.0.push(Arc::new(glyph)) - } - } - - #[test] - fn component_sorting() { - let mut builder = GlyphOrderBuilder::default(); - builder.add_glyph("a", ["b", "c"]); - builder.add_glyph("b", ["z"]); - builder.add_glyph("c", ["d"]); - builder.add_glyph("d", ["x", "y"]); - builder.add_glyph("e", ["z"]); - - let sorted = component_ordering_within_glyphs(builder.0.into_iter()).collect::>(); - - assert_eq!(sorted, ["b", "d", "e", "c", "a"]); - } - - // all we care about here is that we don't deadlock - #[test] - fn component_sorting_with_cycle() { - let mut builder = GlyphOrderBuilder::default(); - builder.add_glyph("a", ["z"]); - builder.add_glyph("b", ["c"]); - builder.add_glyph("c", ["d"]); - builder.add_glyph("z", ["a"]); // a cycle - // - let sorted = component_ordering_within_glyphs(builder.0.into_iter()).collect::>(); - - assert_eq!(sorted, ["c", "b", "a", "z"]); - } }