Skip to content
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

Merged
merged 2 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified font-test-data/test_data/ttf/vazirmatn_var_trimmed.ttf
Binary file not shown.
4 changes: 2 additions & 2 deletions font-test-data/test_data/ttx/vazirmatn_var_trimmed.ttx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ and is intended to contain examples of many variable font tables -->

<TTGlyph name="Agrave" xMin="29" yMin="0" xMax="1310" yMax="1847">
<component glyphName="A" x="0" y="0" flags="0x204"/>
<component glyphName="grave" x="303" y="311" flags="0x4"/>
<component glyphName="grave" x="303" y="311" flags="0x404"/>
<instructions>
<assembly>
PUSHB[ ] /* 2 values pushed */
Expand All @@ -142,7 +142,7 @@ and is intended to contain examples of many variable font tables -->

<TTGlyph name="grave" xMin="57" yMin="1242" xMax="474" yMax="1536">
<contour>
<pt x="281" y="1536" on="1"/>
<pt x="281" y="1536" on="1" overlap="1"/>
<pt x="474" y="1242" on="1"/>
<pt x="315" y="1242" on="1"/>
<pt x="57" y="1536" on="1"/>
Expand Down
79 changes: 69 additions & 10 deletions read-fonts/src/tables/glyf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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 {
Expand All @@ -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))
}
}

Expand Down Expand Up @@ -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())
}
Copy link
Collaborator

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()) ?

Copy link
Collaborator

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())

Copy link
Member Author

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!

}
}

#[test]
fn composite_overlapping_contour_flag() {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above I might prefer it as something like assert_eq!(vec![(2, 1)], ...collect (gid, component index) of things with overlap compound)

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 };
Expand Down
2 changes: 2 additions & 0 deletions skrifa/src/scale/glyf/glyph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub struct ScalerGlyph<'a> {
pub has_hinting: bool,
/// True if the glyph requires variation delta processing.
pub has_variations: bool,
/// True if the glyph contains any simple or compound overlap flags.
pub has_overlaps: bool,
}

impl<'a> ScalerGlyph<'a> {
Expand Down
2 changes: 2 additions & 0 deletions skrifa/src/scale/glyf/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ mod tests {
max_component_delta_stack: 4,
has_hinting: false,
has_variations: true,
has_overlaps: false,
};
let required_size = outline_info.required_buffer_size();
let mut buf = vec![0u8; required_size];
Expand Down Expand Up @@ -185,6 +186,7 @@ mod tests {
max_component_delta_stack: 4,
has_hinting: false,
has_variations: true,
has_overlaps: false,
};
// Required size adds 4 bytes slop to account for internal alignment
// requirements. So subtract 5 to force a failure.
Expand Down
23 changes: 22 additions & 1 deletion skrifa/src/scale/glyf/scaler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, I'd prefer assert_eq!(vec[2, 3], ...collect gids with overlap...)

}
}