Skip to content

Commit

Permalink
Flatten
Browse files Browse the repository at this point in the history
  • Loading branch information
rsheeter committed Apr 18, 2023
1 parent 9df4b92 commit bccfd78
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 50 deletions.
163 changes: 113 additions & 50 deletions fontir/src/glyph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ fn components(glyph: &Glyph, transform: Affine) -> VecDeque<(NormalizedLocation,
/// At time of writing we only support this if every instance uses the same set of components.
///
/// <https://github.com/googlefonts/ufo2ft/blob/dd738cdcddf61cce2a744d1cafab5c9b33e92dd4/Lib/ufo2ft/util.py#L165>
fn convert_components_to_contours(context: &Context, original: &Glyph) -> Result<Glyph, WorkError> {
fn convert_components_to_contours(context: &Context, original: &Glyph) -> Result<(), WorkError> {
let mut simple: GlyphBuilder = original.into();
simple
.sources
Expand Down Expand Up @@ -272,7 +272,87 @@ fn convert_components_to_contours(context: &Context, original: &Glyph) -> Result
}
}

simple.try_into()
let simple: Glyph = simple.try_into()?;
debug_assert!(
simple.name == original.name,
"{} != {}",
simple.name,
original.name
);
context.set_glyph_ir(simple);
Ok(())
}

fn move_contours_to_new_component(
context: &Context,
new_glyph_order: &mut IndexSet<GlyphName>,
glyph: &Glyph,
) -> Result<(), WorkError> {
debug!(
"Hoisting the contours from '{0}' into a new component",
glyph.name
);
let (simple, composite) = split_glyph(&new_glyph_order, &glyph)?;

// Capture the updated/new IR and update glyph order
debug_assert!(composite.name == glyph.name);
debug_assert!(simple.name != glyph.name);

new_glyph_order.insert(simple.name.clone());
context.set_glyph_ir(simple);
context.set_glyph_ir(composite);
Ok(())
}

/// Make sure components only reference simple (contour) glyphs.
///
/// Assumed to run after component consistency is checked/fixed so we can assume
/// that no mixed contour+component glyphs exist.
///
/// See <https://github.com/googlefonts/ufo2ft/blob/main/Lib/ufo2ft/filters/flattenComponents.py>
fn flatten_components(context: &Context, glyph: &Glyph) -> Result<(), WorkError> {
// Guard: nothing to see here folks
if glyph.default_instance().components.is_empty() {
return Ok(());
}
trace!(
"Flatten {} {:?}",
glyph.name,
glyph.default_instance().components
);
let mut glyph = glyph.clone();
for (loc, inst) in glyph.sources_mut() {
let mut simple = Vec::new();
let mut frontier = VecDeque::new();
frontier.extend(inst.components.split_off(0));
while let Some(component) = frontier.pop_front() {
let ref_glyph = context.get_glyph_ir(&component.base);
let ref_inst = ref_glyph.sources().get(loc).ok_or_else(|| {
WorkError::GlyphUndefAtNormalizedLocation {
glyph_name: ref_glyph.name.clone(),
pos: loc.clone(),
}
})?;
if ref_inst.components.is_empty() {
simple.push(component.clone());
} else {
for ref_component in ref_inst.components.iter().rev() {
frontier.push_front(Component {
base: ref_component.base.clone(),
transform: component.transform * ref_component.transform,
});
}
}
}
inst.components = simple;
}
trace!(
"Flattened {} to {:?}",
glyph.name,
glyph.default_instance().components
);
context.set_glyph_ir(glyph);
Ok(())
}

impl Work<Context, WorkError> for FinalizeStaticMetadataWork {
Expand All @@ -288,57 +368,36 @@ impl Work<Context, WorkError> for FinalizeStaticMetadataWork {
// 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.
for (glyph_to_fix, has_consistent_2x2_transforms) in current_metadata
.glyph_order
.iter()
.map(|gn| context.get_glyph_ir(gn))
.map(|glyph| {
let consistent_transforms = has_consistent_2x2_transforms(&glyph);
(glyph, consistent_transforms)
})
.filter(|(glyph, has_consistent_2x2_transforms)| {
!has_consistent_2x2_transforms || has_components_and_contours(glyph)
})
{
if !has_consistent_2x2_transforms || context.flags.contains(Flags::PREFER_SIMPLE_GLYPHS)
{
if !has_consistent_2x2_transforms {
for glyph_name in current_metadata.glyph_order.iter() {
let glyph = context.get_glyph_ir(glyph_name);
let inconsistent_components = !has_consistent_2x2_transforms(&glyph);
if inconsistent_components || has_components_and_contours(&glyph) {
if inconsistent_components {
debug!(
"Coalescing'{0}' into a simple glyph because component 2x2s vary across the designspace",
glyph_to_fix.name
glyph.name
);
} else {
convert_components_to_contours(context, &glyph)?;
} else if context.flags.contains(Flags::PREFER_SIMPLE_GLYPHS) {
debug!(
"Coalescing'{0}' into a simple glyph because it has contours and components and that's how fontmake handles it",
glyph_to_fix.name
"Coalescing'{0}' into a simple glyph because it has contours and components and prefer simple glyphs is set",
glyph.name
);
convert_components_to_contours(context, &glyph)?;
} else {
move_contours_to_new_component(context, &mut new_glyph_order, &glyph)?;
}
let simple = convert_components_to_contours(context, &glyph_to_fix)?;
debug_assert!(
simple.name == glyph_to_fix.name,
"{} != {}",
simple.name,
glyph_to_fix.name
);
context.set_glyph_ir(simple);
} else {
// We don't have to match fontmake; prefer to retain components than to collapse to simple glyph
debug!(
"Hoisting the contours from '{0}' into a new component",
glyph_to_fix.name
);
let (simple, composite) = split_glyph(&new_glyph_order, &glyph_to_fix)?;

// Capture the updated/new IR and update glyph order
debug_assert!(composite.name == glyph_to_fix.name);
debug_assert!(simple.name != glyph_to_fix.name);

new_glyph_order.insert(simple.name.clone());
context.set_glyph_ir(simple);
context.set_glyph_ir(composite);
}
}

if context.flags.contains(Flags::FLATTEN_COMPONENTS) {
for glyph_name in new_glyph_order.iter() {
let glyph = context.get_glyph_ir(glyph_name);
flatten_components(context, &glyph)?;
}
}

// We now have the final static metadata
// If the glyph order changed try not to forget about it
if current_metadata.glyph_order != new_glyph_order {
if log_enabled!(log::Level::Trace) {
Expand All @@ -358,7 +417,6 @@ impl Work<Context, WorkError> for FinalizeStaticMetadataWork {
trace!("No new glyphs; final static metadata is unchanged");
context.set_final_static_metadata((*current_metadata).clone());
}

Ok(())
}
}
Expand Down Expand Up @@ -553,7 +611,8 @@ mod tests {
);
context.set_glyph_ir(contour_glyph("component"));

let simple = convert_components_to_contours(&context, &coalesce_me).unwrap();
convert_components_to_contours(&context, &coalesce_me).unwrap();
let simple = context.get_glyph_ir(&coalesce_me.name);
assert_simple(&simple);

// Our sample is unimaginative; both weights are identical
Expand Down Expand Up @@ -622,7 +681,8 @@ mod tests {
.unwrap();
let nested_components = nested_components.try_into().unwrap();

let simple = convert_components_to_contours(&context, &nested_components).unwrap();
convert_components_to_contours(&context, &nested_components).unwrap();
let simple = context.get_glyph_ir(&nested_components.name);
assert_simple(&simple);
assert_eq!(1, simple.sources().len());
let inst = simple.default_instance();
Expand Down Expand Up @@ -676,7 +736,8 @@ mod tests {
context.set_glyph_ir(reuse_me);

let glyph = glyph.try_into().unwrap();
let simple = convert_components_to_contours(&context, &glyph).unwrap();
convert_components_to_contours(&context, &glyph).unwrap();
let simple = context.get_glyph_ir(&glyph.name);
assert_simple(&simple);
assert_eq!(1, simple.sources().len());
let inst = simple.sources().values().next().unwrap();
Expand Down Expand Up @@ -739,7 +800,8 @@ mod tests {
);
context.set_glyph_ir(contour_glyph("component"));

let simple = convert_components_to_contours(&context, &glyph).unwrap();
convert_components_to_contours(&context, &glyph).unwrap();
let simple = context.get_glyph_ir(&glyph.name);
assert_simple(&simple);
}

Expand All @@ -754,7 +816,8 @@ mod tests {
);
context.set_glyph_ir(contour_glyph("component"));

let simple = convert_components_to_contours(&context, &glyph).unwrap();
convert_components_to_contours(&context, &glyph).unwrap();
let simple = context.get_glyph_ir(&glyph.name);
assert_simple(&simple);
}

Expand Down
6 changes: 6 additions & 0 deletions fontir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,12 @@ impl Glyph {
&self.sources
}

pub fn sources_mut(
&mut self,
) -> impl Iterator<Item = (&NormalizedLocation, &mut GlyphInstance)> {
self.sources.iter_mut()
}

pub fn source_mut(&mut self, loc: &NormalizedLocation) -> Option<&mut GlyphInstance> {
self.sources.get_mut(loc)
}
Expand Down

0 comments on commit bccfd78

Please sign in to comment.