-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Capture overlap bits from TrueType glyphs #582
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,6 +145,17 @@ impl<'a> SimpleGlyph<'a> { | |
.unwrap_or(0) | ||
} | ||
|
||
/// Returns true if the contours in the simple glyph may overlap. | ||
pub fn has_overlapping_contours(&self) -> bool { | ||
// Checks the first flag for the OVERLAP_SIMPLE bit. | ||
// Spec says: "When used, it must be set on the first flag byte for | ||
// the glyph." | ||
FontData::new(self.glyph_data()) | ||
.read_at::<SimpleGlyphFlags>(0) | ||
.map(|flag| flag.contains(SimpleGlyphFlags::OVERLAP_SIMPLE)) | ||
.unwrap_or_default() | ||
} | ||
|
||
/// Reads points and flags into the provided buffers. | ||
/// | ||
/// Drops all flag bits except on-curve. The lengths of the buffers must be | ||
|
@@ -477,10 +488,12 @@ impl<'a> CompositeGlyph<'a> { | |
} | ||
} | ||
|
||
/// Returns an iterator that yields the glyph identifier of each component | ||
/// in the composite glyph. | ||
pub fn component_glyphs(&self) -> impl Iterator<Item = GlyphId> + 'a + Clone { | ||
ComponentGlyphIdIter { | ||
/// Returns an iterator that yields the glyph identifier and flags of each | ||
/// component in the composite glyph. | ||
pub fn component_glyphs_and_flags( | ||
&self, | ||
) -> impl Iterator<Item = (GlyphId, CompositeGlyphFlags)> + 'a + Clone { | ||
ComponentGlyphIdFlagsIter { | ||
cur_flags: CompositeGlyphFlags::empty(), | ||
done: false, | ||
cursor: FontData::new(self.component_data()).cursor(), | ||
|
@@ -490,7 +503,7 @@ impl<'a> CompositeGlyph<'a> { | |
/// Returns the component count and TrueType interpreter instructions | ||
/// in a single pass. | ||
pub fn count_and_instructions(&self) -> (usize, Option<&'a [u8]>) { | ||
let mut iter = ComponentGlyphIdIter { | ||
let mut iter = ComponentGlyphIdFlagsIter { | ||
cur_flags: CompositeGlyphFlags::empty(), | ||
done: false, | ||
cursor: FontData::new(self.component_data()).cursor(), | ||
|
@@ -581,19 +594,19 @@ impl Iterator for ComponentIter<'_> { | |
} | ||
} | ||
|
||
/// Iterator that only returns glyph identifiers for each component. | ||
/// Iterator that only returns glyph identifiers and flags for each component. | ||
/// | ||
/// Significantly faster in cases where we're just processing the glyph | ||
/// tree, counting components or accessing instructions. | ||
#[derive(Clone)] | ||
struct ComponentGlyphIdIter<'a> { | ||
struct ComponentGlyphIdFlagsIter<'a> { | ||
cur_flags: CompositeGlyphFlags, | ||
done: bool, | ||
cursor: Cursor<'a>, | ||
} | ||
|
||
impl Iterator for ComponentGlyphIdIter<'_> { | ||
type Item = GlyphId; | ||
impl Iterator for ComponentGlyphIdFlagsIter<'_> { | ||
type Item = (GlyphId, CompositeGlyphFlags); | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if self.done { | ||
|
@@ -616,7 +629,7 @@ impl Iterator for ComponentGlyphIdIter<'_> { | |
self.cursor.advance_by(8); | ||
} | ||
self.done = !flags.contains(CompositeGlyphFlags::MORE_COMPONENTS); | ||
Some(glyph) | ||
Some((glyph, flags)) | ||
} | ||
} | ||
|
||
|
@@ -945,6 +958,52 @@ mod tests { | |
); | ||
} | ||
|
||
#[test] | ||
fn simple_glyph_overlapping_contour_flag() { | ||
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap(); | ||
let loca = font.loca(None).unwrap(); | ||
let glyf = font.glyf().unwrap(); | ||
let glyph_count = font.maxp().unwrap().num_glyphs(); | ||
for gid in 0..glyph_count { | ||
let glyph = match loca.get_glyf(GlyphId::new(gid), &glyf) { | ||
Ok(Some(Glyph::Simple(glyph))) => glyph, | ||
_ => continue, | ||
}; | ||
if gid == 3 { | ||
// Only GID 3 has the overlap bit set | ||
assert!(glyph.has_overlapping_contours()) | ||
} else { | ||
assert!(!glyph.has_overlapping_contours()) | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn composite_overlapping_contour_flag() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The loading of test data, num glyphs, etc is repetitive noise; perhaps a test helper that lets you easily iterate over (gid, &Glyph) would help tidy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The refactoring part of my brain seems to turn off when writing tests. Good call, ty! |
||
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap(); | ||
let loca = font.loca(None).unwrap(); | ||
let glyf = font.glyf().unwrap(); | ||
let glyph_count = font.maxp().unwrap().num_glyphs(); | ||
for gid in 0..glyph_count { | ||
let glyph = match loca.get_glyf(GlyphId::new(gid), &glyf) { | ||
Ok(Some(Glyph::Composite(glyph))) => glyph, | ||
_ => continue, | ||
}; | ||
// Only GID 2, component 1 has the overlap bit set | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above I might prefer it as something like |
||
for (component_ix, component) in glyph.components().enumerate() { | ||
if gid == 2 && component_ix == 1 { | ||
assert!(component | ||
.flags | ||
.contains(CompositeGlyphFlags::OVERLAP_COMPOUND)) | ||
} else { | ||
assert!(!component | ||
.flags | ||
.contains(CompositeGlyphFlags::OVERLAP_COMPOUND)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn compute_anchor_flags() { | ||
let anchor = Anchor::Offset { x: -128, y: 127 }; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,12 +112,14 @@ impl<'a> Scaler<'a> { | |
info.contours += simple.end_pts_of_contours().len(); | ||
info.has_hinting = info.has_hinting || simple.instruction_length() != 0; | ||
info.max_other_points = info.max_other_points.max(num_points_with_phantom); | ||
info.has_overlaps |= simple.has_overlapping_contours(); | ||
} | ||
Glyph::Composite(composite) => { | ||
let (mut count, instructions) = composite.count_and_instructions(); | ||
count += PHANTOM_POINT_COUNT; | ||
let point_base = info.points; | ||
for component in composite.component_glyphs() { | ||
for (component, flags) in composite.component_glyphs_and_flags() { | ||
info.has_overlaps |= flags.contains(CompositeGlyphFlags::OVERLAP_COMPOUND); | ||
let component_glyph = self.loca.get_glyf(component, &self.glyf)?; | ||
let Some(component_glyph) = component_glyph else { | ||
continue; | ||
|
@@ -733,3 +735,22 @@ impl<'a, H> ScalerInstance<'a, H> { | |
self.phantom[3].y = self.phantom[2].y - F26Dot6::from_bits(vadvance); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use read_fonts::{FontRef, TableProvider}; | ||
|
||
#[test] | ||
fn overlap_flags() { | ||
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap(); | ||
let scaler = Scaler::new(&font).unwrap(); | ||
let glyph_count = font.maxp().unwrap().num_glyphs(); | ||
for gid in 0..glyph_count { | ||
let glyph = scaler.glyph(GlyphId::new(gid), false).unwrap(); | ||
// GID 2 is a composite glyph with the overlap bit on a component | ||
// GID 3 is a simple glyph with the overlap bit on the first flag | ||
assert_eq!(glyph.has_overlaps, gid == 2 || gid == 3); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, I'd prefer |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like the verbose version of
assert_eq!(gid == 3, glyph.has_overlapping_contours())
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. Actually even then it's a bit more vulnerable to broken test data than I like; I propose we directly assert that only gid 3 has the bit:
assert_eq!(vec![3], (0..glyph_count).filter(...retain only if has overlaps).collect())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this style as well. Fixed!