Repository: googlefonts/fontc (rust)

Issue:
static fonts from single-master sources have overlaps, skewing glyf/gvar ttx diff
Following #971, ttx_diff.py now invokes fontmake `-o ttf` instead of `-o variable` when the input source contains only one master, in order to avoid fontmake adding unnecessary empty/noop variable tables (https://github.com/googlefonts/fontmake/issues/1125).

But fontmake `-o ttf` also passes the source through the ufo2ft's [remove overlaps filter](https://github.com/googlefonts/ufo2ft/blob/main/Lib/ufo2ft/filters/removeOverlaps.py) (using booleanOperations by default, or skia-pathops if requested via `--overlaps-backend pathops`).

fontc being variable-first font compile does not attempt nor support removing the overlaps (as it would destroy master compatibility for a VF target).

Therefore comparing fontc-built single-master sources vs fontmake-built ones will usually produce several glyph diffs in glyph-related tables like glyf, gvar, maxp, etc. because the number of outline points when glyphs contain overlapping contours will be different between the two fonts.

For example, take this test font, containing only one glyph "T" with overlapping contours, and compare it with ttx_diff.py:

[SingleMaster.glyphs.zip](https://github.com/user-attachments/files/17203384/SingleMaster.glyphs.zip)

```diff
--- build/default/fontc.ttf
+++ build/default/fontmake.ttf
@@ -12,12 +12,12 @@
     <!-- Most of this table will be recalculated by the compiler -->
     <tableVersion value="1.0"/>
     <fontRevision value="1.0"/>
-    <checkSumAdjustment value="0x0"/>
+    <checkSumAdjustment value="0xd88e9083"/>
     <magicNumber value="0x5f0f3cf5"/>
     <flags value="00000000 00000011"/>
     <unitsPerEm value="1000"/>
     <created value="Mon Sep 30 16:38:24 2024"/>
-    <modified value="Tue Oct  1 10:17:24 2024"/>
+    <modified value="Tue Oct  1 10:17:25 2024"/>
     <xMin value="26"/>
     <yMin value="-200"/>
     <xMax value="574"/>
@@ -53,7 +53,7 @@
     <!-- Most of this table will be recalculated by the compiler -->
     <tableVersion value="0x10000"/>
     <numGlyphs value="3"/>
-    <maxPoints value="10"/>
+    <maxPoints value="8"/>
     <maxContours value="2"/>
     <maxCompositePoints value="0"/>
     <maxCompositeContours value="0"/>
@@ -169,12 +169,10 @@
         <pt x="26" y="723" on="1"/>
         <pt x="574" y="723" on="1"/>
         <pt x="574" y="578" on="1"/>
-        <pt x="363" y="578" on="1"/>
-        <pt x="408" y="623" on="1"/>
+        <pt x="408" y="578" on="1"/>
         <pt x="408" y="0" on="1"/>
         <pt x="209" y="0" on="1"/>
-        <pt x="209" y="623" on="1"/>
-        <pt x="254" y="578" on="1"/>
+        <pt x="209" y="578" on="1"/>
         <pt x="26" y="578" on="1"/>
       </contour>
       <instructions/>
```

Pull request:
ttx_diff: pass --keep-overlaps for static fontmake builds for now
Works around #975 (until we also have gain the ability to remove overlaps)

Patch:
diff --git a/fontbe/src/os2.rs b/fontbe/src/os2.rs
index ec584843..c0c1cd2e 100644
--- a/fontbe/src/os2.rs
+++ b/fontbe/src/os2.rs
@@ -587,13 +587,12 @@ mod tests {
     #[test]
     fn build_basic_os2() {
         let default_location = NormalizedLocation::new();
-        let mut global_metrics = GlobalMetrics::new();
+        let mut global_metrics =
+            GlobalMetrics::new(default_location.clone(), 1000, None, None, None, 0.0);
 
         global_metrics.set(GlobalMetric::CapHeight, default_location.clone(), 37.5);
         global_metrics.set(GlobalMetric::XHeight, default_location.clone(), 112.2);
 
-        global_metrics.populate_defaults(&default_location, 1000, None, None, None, Some(0.0));
-
         let mut os2 = Os2 {
             ach_vend_id: Tag::new(b"DUCK"),
             x_avg_char_width: 42,
diff --git a/fontc/Cargo.toml b/fontc/Cargo.toml
index 8a4ae395..1e208da2 100644
--- a/fontc/Cargo.toml
+++ b/fontc/Cargo.toml
@@ -49,7 +49,6 @@ pretty_assertions.workspace = true
 skrifa.workspace = true
 kurbo.workspace = true
 chrono.workspace = true
-ordered-float.workspace = true
 
 [build-dependencies]
 vergen = { version = "8.2.6", features = ["build", "cargo", "git", "gitcl", "rustc"] }
diff --git a/fontc/src/lib.rs b/fontc/src/lib.rs
index 7ba09956..35d011f5 100644
--- a/fontc/src/lib.rs
+++ b/fontc/src/lib.rs
@@ -555,8 +555,6 @@ mod tests {
         time::Instant,
     };
 
-    use ordered_float::OrderedFloat;
-
     use chrono::{Duration, TimeZone, Utc};
     use fontbe::orchestration::{
         AnyWorkId, Context as BeContext, Glyph, LocaFormatWrapper, WorkId as BeWorkIdentifier,
@@ -567,7 +565,7 @@ mod tests {
         types::{GlyphName, WidthClass},
     };
     use fontir::{
-        ir::{self, GlobalMetric, GlyphOrder, KernGroup, KernPair, KernSide},
+        ir::{self, GlyphOrder, KernGroup, KernPair, KernSide},
         orchestration::{Context as FeContext, Persistable, WorkId as FeWorkIdentifier},
     };
     use indexmap::IndexSet;
@@ -2824,7 +2822,7 @@ mod tests {
 
     #[test]
     fn default_fs_type_designspace() {
-        assert_fs_type("wght_var.designspace", 1 << 2);
+        assert_fs_type("designspace_from_glyphs/WghtVar.designspace", 1 << 2);
     }
 
     #[test]
@@ -2918,6 +2916,7 @@ mod tests {
         expected_value_records: Vec<(Tag, u16)>,
         expected_delta_sets: Vec<Vec<i32>>,
     ) {
+        assert_eq!(mvar.value_records().len(), expected_value_records.len());
         let actual_records = mvar
             .value_records()
             .iter()
@@ -3042,31 +3041,6 @@ mod tests {
         assert_mvar_from_glyphs(compile.font().mvar().unwrap());
     }
 
-    #[test]
-    fn strikeout_size_fallback() {
-        let compile =
-            TestCompile::compile_source("glyphs3/GlobalMetrics_font_customParameters.glyphs");
-        let strikeout_sizes = compile
-            .fe_context
-            .global_metrics
-            .get()
-            .iter()
-            .filter_map(|(metric, values)| {
-                if *metric == GlobalMetric::StrikeoutSize {
-                    Some(values)
-                } else {
-                    None
-                }
-            })
-            .flat_map(|v| v.values().copied())
-            .collect::<HashSet<_>>();
-        // Light has an explicit 40, everything else uses 42
-        assert_eq!(
-            HashSet::from([OrderedFloat(40.0), OrderedFloat(42.0)]),
-            strikeout_sizes
-        );
-    }
-
     #[test]
     fn compile_ufo() {
         let result = TestCompile::compile_source("WghtVar-Bold.ufo");
diff --git a/fontir/src/ir.rs b/fontir/src/ir.rs
index b2c98eeb..741afd9b 100644
--- a/fontir/src/ir.rs
+++ b/fontir/src/ir.rs
@@ -408,7 +408,7 @@ impl StaticMetadata {
 ///
 /// Represents the values of these metrics at a specific position in design space.
 /// At a minimum should be defined at the default location.
-#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
 pub struct GlobalMetrics(
     pub(crate) HashMap<GlobalMetric, HashMap<NormalizedLocation, OrderedFloat<f32>>>,
 );
@@ -507,9 +507,25 @@ fn adjust_offset(offset: f64, angle: f64) -> f64 {
 pub type GlobalMetricValues = HashMap<NormalizedLocation, OrderedFloat<f32>>;
 
 impl GlobalMetrics {
-    /// Creates a new, empty, [GlobalMetrics]
-    pub fn new() -> Self {
-        Default::default()
+    /// Creates a new [GlobalMetrics] with default values at the default location.
+    pub fn new(
+        default_location: NormalizedLocation,
+        units_per_em: u16,
+        x_height: Option<f64>,
+        ascender: Option<f64>,
+        descender: Option<f64>,
+        italic_angle: f64,
+    ) -> GlobalMetrics {
+        let mut metrics = GlobalMetrics(HashMap::new());
+        metrics.populate_defaults(
+            &default_location,
+            units_per_em,
+            x_height,
+            ascender,
+            descender,
+            Some(italic_angle),
+        );
+        metrics
     }
 
     /// Populate default values for all metrics at the given location.
@@ -521,7 +537,7 @@ impl GlobalMetrics {
     /// variable fonts from Glyphs or Designspace sources.
     pub fn populate_defaults(
         &mut self,
-        pos: &NormalizedLocation,
+        location: &NormalizedLocation,
         units_per_em: u16,
         x_height: Option<f64>,
         ascender: Option<f64>,
@@ -530,14 +546,13 @@ impl GlobalMetrics {
     ) {
         let units_per_em = units_per_em as f64;
 
-        let mut set_if_absent =
-            |metric, value| self.set_if_absent(metric, pos.clone(), value as f32);
+        let mut set = |metric, value| self.set(metric, location.clone(), value as f32);
 
         // https://github.com/googlefonts/ufo2ft/blob/fca66fe3ea1ea88ffb36f8264b21ce042d3afd05/Lib/ufo2ft/fontInfoData.py#L38-L45
         let ascender = ascender.unwrap_or(0.8 * units_per_em);
         let descender = descender.unwrap_or(-0.2 * units_per_em);
-        set_if_absent(GlobalMetric::Ascender, ascender);
-        set_if_absent(GlobalMetric::Descender, descender);
+        set(GlobalMetric::Ascender, ascender);
+        set(GlobalMetric::Descender, descender);
 
         // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L229-L238
         let typo_line_gap = units_per_em * 1.2 + descender - ascender;
@@ -546,78 +561,71 @@ impl GlobalMetrics {
         } else {
             0.0
         };
-        set_if_absent(GlobalMetric::Os2TypoLineGap, typo_line_gap);
+        set(GlobalMetric::Os2TypoLineGap, typo_line_gap);
 
         // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L215-L226
-        set_if_absent(GlobalMetric::Os2TypoAscender, ascender);
-        set_if_absent(GlobalMetric::Os2TypoDescender, descender);
+        set(GlobalMetric::Os2TypoAscender, ascender);
+        set(GlobalMetric::Os2TypoDescender, descender);
 
         // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L126-L130
-        set_if_absent(GlobalMetric::HheaAscender, ascender + typo_line_gap);
-        set_if_absent(GlobalMetric::HheaDescender, descender);
+        set(GlobalMetric::HheaAscender, ascender + typo_line_gap);
+        set(GlobalMetric::HheaDescender, descender);
         // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L366
-        set_if_absent(GlobalMetric::HheaLineGap, 0.0);
+        set(GlobalMetric::HheaLineGap, 0.0);
 
         // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L241-L254
-        set_if_absent(GlobalMetric::Os2WinAscent, ascender + typo_line_gap);
-        set_if_absent(GlobalMetric::Os2WinDescent, descender.abs());
+        set(GlobalMetric::Os2WinAscent, ascender + typo_line_gap);
+        set(GlobalMetric::Os2WinDescent, descender.abs());
 
         // https://github.com/googlefonts/ufo2ft/blob/fca66fe3ea1ea88ffb36f8264b21ce042d3afd05/Lib/ufo2ft/fontInfoData.py#L48-L55
-        set_if_absent(GlobalMetric::CapHeight, 0.7 * units_per_em);
-        set_if_absent(
-            GlobalMetric::XHeight,
-            x_height.unwrap_or(0.5 * units_per_em),
-        );
+        set(GlobalMetric::CapHeight, 0.7 * units_per_em);
+        let x_height = x_height.unwrap_or(0.5 * units_per_em);
+        set(GlobalMetric::XHeight, x_height);
 
         // https://github.com/googlefonts/ufo2ft/blob/150c2d6a00da9d5854173c8457a553ce03b89cf7/Lib/ufo2ft/fontInfoData.py#L133-L148
         // https://github.com/googlefonts/ufo2ft/blob/150c2d6a00da9d5854173c8457a553ce03b89cf7/Lib/ufo2ft/fontInfoData.py#L151-L161
         let italic_angle = italic_angle.unwrap_or(0.0);
-        set_if_absent(GlobalMetric::CaretSlopeRise, units_per_em);
-        set_if_absent(
+        set(GlobalMetric::CaretSlopeRise, units_per_em);
+        set(
             GlobalMetric::CaretSlopeRun,
             adjust_offset(units_per_em, italic_angle),
         );
 
         // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L367
-        set_if_absent(GlobalMetric::CaretOffset, 0.0);
+        set(GlobalMetric::CaretOffset, 0.0);
 
         // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/outlineCompiler.py#L575-L616
         let subscript_x_size = units_per_em * 0.65;
         let subscript_y_size = units_per_em * 0.60;
         let subscript_y_offset = units_per_em * 0.075;
         let superscript_y_offset = units_per_em * 0.35;
-
-        set_if_absent(GlobalMetric::SubscriptXSize, subscript_x_size);
-        set_if_absent(GlobalMetric::SubscriptYSize, subscript_y_size);
-        set_if_absent(
+        set(GlobalMetric::SubscriptXSize, subscript_x_size);
+        set(GlobalMetric::SubscriptYSize, subscript_y_size);
+        set(
             GlobalMetric::SubscriptXOffset,
             adjust_offset(-subscript_y_offset, italic_angle),
         );
-        set_if_absent(GlobalMetric::SubscriptYOffset, subscript_y_offset);
+        set(GlobalMetric::SubscriptYOffset, subscript_y_offset);
 
-        set_if_absent(GlobalMetric::SuperscriptXSize, subscript_x_size);
-        set_if_absent(GlobalMetric::SuperscriptYSize, subscript_y_size);
-        set_if_absent(
+        set(GlobalMetric::SuperscriptXSize, subscript_x_size);
+        set(GlobalMetric::SuperscriptYSize, subscript_y_size);
+        set(
             GlobalMetric::SuperscriptXOffset,
             adjust_offset(superscript_y_offset, italic_angle),
         );
-        set_if_absent(GlobalMetric::SuperscriptYOffset, superscript_y_offset);
+        set(GlobalMetric::SuperscriptYOffset, superscript_y_offset);
+
+        // // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L313-L316
+        set(GlobalMetric::StrikeoutSize, 0.05 * units_per_em);
+        set(GlobalMetric::StrikeoutPosition, x_height * 0.6);
 
         // ufo2ft and Glyphs.app have different defaults for the post.underlinePosition:
         // the former uses 0.075*UPEM whereas the latter 0.1*UPEM (both use the same
         // underlineThickness 0.05*UPEM). We prefer to match Glyphs.app as more widely used.
         // https://github.com/googlefonts/ufo2ft/blob/main/Lib/ufo2ft/fontInfoData.py#L313-L322
         // https://github.com/googlefonts/glyphsLib/blob/main/Lib/glyphsLib/builder/custom_params.py#L1116-L1125
-        let underline_thickness =
-            set_if_absent(GlobalMetric::UnderlineThickness, 0.05 * units_per_em);
-        set_if_absent(GlobalMetric::UnderlinePosition, -0.1 * units_per_em);
-
-        // // https://github.com/googlefonts/ufo2ft/blob/0d2688cd847d003b41104534d16973f72ef26c40/Lib/ufo2ft/fontInfoData.py#L313-L316
-        set_if_absent(GlobalMetric::StrikeoutSize, underline_thickness.0 as f64);
-        set_if_absent(
-            GlobalMetric::StrikeoutPosition,
-            x_height.map(|v| v * 0.6).unwrap_or(units_per_em * 0.22),
-        );
+        set(GlobalMetric::UnderlineThickness, 0.05 * units_per_em);
+        set(GlobalMetric::UnderlinePosition, -0.1 * units_per_em);
     }
 
     fn values(&self, metric: GlobalMetric) -> &GlobalMetricValues {
@@ -645,17 +653,8 @@ impl GlobalMetrics {
         self.values_mut(metric).insert(pos, value.into());
     }
 
-    pub fn set_if_absent(
-        &mut self,
-        metric: GlobalMetric,
-        pos: NormalizedLocation,
-        value: impl Into<OrderedFloat<f32>>,
-    ) -> OrderedFloat<f32> {
-        *self.values_mut(metric).entry(pos).or_insert(value.into())
-    }
-
     pub fn set_if_some(
-        &mut self,
+        self: &mut GlobalMetrics,
         metric: GlobalMetric,
         pos: NormalizedLocation,
         maybe_value: Option<impl Into<OrderedFloat<f64>>>,
diff --git a/glyphs-reader/src/font.rs b/glyphs-reader/src/font.rs
index 51c7f0c7..a8e58c0b 100644
--- a/glyphs-reader/src/font.rs
+++ b/glyphs-reader/src/font.rs
@@ -3246,7 +3246,7 @@ mod tests {
         assert_eq!(Some(0), font.hhea_line_gap);
         assert_eq!(Some(1185), font.win_ascent);
         assert_eq!(Some(420), font.win_descent);
-        assert_eq!(Some(OrderedFloat(42_f64)), font.underline_thickness);
+        assert_eq!(Some(OrderedFloat(50_f64)), font.underline_thickness);
         assert_eq!(Some(OrderedFloat(-300_f64)), font.underline_position);
     }
 }
diff --git a/glyphs2fontir/src/source.rs b/glyphs2fontir/src/source.rs
index cb5d35a0..21a5fc95 100644
--- a/glyphs2fontir/src/source.rs
+++ b/glyphs2fontir/src/source.rs
@@ -559,10 +559,29 @@ impl Work<Context, WorkId, Error> for GlobalMetricWork {
         );
 
         let static_metadata = context.static_metadata.get();
-        let mut metrics = GlobalMetrics::new();
+        let default_master = font.default_master();
+        let mut metrics = GlobalMetrics::new(
+            static_metadata.default_location().clone(),
+            static_metadata.units_per_em,
+            default_master.x_height(),
+            default_master.ascender(),
+            default_master.descender(),
+            static_metadata.italic_angle.into_inner(),
+        );
 
         for master in font.masters.iter() {
             let pos = font_info.locations.get(&master.axes_values).unwrap();
+            if !pos.is_default() {
+                metrics.populate_defaults(
+                    pos,
+                    static_metadata.units_per_em,
+                    master.x_height(),
+                    master.ascender(),
+                    master.descender(),
+                    // turn clockwise angle counter-clockwise
+                    master.italic_angle().map(|v| -v),
+                );
+            }
 
             metrics.set_if_some(GlobalMetric::CapHeight, pos.clone(), master.cap_height());
             metrics.set_if_some(GlobalMetric::XHeight, pos.clone(), master.x_height());
@@ -713,17 +732,7 @@ impl Work<Context, WorkId, Error> for GlobalMetricWork {
                 GlobalMetric::UnderlinePosition,
                 pos.clone(),
                 master.underline_position.or(font.underline_position),
-            );
-
-            metrics.populate_defaults(
-                pos,
-                static_metadata.units_per_em,
-                master.x_height(),
-                master.ascender(),
-                master.descender(),
-                // turn clockwise angle counter-clockwise
-                master.italic_angle().map(|v| -v),
-            );
+            )
         }
 
         context.global_metrics.set(metrics);
@@ -1801,37 +1810,66 @@ mod tests {
             .get()
             .at(static_metadata.default_location());
         assert_eq!(
-            (
-                NormalizedLocation::for_pos(&[("wght", 0.0)]),
-                GlobalMetricsInstance {
-                    pos: static_metadata.default_location().clone(),
-                    ascender: 800.0.into(),
-                    descender: (-200.0).into(),
-                    caret_slope_rise: 1000.0.into(),
-                    cap_height: 660.0.into(),
-                    x_height: 478.0.into(),
-                    subscript_x_size: 650.0.into(),
-                    subscript_y_size: 600.0.into(),
-                    subscript_y_offset: 75.0.into(),
-                    superscript_x_size: 650.0.into(),
-                    superscript_y_size: 600.0.into(),
-                    superscript_y_offset: 350.0.into(),
-                    strikeout_position: 286.8.into(),
-                    strikeout_size: 40.0.into(), // fallback to underline thickness
-                    os2_typo_ascender: 950.0.into(),
-                    os2_typo_descender: (-350.0).into(),
-                    os2_typo_line_gap: 0.0.into(),
-                    os2_win_ascent: 1185.0.into(),
-                    os2_win_descent: 420.0.into(),
-                    hhea_ascender: 950.0.into(),
-                    hhea_descender: (-350.0).into(),
-                    hhea_line_gap: 0.0.into(),
-                    underline_thickness: 40.0.into(), // overridden from global value
-                    underline_position: (-300.0).into(),
-                    ..Default::default()
-                }
-            ),
-            (static_metadata.default_location().clone(), default_metrics)
+            GlobalMetricsInstance {
+                pos: static_metadata.default_location().clone(),
+                ascender: 800.0.into(),
+                descender: (-200.0).into(),
+                caret_slope_rise: 1000.0.into(),
+                cap_height: 660.0.into(),
+                x_height: 478.0.into(),
+                subscript_x_size: 650.0.into(),
+                subscript_y_size: 600.0.into(),
+                subscript_y_offset: 75.0.into(),
+                superscript_x_size: 650.0.into(),
+                superscript_y_size: 600.0.into(),
+                superscript_y_offset: 350.0.into(),
+                strikeout_position: 286.8.into(),
+                strikeout_size: 50.0.into(),
+                os2_typo_ascender: 950.0.into(),
+                os2_typo_descender: (-350.0).into(),
+                os2_typo_line_gap: 0.0.into(),
+                os2_win_ascent: 1185.0.into(),
+                os2_win_descent: 420.0.into(),
+                hhea_ascender: 950.0.into(),
+                hhea_descender: (-350.0).into(),
+                hhea_line_gap: 0.0.into(),
+                underline_thickness: 50.0.into(),
+                underline_position: (-300.0).into(),
+                ..Default::default()
+            },
+            default_metrics
+        );
+        let light = NormalizedLocation::for_pos(&[("wght", 0.0)]);
+        let light_metrics = context.global_metrics.get().at(&light);
+        assert_eq!(
+            GlobalMetricsInstance {
+                pos: light.clone(),
+                ascender: 800.0.into(),
+                descender: (-200.0).into(),
+                caret_slope_rise: 1000.0.into(),
+                cap_height: 660.0.into(),
+                x_height: 478.0.into(),
+                subscript_x_size: 650.0.into(),
+                subscript_y_size: 600.0.into(),
+                subscript_y_offset: 75.0.into(),
+                superscript_x_size: 650.0.into(),
+                superscript_y_size: 600.0.into(),
+                superscript_y_offset: 350.0.into(),
+                strikeout_position: 286.8.into(),
+                strikeout_size: 50.0.into(),
+                os2_typo_ascender: 950.0.into(),
+                os2_typo_descender: (-350.0).into(),
+                os2_typo_line_gap: 0.0.into(),
+                os2_win_ascent: 1185.0.into(),
+                os2_win_descent: 420.0.into(),
+                hhea_ascender: 950.0.into(),
+                hhea_descender: (-350.0).into(),
+                hhea_line_gap: 0.0.into(),
+                underline_thickness: 50.0.into(),
+                underline_position: (-300.0).into(),
+                ..Default::default()
+            },
+            light_metrics
         );
         let black = NormalizedLocation::for_pos(&[("wght", 1.0)]);
         let black_metrics = context.global_metrics.get().at(&black);
@@ -1850,7 +1888,7 @@ mod tests {
                 superscript_y_size: 600.0.into(),
                 superscript_y_offset: 350.0.into(),
                 strikeout_position: 300.0.into(),
-                strikeout_size: 42.0.into(), // fallback to underline thickness
+                strikeout_size: 50.0.into(),
                 os2_typo_ascender: 1000.0.into(),
                 os2_typo_descender: (-400.0).into(),
                 os2_typo_line_gap: 0.0.into(),
@@ -1859,7 +1897,7 @@ mod tests {
                 hhea_ascender: 1000.0.into(),
                 hhea_descender: (-400.0).into(),
                 hhea_line_gap: 0.0.into(),
-                underline_thickness: 42.0.into(), // global value
+                underline_thickness: 50.0.into(),
                 underline_position: (-300.0).into(),
                 ..Default::default()
             },
diff --git a/resources/scripts/ttx_diff.py b/resources/scripts/ttx_diff.py
index e45a0700..c35e489b 100755
--- a/resources/scripts/ttx_diff.py
+++ b/resources/scripts/ttx_diff.py
@@ -200,8 +200,9 @@ def build_fontc(source: Path, fontc_cargo_path: Path, build_dir: Path, compare:
 
 
 def build_fontmake(source: Path, build_dir: Path, compare: str):
+    variable = source_is_variable(source)
     buildtype = "variable"
-    if not source_is_variable(source):
+    if not variable:
         buildtype = "ttf"
     cmd = [
         "fontmake",
@@ -217,6 +218,12 @@ def build_fontmake(source: Path, build_dir: Path, compare: str):
         "--debug-feature-file",
         "debug.fea",
     ]
+    if not variable:
+        # fontmake static builds perform overlaps removal, but fontc can't do that yet.
+        # Disable the filter to make the diff less noisy.
+        # TODO(anthrotype): Remove if/when fontc gains the ability to remove overlaps.
+        # https://github.com/googlefonts/fontc/issues/975
+        cmd.append("--keep-overlaps")
     if compare == _COMPARE_GFTOOLS:
         cmd += [
             "--filter",
diff --git a/ufo2fontir/src/source.rs b/ufo2fontir/src/source.rs
index c417c8dd..de5011f5 100644
--- a/ufo2fontir/src/source.rs
+++ b/ufo2fontir/src/source.rs
@@ -956,13 +956,7 @@ impl Work<Context, WorkId, Error> for StaticMetadataWork {
         }
 
         // <https://github.com/googlefonts/glyphsLib/blob/cb8a4a914b0a33431f0a77f474bf57eec2f19bcc/Lib/glyphsLib/builder/custom_params.py#L1117-L1119>
-        static_metadata.misc.fs_type = Some(
-            font_info_at_default
-                .open_type_os2_type
-                .as_ref()
-                .map(|flags| flags.iter().fold(0_u16, |acc, e| acc | 1 << *e))
-                .unwrap_or(1_u16 << 2),
-        );
+        static_metadata.misc.fs_type = Some(1 << 2);
 
         static_metadata.misc.version_major = font_info_at_default
             .version_major
@@ -1003,7 +997,7 @@ fn set_default_underline_pos(
     // ufo2ft default underline position differs from fontir/Glyphs.app's so
     // we override it here.
     // https://github.com/googlefonts/ufo2ft/blob/a421acc/Lib/ufo2ft/fontInfoData.py#L319-L322
-    metrics.set_if_absent(
+    metrics.set(
         GlobalMetric::UnderlinePosition,
         location.clone(),
         -0.075 * units_per_em as f32,
@@ -1019,7 +1013,6 @@ fn populate_default_metrics(
     descender: Option<f64>,
     italic_angle: Option<f64>,
 ) {
-    set_default_underline_pos(metrics, location, units_per_em);
     metrics.populate_defaults(
         location,
         units_per_em,
@@ -1028,6 +1021,7 @@ fn populate_default_metrics(
         descender,
         italic_angle,
     );
+    set_default_underline_pos(metrics, location, units_per_em);
 }
 
 impl Work<Context, WorkId, Error> for GlobalMetricsWork {
@@ -1047,9 +1041,31 @@ impl Work<Context, WorkId, Error> for GlobalMetricsWork {
         let font_infos = font_infos(designspace_dir, &self.designspace)?;
         let master_locations =
             master_locations(&static_metadata.all_source_axes, &self.designspace.sources);
+        let Some((_, default_master)) = default_master(&self.designspace) else {
+            return Err(Error::NoDefaultMaster(self.designspace_file.clone()));
+        };
 
-        let mut metrics = GlobalMetrics::new();
+        let default_fontinfo = font_infos.get(&default_master.filename).ok_or_else(|| {
+            BadSource::new(
+                designspace_dir.join(&default_master.filename),
+                BadSourceKind::ExpectedFile,
+            )
+        })?;
 
+        let mut metrics = GlobalMetrics::new(
+            static_metadata.default_location().clone(),
+            static_metadata.units_per_em,
+            default_fontinfo.x_height,
+            default_fontinfo.ascender,
+            default_fontinfo.descender,
+            static_metadata.italic_angle.into_inner(),
+        );
+        // use ufo2ft's default underline position, different from fontir/Glyphs.app
+        set_default_underline_pos(
+            &mut metrics,
+            static_metadata.default_location(),
+            static_metadata.units_per_em,
+        );
         for source in self
             .designspace
             .sources
@@ -1064,6 +1080,18 @@ impl Work<Context, WorkId, Error> for GlobalMetricsWork {
                 )
             })?;
 
+            if !pos.is_default() {
+                populate_default_metrics(
+                    &mut metrics,
+                    pos,
+                    static_metadata.units_per_em,
+                    font_info.x_height,
+                    font_info.ascender,
+                    font_info.descender,
+                    font_info.italic_angle,
+                );
+            }
+
             metrics.set_if_some(GlobalMetric::CapHeight, pos.clone(), font_info.cap_height);
             metrics.set_if_some(GlobalMetric::XHeight, pos.clone(), font_info.x_height);
             metrics.set_if_some(
@@ -1185,16 +1213,6 @@ impl Work<Context, WorkId, Error> for GlobalMetricsWork {
                 pos.clone(),
                 font_info.postscript_underline_position,
             );
-
-            populate_default_metrics(
-                &mut metrics,
-                pos,
-                static_metadata.units_per_em,
-                font_info.x_height,
-                font_info.ascender,
-                font_info.descender,
-                font_info.italic_angle,
-            );
         }
 
         trace!("{:#?}", metrics);
@@ -2303,20 +2321,4 @@ mod tests {
             ]
         );
     }
-
-    fn assert_fs_type(src: &str, expected: u16) {
-        let (_, context) = build_static_metadata(src, default_test_flags());
-        let static_metadata = context.static_metadata.get();
-        assert_eq!(Some(expected), static_metadata.misc.fs_type);
-    }
-
-    #[test]
-    fn default_fs_type() {
-        assert_fs_type("wght_var.designspace", 1 << 2);
-    }
-
-    #[test]
-    fn obeys_explicit_fs_type() {
-        assert_fs_type("MVAR.designspace", 1 << 3);
-    }
 }


Test patch:
diff --git a/resources/testdata/glyphs3/GlobalMetrics_font_customParameters.glyphs b/resources/testdata/glyphs3/GlobalMetrics_font_customParameters.glyphs
index 5934b16d..1caa8448 100644
--- a/resources/testdata/glyphs3/GlobalMetrics_font_customParameters.glyphs
+++ b/resources/testdata/glyphs3/GlobalMetrics_font_customParameters.glyphs
@@ -55,7 +55,7 @@ value = 420;
 },
 {
 name = underlineThickness;
-value = 42;
+value = 50;
 },
 {
 name = underlinePosition;
@@ -90,12 +90,6 @@ pos = -200;
 }
 );
 name = ExtraLight;
-customParameters = (
-{
-name = underlineThickness;
-value = 40;
-},
-);
 },
 {
 axesValues = (