Skip to content

Commit

Permalink
Merge pull request #423 from googlefonts/notdef0
Browse files Browse the repository at this point in the history
Generate missing .notdef like ufo2ft
  • Loading branch information
rsheeter authored Aug 29, 2023
2 parents d869388 + 9c3e62d commit 23ce03f
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 33 deletions.
92 changes: 64 additions & 28 deletions fontc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,12 +520,14 @@ mod tests {
BeWorkIdentifier::Font.into(),
BeWorkIdentifier::Fvar.into(),
BeWorkIdentifier::Glyf.into(),
BeWorkIdentifier::GlyfFragment(".notdef".into()).into(),
BeWorkIdentifier::GlyfFragment("bar".into()).into(),
BeWorkIdentifier::GlyfFragment("plus".into()).into(),
BeWorkIdentifier::Gpos.into(),
BeWorkIdentifier::Gsub.into(),
BeWorkIdentifier::Gdef.into(),
BeWorkIdentifier::Gvar.into(),
BeWorkIdentifier::GvarFragment(".notdef".into()).into(),
BeWorkIdentifier::GvarFragment("bar".into()).into(),
BeWorkIdentifier::GvarFragment("plus".into()).into(),
BeWorkIdentifier::Head.into(),
Expand Down Expand Up @@ -829,12 +831,13 @@ mod tests {
let result = compile(Args::for_test(build_dir, "static.designspace"));

// See resources/testdata/Static-Regular.ufo/glyphs
// generated .notdef, 8 points, 2 contours
// space, 0 points, 0 contour
// bar, 4 points, 1 contour
// plus, 12 points, 1 contour
// element of, 0 points, 0 contours
assert_eq!(
vec![(0, 0), (4, 1), (12, 1), (0, 0)],
vec![(8, 2), (0, 0), (4, 1), (12, 1), (0, 0)],
result
.glyphs()
.read()
Expand Down Expand Up @@ -891,26 +894,26 @@ mod tests {
// Per source, glyphs should be period, comma, non_uniform_scale
// Period is simple, the other two use it as a component
let glyph_data = result.glyphs();
let glyphs = glyph_data.read();
assert!(glyphs.len() > 1, "{glyphs:#?}");
let period_idx = result.get_glyph_index("period");
assert!(
matches!(glyphs[0], Some(glyf::Glyph::Simple(..))),
"{glyphs:#?}"
);
for (idx, glyph) in glyphs.iter().enumerate() {
if idx == period_idx.try_into().unwrap() {
assert!(
matches!(glyphs[idx], Some(glyf::Glyph::Simple(..))),
"glyphs[{idx}] should be simple\n{glyph:#?}\nAll:\n{glyphs:#?}"
);
} else {
assert!(
matches!(glyphs[idx], Some(glyf::Glyph::Composite(..))),
"glyphs[{idx}] should be composite\n{glyph:#?}\nAll:\n{glyphs:#?}"
);
}
}
let all_glyphs = glyph_data.read();

assert!(matches!(
(
all_glyphs[result.get_glyph_index("period") as usize]
.as_ref()
.unwrap(),
all_glyphs[result.get_glyph_index("comma") as usize]
.as_ref()
.unwrap(),
all_glyphs[result.get_glyph_index("non_uniform_scale") as usize]
.as_ref()
.unwrap(),
),
(
glyf::Glyph::Simple(..),
glyf::Glyph::Composite(..),
glyf::Glyph::Composite(..),
),
));
}

#[test]
Expand Down Expand Up @@ -1018,11 +1021,12 @@ mod tests {
.collect();
assert_eq!(
vec![
(0x002C, 1),
(0x002E, 0),
(0x0030, 2),
(0x031, 3),
(0x032, 4)
(0x0000, 0),
(0x002C, 2),
(0x002E, 1),
(0x0030, 3),
(0x031, 4),
(0x032, 5)
],
cp_and_gid,
"start {:?}\nend {:?}id_delta {:?}",
Expand Down Expand Up @@ -1259,6 +1263,37 @@ mod tests {
.collect::<Vec<_>>()
}

#[test]
fn compile_generates_notdef() {
let temp_dir = tempdir().unwrap();
let build_dir = temp_dir.path();
let result = compile(Args::for_test(build_dir, "glyphs2/WghtVar.glyphs"));

assert!(!result
.fe_context
.preliminary_glyph_order
.get()
.contains(&GlyphName::NOTDEF));
assert_eq!(
Some(0),
result
.fe_context
.glyph_order
.get()
.glyph_id(&GlyphName::NOTDEF)
);

let font_file = build_dir.join("font.ttf");
assert!(font_file.exists());
let buf = fs::read(font_file).unwrap();
let font = FontRef::new(&buf).unwrap();

assert_eq!(
GlyphId::new(0),
font.cmap().unwrap().map_codepoint(0u32).unwrap()
);
}

#[test]
fn compile_glyphs_font_with_weight_axis() {
let temp_dir = tempdir().unwrap();
Expand All @@ -1280,9 +1315,10 @@ mod tests {
GlyphId::new(0),
GlyphId::new(1),
GlyphId::new(2),
GlyphId::new(5),
GlyphId::new(3),
GlyphId::new(6),
],
[0x20, 0x21, 0x2d, 0x3d]
[0x00, 0x20, 0x21, 0x2d, 0x3d]
.iter()
.map(|cp| font.cmap().unwrap().map_codepoint(*cp as u32).unwrap())
.collect::<Vec<_>>()
Expand Down
3 changes: 3 additions & 0 deletions fontdrasil/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use smol_str::SmolStr;
pub struct GlyphName(SmolStr);

impl GlyphName {
/// The name of the undefined glyph
pub const NOTDEF: GlyphName = GlyphName(SmolStr::new_inline(".notdef"));

pub fn as_str(&self) -> &str {
self.0.as_str()
}
Expand Down
39 changes: 38 additions & 1 deletion fontir/src/glyph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,37 @@ fn flatten_glyph(context: &Context, glyph: &Glyph) -> Result<(), WorkError> {
Ok(())
}

fn ensure_notdef_exists_and_is_gid_0(
context: &Context,
glyph_order: &mut GlyphOrder,
) -> Result<(), WorkError> {
// Make sure we have a .notdef and that it's gid 0
match glyph_order.glyph_id(&GlyphName::NOTDEF) {
Some(0) => (), // .notdef is gid 0; life is good
Some(..) => {
trace!("Move {} to gid 0", GlyphName::NOTDEF);
glyph_order.set_glyph_id(&GlyphName::NOTDEF, 0);
}
None => {
trace!("Generate {} and make it gid 0", GlyphName::NOTDEF);
glyph_order.set_glyph_id(&GlyphName::NOTDEF, 0);
let static_metadata = context.static_metadata.get();
let metrics = context
.global_metrics
.get()
.at(static_metadata.default_location());
let builder = GlyphBuilder::new_notdef(
static_metadata.default_location().clone(),
static_metadata.units_per_em,
metrics.ascender.0,
metrics.descender.0,
);
context.glyphs.set(builder.try_into()?);
}
}
Ok(())
}

impl Work<Context, WorkId, WorkError> for GlyphOrderWork {
fn id(&self) -> WorkId {
WorkId::GlyphOrder
Expand All @@ -367,7 +398,10 @@ impl Work<Context, WorkId, WorkError> for GlyphOrderWork {
Access::Custom(Arc::new(|id| {
matches!(
id,
WorkId::Glyph(..) | WorkId::StaticMetadata | WorkId::PreliminaryGlyphOrder
WorkId::Glyph(..)
| WorkId::StaticMetadata
| WorkId::PreliminaryGlyphOrder
| WorkId::GlobalMetrics
)
}))
}
Expand Down Expand Up @@ -420,6 +454,8 @@ impl Work<Context, WorkId, WorkError> for GlyphOrderWork {
}
}

ensure_notdef_exists_and_is_gid_0(context, &mut new_glyph_order)?;

// We now have the final static metadata
// If the glyph order changed try not to forget about it
if *current_glyph_order != new_glyph_order {
Expand All @@ -435,6 +471,7 @@ impl Work<Context, WorkId, WorkError> for GlyphOrderWork {
} else {
trace!("No new glyphs, final glyph order == preliminary glyph order");
}

context.glyph_order.set(new_glyph_order);
Ok(())
}
Expand Down
66 changes: 65 additions & 1 deletion fontir/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use std::{
io::Read,
path::PathBuf,
};
use write_fonts::tables::os2::SelectionFlags;
use write_fonts::{tables::os2::SelectionFlags, OtRound};

pub const DEFAULT_VENDOR_ID: &str = "NONE";
const DEFAULT_VENDOR_ID_TAG: Tag = Tag::new(b"NONE");
Expand Down Expand Up @@ -154,6 +154,17 @@ impl GlyphOrder {
pub fn len(&self) -> usize {
self.0.len()
}

pub(crate) fn set_glyph_id(&mut self, name: &GlyphName, new_index: usize) {
match self.0.get_index_of(name) {
Some(index) if index == new_index => (), // nop
Some(index) => self.0.move_index(index, new_index),
None => {
self.insert(name.clone());
self.set_glyph_id(name, new_index)
}
}
}
}

impl IntoIterator for GlyphOrder {
Expand Down Expand Up @@ -975,6 +986,59 @@ impl GlyphBuilder {
self.sources.insert(unique_location.clone(), source);
Ok(())
}

/// * see <https://github.com/googlefonts/ufo2ft/blob/b3895a96ca910c1764df016bfee4719448cfec4a/Lib/ufo2ft/outlineCompiler.py#L1666-L1694>
pub fn new_notdef(
default_location: NormalizedLocation,
upem: u16,
ascender: f32,
descender: f32,
) -> Self {
let upem = upem as f32;
let width: u16 = (upem * 0.5).ot_round();
let width = width as f64;
let stroke: u16 = (upem * 0.05).ot_round();
let stroke = stroke as f64;

let mut path = BezPath::new();

// outer box
let x_min = stroke;
let x_max = width - stroke;
let y_max = ascender as f64;
let y_min = descender as f64;
path.move_to((x_min, y_min));
path.line_to((x_max, y_min));
path.line_to((x_max, y_max));
path.line_to((x_min, y_max));
path.line_to((x_min, y_min));
path.close_path();

// inner, cut out, box
let x_min = x_min + stroke;
let x_max = x_max - stroke;
let y_max = y_max - stroke;
let y_min = y_min + stroke;
path.move_to((x_min, y_min));
path.line_to((x_min, y_max));
path.line_to((x_max, y_max));
path.line_to((x_max, y_min));
path.line_to((x_min, y_min));
path.close_path();

Self {
name: GlyphName::NOTDEF.clone(),
codepoints: HashSet::from([0]),
sources: HashMap::from([(
default_location,
GlyphInstance {
width,
contours: vec![path],
..Default::default()
},
)]),
}
}
}

impl TryInto<Glyph> for GlyphBuilder {
Expand Down
5 changes: 2 additions & 3 deletions resources/testdata/glyphs2/Mono.glyphs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ fontMaster = (
id = m01;
}
);
glyphs = (
{
glyphname = First;
glyphs = ({
glyphname = .notdef;
layers = (
{
layerId = m01;
Expand Down

0 comments on commit 23ce03f

Please sign in to comment.