Repository: googlefonts/fontations
Pull request #996 (2024-07-11 14:02:55)

[codegen] Generate offset_data() wrapper for format enums


Patch:
diff --git a/fauntlet/src/compare_glyphs.rs b/fauntlet/src/compare_glyphs.rs
index b16064c0..cda6550c 100644
--- a/fauntlet/src/compare_glyphs.rs
+++ b/fauntlet/src/compare_glyphs.rs
@@ -22,7 +22,7 @@ pub fn compare_glyphs(
     let mut ok = true;
 
     for gid in 0..glyph_count {
-        let gid = GlyphId::new(gid);
+        let gid = GlyphId::from(gid);
         // Restore this when <https://github.com/googlefonts/fontations/issues/790>
         // is completed.
         //
@@ -81,7 +81,7 @@ pub fn compare_glyphs(
                 options.index,
                 options.ppem,
                 options.coords,
-                gid.to_u16(),
+                gid.to_u32(),
             )
             .unwrap();
             if exit_on_fail {
diff --git a/fauntlet/src/font/freetype.rs b/fauntlet/src/font/freetype.rs
index 8974671e..2fbd2445 100644
--- a/fauntlet/src/font/freetype.rs
+++ b/fauntlet/src/font/freetype.rs
@@ -91,9 +91,7 @@ impl FreeTypeInstance {
             // Without this load flag, FT applies scale to linearHoriAdvance
             load_flags |= LoadFlag::LINEAR_DESIGN;
         }
-        self.face
-            .load_glyph(glyph_id.to_u16() as u32, load_flags)
-            .ok()?;
+        self.face.load_glyph(glyph_id.to_u32(), load_flags).ok()?;
         let advance = self.face.glyph().linear_hori_advance() as f32;
         Some(if is_scaled {
             advance / 65536.0
@@ -104,7 +102,7 @@ impl FreeTypeInstance {
 
     pub fn outline(&mut self, glyph_id: GlyphId, pen: &mut impl OutlinePen) -> Option<()> {
         self.face
-            .load_glyph(glyph_id.to_u16() as u32, self.load_flags())
+            .load_glyph(glyph_id.to_u32(), self.load_flags())
             .ok()?;
         let mut ft_pen = FreeTypePen {
             inner: pen,
diff --git a/font-codegen/src/parsing.rs b/font-codegen/src/parsing.rs
index 32edfeeb..52b7fbf7 100644
--- a/font-codegen/src/parsing.rs
+++ b/font-codegen/src/parsing.rs
@@ -776,7 +776,7 @@ enum WellKnownScalar {
     LongDateTime,
     Tag,
     Version16Dot16,
-    GlyphId,
+    GlyphId16,
     NameId,
     MajorMinor,
 }
@@ -802,7 +802,7 @@ impl FromStr for WellKnownScalar {
             "LongDateTime" => Ok(WellKnownScalar::LongDateTime),
             "Tag" => Ok(WellKnownScalar::Tag),
             "Version16Dot16" => Ok(WellKnownScalar::Version16Dot16),
-            "GlyphId" => Ok(WellKnownScalar::GlyphId),
+            "GlyphId16" => Ok(WellKnownScalar::GlyphId16),
             "NameId" => Ok(WellKnownScalar::NameId),
             "MajorMinor" => Ok(WellKnownScalar::MajorMinor),
             _ => Err(()),
diff --git a/font-codegen/src/table.rs b/font-codegen/src/table.rs
index 2782ca10..991b1305 100644
--- a/font-codegen/src/table.rs
+++ b/font-codegen/src/table.rs
@@ -677,10 +677,28 @@ fn generate_format_shared_getters(item: &TableFormat, items: &Items) -> syn::Res
         .iter()
         .map(|fld| generate_format_getter_for_shared_field(item, fld));
 
+    // and we also want to have a wrapper for offset_data():
+    let data_arms = item
+        .variants
+        .iter()
+        .filter(|v| v.attrs.write_only.is_none())
+        .map(|variant| {
+            let var_name = &variant.name;
+            quote!(Self::#var_name(item) => item.offset_data(), )
+        });
+
     // now we have a collection of fields present on all variants, and
     // we need to actually generate the wrapping getter
 
-    Ok(quote!( #( #getters )* ))
+    Ok(quote! {
+        #[doc = "Return the `FontData` used to resolve offsets for this table."]
+        pub fn offset_data(&self) -> FontData<'a> {
+            match self {
+                #( #data_arms )*
+            }
+        }
+        #( #getters )*
+    })
 }
 
 fn generate_format_getter_for_shared_field(item: &TableFormat, field: &Field) -> TokenStream {
diff --git a/font-types/src/glyph_id.rs b/font-types/src/glyph_id.rs
index be5686d9..474edefe 100644
--- a/font-types/src/glyph_id.rs
+++ b/font-types/src/glyph_id.rs
@@ -1,4 +1,4 @@
-//! Glyph Identifiers
+//! Glyph Identifiers.
 //!
 //! Although these are treated as u16s in the spec, we choose to represent them
 //! as a distinct type.
@@ -8,15 +8,15 @@
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 #[cfg_attr(feature = "bytemuck", derive(bytemuck::AnyBitPattern))]
 #[repr(transparent)]
-pub struct GlyphId(u16);
+pub struct GlyphId16(u16);
 
-impl GlyphId {
+impl GlyphId16 {
     /// The identifier reserved for unknown glyphs
-    pub const NOTDEF: GlyphId = GlyphId(0);
+    pub const NOTDEF: GlyphId16 = GlyphId16(0);
 
-    /// Construct a new `GlyphId`.
+    /// Construct a new `GlyphId16`.
     pub const fn new(raw: u16) -> Self {
-        GlyphId(raw)
+        GlyphId16(raw)
     }
 
     /// The identifier as a u16.
@@ -34,6 +34,54 @@ impl GlyphId {
     }
 }
 
+impl Default for GlyphId16 {
+    fn default() -> Self {
+        GlyphId16::NOTDEF
+    }
+}
+
+impl From<u16> for GlyphId16 {
+    fn from(value: u16) -> Self {
+        Self(value)
+    }
+}
+
+impl std::fmt::Display for GlyphId16 {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(f, "GID_{}", self.0)
+    }
+}
+
+impl From<GlyphId16> for u32 {
+    fn from(value: GlyphId16) -> u32 {
+        value.to_u32()
+    }
+}
+
+crate::newtype_scalar!(GlyphId16, [u8; 2]);
+
+/// A 32-bit glyph identifier.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "bytemuck", derive(bytemuck::AnyBitPattern))]
+#[repr(transparent)]
+pub struct GlyphId(u32);
+
+impl GlyphId {
+    /// The identifier reserved for unknown glyphs.
+    pub const NOTDEF: GlyphId = GlyphId(0);
+
+    /// Construct a new `GlyphId`.
+    pub const fn new(raw: u32) -> Self {
+        Self(raw)
+    }
+
+    /// The identifier as a u32.
+    pub const fn to_u32(self) -> u32 {
+        self.0
+    }
+}
+
 impl Default for GlyphId {
     fn default() -> Self {
         GlyphId::NOTDEF
@@ -42,6 +90,12 @@ impl Default for GlyphId {
 
 impl From<u16> for GlyphId {
     fn from(value: u16) -> Self {
+        Self(value as u32)
+    }
+}
+
+impl From<u32> for GlyphId {
+    fn from(value: u32) -> Self {
         Self(value)
     }
 }
@@ -58,4 +112,58 @@ impl From<GlyphId> for u32 {
     }
 }
 
-crate::newtype_scalar!(GlyphId, [u8; 2]);
+impl From<GlyphId16> for GlyphId {
+    fn from(value: GlyphId16) -> GlyphId {
+        Self(value.to_u32())
+    }
+}
+
+impl PartialEq<GlyphId16> for GlyphId {
+    fn eq(&self, other: &GlyphId16) -> bool {
+        self.0 == other.0 as u32
+    }
+}
+
+impl PartialOrd<GlyphId16> for GlyphId {
+    fn partial_cmp(&self, other: &GlyphId16) -> Option<core::cmp::Ordering> {
+        Some(self.0.cmp(&(other.0 as u32)))
+    }
+}
+
+impl PartialEq<GlyphId> for GlyphId16 {
+    fn eq(&self, other: &GlyphId) -> bool {
+        self.0 as u32 == other.0
+    }
+}
+
+impl PartialOrd<GlyphId> for GlyphId16 {
+    fn partial_cmp(&self, other: &GlyphId) -> Option<core::cmp::Ordering> {
+        Some((self.0 as u32).cmp(&other.0))
+    }
+}
+
+impl TryFrom<GlyphId> for GlyphId16 {
+    type Error = TryFromGlyphIdError;
+
+    fn try_from(value: GlyphId) -> Result<Self, Self::Error> {
+        Ok(Self(
+            value
+                .0
+                .try_into()
+                .map_err(|_| TryFromGlyphIdError(value.0))?,
+        ))
+    }
+}
+
+/// The error type returned when a glyph identifier conversion fails.
+#[derive(Debug)]
+pub struct TryFromGlyphIdError(u32);
+
+impl core::fmt::Display for TryFromGlyphIdError {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(f, "glyph identifier {} too large for conversion", self.0)
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for TryFromGlyphIdError {}
diff --git a/font-types/src/lib.rs b/font-types/src/lib.rs
index b57542e4..49e2ae44 100644
--- a/font-types/src/lib.rs
+++ b/font-types/src/lib.rs
@@ -33,7 +33,7 @@ mod serde_test;
 pub use bbox::BoundingBox;
 pub use fixed::{F26Dot6, F2Dot14, F4Dot12, F6Dot10, Fixed};
 pub use fword::{FWord, UfWord};
-pub use glyph_id::GlyphId;
+pub use glyph_id::{GlyphId, GlyphId16, TryFromGlyphIdError};
 pub use longdatetime::LongDateTime;
 pub use name_id::NameId;
 pub use offset::{Nullable, Offset16, Offset24, Offset32};
diff --git a/int-set/src/lib.rs b/int-set/src/lib.rs
index 674d9b95..0c44c5f6 100644
--- a/int-set/src/lib.rs
+++ b/int-set/src/lib.rs
@@ -24,7 +24,7 @@ mod output_bit_stream;
 pub mod sparse_bit_set;
 
 use bitset::BitSet;
-use font_types::GlyphId;
+use font_types::{GlyphId, GlyphId16};
 use std::hash::Hash;
 use std::marker::PhantomData;
 use std::ops::RangeInclusive;
@@ -700,13 +700,13 @@ impl Domain<u8> for u8 {
     }
 }
 
-impl Domain<GlyphId> for GlyphId {
+impl Domain<GlyphId16> for GlyphId16 {
     fn to_u32(&self) -> u32 {
         self.to_u16() as u32
     }
 
-    fn from_u32(member: InDomain) -> GlyphId {
-        GlyphId::from(member.value() as u16)
+    fn from_u32(member: InDomain) -> GlyphId16 {
+        GlyphId16::new(member.value() as u16)
     }
 
     fn is_continous() -> bool {
@@ -717,6 +717,30 @@ impl Domain<GlyphId> for GlyphId {
         (u16::MIN as u32)..=(u16::MAX as u32)
     }
 
+    fn ordered_values_range(
+        range: RangeInclusive<GlyphId16>,
+    ) -> impl DoubleEndedIterator<Item = u32> {
+        range.start().to_u32()..=range.end().to_u32()
+    }
+}
+
+impl Domain<GlyphId> for GlyphId {
+    fn to_u32(&self) -> u32 {
+        GlyphId::to_u32(*self)
+    }
+
+    fn from_u32(member: InDomain) -> GlyphId {
+        GlyphId::from(member.value())
+    }
+
+    fn is_continous() -> bool {
+        true
+    }
+
+    fn ordered_values() -> impl DoubleEndedIterator<Item = u32> {
+        u32::MIN..=u32::MAX
+    }
+
     fn ordered_values_range(
         range: RangeInclusive<GlyphId>,
     ) -> impl DoubleEndedIterator<Item = u32> {
@@ -1569,6 +1593,45 @@ mod test {
         assert_eq!(it.next(), Some(6));
     }
 
+    #[test]
+    fn with_glyph_id_16() {
+        let mut set = IntSet::<font_types::GlyphId16>::empty();
+
+        set.insert(GlyphId16::new(5));
+        set.insert(GlyphId16::new(8));
+        set.insert(GlyphId16::new(12));
+        set.insert_range(GlyphId16::new(200)..=GlyphId16::new(210));
+
+        assert!(set.contains(GlyphId16::new(5)));
+        assert!(!set.contains(GlyphId16::new(6)));
+        assert!(!set.contains(GlyphId16::new(199)));
+        assert!(set.contains(GlyphId16::new(200)));
+        assert!(set.contains(GlyphId16::new(210)));
+        assert!(!set.contains(GlyphId16::new(211)));
+
+        let copy: IntSet<GlyphId16> = set.iter().collect();
+        assert_eq!(set, copy);
+
+        set.invert();
+
+        assert!(!set.contains(GlyphId16::new(5)));
+        assert!(set.contains(GlyphId16::new(6)));
+
+        let Some(max) = set.iter().max() else {
+            panic!("should have a max");
+        };
+
+        assert_eq!(max, GlyphId16::new(u16::MAX));
+
+        let mut it = set.iter();
+        assert_eq!(it.next(), Some(GlyphId16::new(0)));
+        assert_eq!(it.next(), Some(GlyphId16::new(1)));
+        assert_eq!(it.next(), Some(GlyphId16::new(2)));
+        assert_eq!(it.next(), Some(GlyphId16::new(3)));
+        assert_eq!(it.next(), Some(GlyphId16::new(4)));
+        assert_eq!(it.next(), Some(GlyphId16::new(6)));
+    }
+
     #[test]
     fn with_glyph_id() {
         let mut set = IntSet::<font_types::GlyphId>::empty();
@@ -1593,12 +1656,6 @@ mod test {
         assert!(!set.contains(GlyphId::new(5)));
         assert!(set.contains(GlyphId::new(6)));
 
-        let Some(max) = set.iter().max() else {
-            panic!("should have a max");
-        };
-
-        assert_eq!(max, GlyphId::new(u16::MAX));
-
         let mut it = set.iter();
         assert_eq!(it.next(), Some(GlyphId::new(0)));
         assert_eq!(it.next(), Some(GlyphId::new(1)));
diff --git a/klippa/src/hmtx.rs b/klippa/src/hmtx.rs
index 84eab1d7..6b113ccd 100644
--- a/klippa/src/hmtx.rs
+++ b/klippa/src/hmtx.rs
@@ -15,7 +15,7 @@ impl Subset for Hmtx {
         let mut new_metrics = Vec::with_capacity(num_long_metrics);
         let mut new_side_bearings = Vec::new();
         for gid in gids.iter() {
-            let glyph_id = gid.to_u16() as usize;
+            let glyph_id = gid.to_u32() as usize;
             let side_bearing =
                 get_gid_side_bearing(&self.h_metrics, &self.left_side_bearings, glyph_id);
             if glyph_id < num_long_metrics {
diff --git a/klippa/src/lib.rs b/klippa/src/lib.rs
index dde9978c..7e93fd5e 100644
--- a/klippa/src/lib.rs
+++ b/klippa/src/lib.rs
@@ -24,7 +24,10 @@ use write_fonts::{
 
 const MAX_COMPOSITE_OPERATIONS_PER_GLYPH: u8 = 64;
 const MAX_NESTING_LEVEL: u8 = 64;
-const MAX_GID: GlyphId = GlyphId::new(u16::MAX);
+// Support 24-bit gids. This should probably be extended to u32::MAX but
+// this causes tests to fail with 'subtract with overflow error'.
+// See <https://github.com/googlefonts/fontations/issues/997>
+const MAX_GID: GlyphId = GlyphId::new(0xFFFFFF);
 
 #[allow(dead_code)]
 #[derive(Default)]
@@ -185,7 +188,7 @@ fn glyf_closure_glyphs(
             operation_count = glyf_closure_glyphs(
                 loca,
                 glyf,
-                child.glyph,
+                child.glyph.into(),
                 gids_to_retain,
                 operation_count,
                 depth,
@@ -196,7 +199,7 @@ fn glyf_closure_glyphs(
 }
 
 fn remove_invalid_gids(gids: &mut IntSet<GlyphId>, num_glyphs: usize) {
-    gids.remove_range(GlyphId::new(num_glyphs as u16)..=MAX_GID);
+    gids.remove_range(GlyphId::new(num_glyphs as u32)..=MAX_GID);
 }
 
 fn get_font_num_glyphs(font: &FontRef) -> usize {
@@ -211,7 +214,7 @@ fn compute_new_num_h_metrics(hmtx_table: &Hmtx, glyph_ids: &IntSet<GlyphId>) ->
     let num_long_metrics = glyph_ids.len().min(0xFFFF);
     //TODO: we still need a BTreeSet here because we currently don't have max() and Iterator::rev() for IntSet
     let gids: BTreeSet<GlyphId> = glyph_ids.iter().collect();
-    let last_gid = gids.last().unwrap().to_u16() as usize;
+    let last_gid = gids.last().unwrap().to_u32() as usize;
     let last_advance = hmtx_table
         .h_metrics
         .get(last_gid)
@@ -225,7 +228,7 @@ fn compute_new_num_h_metrics(hmtx_table: &Hmtx, glyph_ids: &IntSet<GlyphId>) ->
         .take_while(|gid| {
             hmtx_table
                 .h_metrics
-                .get(gid.to_u16() as usize)
+                .get(gid.to_u32() as usize)
                 .or_else(|| hmtx_table.h_metrics.last())
                 .unwrap()
                 .advance
@@ -241,7 +244,7 @@ pub enum SubsetError {
     InvalidGid(String),
 
     #[error("Invalid gid range {start}-{end}")]
-    InvalidGidRange { start: u16, end: u16 },
+    InvalidGidRange { start: u32, end: u32 },
 
     #[error("Invalid input unicode {0}")]
     InvalidUnicode(String),
diff --git a/klippa/src/parsing_util.rs b/klippa/src/parsing_util.rs
index 008f3fba..a9ca3186 100644
--- a/klippa/src/parsing_util.rs
+++ b/klippa/src/parsing_util.rs
@@ -11,19 +11,19 @@ pub fn populate_gids(gid_str: &str) -> Result<IntSet<GlyphId>, SubsetError> {
     }
     for gid in gid_str.split(',') {
         if let Some((start, end)) = gid.split_once('-') {
-            let start: u16 = start
-                .parse::<u16>()
+            let start: u32 = start
+                .parse::<u32>()
                 .map_err(|_| SubsetError::InvalidGid(start.to_owned()))?;
-            let end: u16 = end
-                .parse::<u16>()
+            let end: u32 = end
+                .parse::<u32>()
                 .map_err(|_| SubsetError::InvalidGid(end.to_owned()))?;
             if start > end {
                 return Err(SubsetError::InvalidGidRange { start, end });
             }
             result.extend((start..=end).map(GlyphId::new));
         } else {
-            let glyph_id: u16 = gid
-                .parse::<u16>()
+            let glyph_id: u32 = gid
+                .parse::<u32>()
                 .map_err(|_| SubsetError::InvalidGid(gid.to_owned()))?;
             result.insert(GlyphId::new(glyph_id));
         }
diff --git a/otexplorer/src/print.rs b/otexplorer/src/print.rs
index c5ab8dc6..fa280096 100644
--- a/otexplorer/src/print.rs
+++ b/otexplorer/src/print.rs
@@ -175,7 +175,7 @@ impl<'a> PrettyPrinter<'a> {
             FieldType::F2Dot14(val) => write!(self, "{val}")?,
             FieldType::Fixed(val) => write!(self, "{val}")?,
             FieldType::LongDateTime(val) => write!(self, "{val:?}")?,
-            FieldType::GlyphId(val) => self.print_with_style(Color::Yellow.into(), |this| {
+            FieldType::GlyphId16(val) => self.print_with_style(Color::Yellow.into(), |this| {
                 write!(this, "{}", val.to_u16())
             })?,
             FieldType::NameId(val) => write!(self, "{val:?}")?,
@@ -247,7 +247,7 @@ impl<'a> PrettyPrinter<'a> {
             FieldType::F2Dot14(val) => self.print_hex(&val.to_be_bytes())?,
             FieldType::Fixed(val) => self.print_hex(&val.to_be_bytes())?,
             FieldType::LongDateTime(val) => self.print_hex(&val.to_be_bytes())?,
-            FieldType::GlyphId(val) => self.print_hex(&val.to_be_bytes())?,
+            FieldType::GlyphId16(val) => self.print_hex(&val.to_be_bytes())?,
             FieldType::BareOffset(offset) => self.print_offset_hex(*offset)?,
             _ => (),
         }
@@ -355,6 +355,6 @@ fn is_scalar(field_type: &FieldType) -> bool {
             | FieldType::F2Dot14(_)
             | FieldType::Fixed(_)
             | FieldType::LongDateTime(_)
-            | FieldType::GlyphId(_)
+            | FieldType::GlyphId16(_)
     )
 }
diff --git a/otexplorer/src/query.rs b/otexplorer/src/query.rs
index 039d9212..3b93d11b 100644
--- a/otexplorer/src/query.rs
+++ b/otexplorer/src/query.rs
@@ -171,7 +171,7 @@ fn field_type_name(field_type: &FieldType) -> Cow<'static, str> {
         FieldType::F2Dot14(_) => "F2Dot14".into(),
         FieldType::Fixed(_) => "Fixed".into(),
         FieldType::LongDateTime(_) => "LongDateTime".into(),
-        FieldType::GlyphId(_) => "GlyphId".into(),
+        FieldType::GlyphId16(_) => "GlyphId".into(),
         FieldType::NameId(_) => "NameId".into(),
         FieldType::Array(arr) => format!("[{}]", arr.type_name()).into(),
         FieldType::Record(record) => record.type_name().to_string().into(),
diff --git a/read-fonts/generated/generated_aat.rs b/read-fonts/generated/generated_aat.rs
index 4cef4bd0..808f69f3 100644
--- a/read-fonts/generated/generated_aat.rs
+++ b/read-fonts/generated/generated_aat.rs
@@ -18,6 +18,18 @@ pub enum Lookup<'a> {
 }
 
 impl<'a> Lookup<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+            Self::Format6(item) => item.offset_data(),
+            Self::Format8(item) => item.offset_data(),
+            Self::Format10(item) => item.offset_data(),
+        }
+    }
+
     /// Format number is set to 0.
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_base.rs b/read-fonts/generated/generated_base.rs
index 06639780..c472e5ec 100644
--- a/read-fonts/generated/generated_base.rs
+++ b/read-fonts/generated/generated_base.rs
@@ -919,6 +919,15 @@ pub enum BaseCoord<'a> {
 }
 
 impl<'a> BaseCoord<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — format = 1
     pub fn base_coord_format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_bitmap.rs b/read-fonts/generated/generated_bitmap.rs
index 7086b2e8..8a2171b7 100644
--- a/read-fonts/generated/generated_bitmap.rs
+++ b/read-fonts/generated/generated_bitmap.rs
@@ -23,9 +23,9 @@ pub struct BitmapSize {
     /// Line metrics for text rendered vertically.
     pub vert: SbitLineMetrics,
     /// Lowest glyph index for this size.
-    pub start_glyph_index: BigEndian<GlyphId>,
+    pub start_glyph_index: BigEndian<GlyphId16>,
     /// Highest glyph index for this size.
-    pub end_glyph_index: BigEndian<GlyphId>,
+    pub end_glyph_index: BigEndian<GlyphId16>,
     /// Horizontal pixels per em.
     pub ppem_x: u8,
     /// Vertical pixels per em.
@@ -69,12 +69,12 @@ impl BitmapSize {
     }
 
     /// Lowest glyph index for this size.
-    pub fn start_glyph_index(&self) -> GlyphId {
+    pub fn start_glyph_index(&self) -> GlyphId16 {
         self.start_glyph_index.get()
     }
 
     /// Highest glyph index for this size.
-    pub fn end_glyph_index(&self) -> GlyphId {
+    pub fn end_glyph_index(&self) -> GlyphId16 {
         self.end_glyph_index.get()
     }
 
@@ -107,8 +107,8 @@ impl FixedSize for BitmapSize {
         + u32::RAW_BYTE_LEN
         + SbitLineMetrics::RAW_BYTE_LEN
         + SbitLineMetrics::RAW_BYTE_LEN
-        + GlyphId::RAW_BYTE_LEN
-        + GlyphId::RAW_BYTE_LEN
+        + GlyphId16::RAW_BYTE_LEN
+        + GlyphId16::RAW_BYTE_LEN
         + u8::RAW_BYTE_LEN
         + u8::RAW_BYTE_LEN
         + u8::RAW_BYTE_LEN
@@ -744,11 +744,11 @@ pub struct IndexSubtableArrayMarker {}
 impl IndexSubtableArrayMarker {
     fn first_glyph_index_byte_range(&self) -> Range<usize> {
         let start = 0;
-        start..start + GlyphId::RAW_BYTE_LEN
+        start..start + GlyphId16::RAW_BYTE_LEN
     }
     fn last_glyph_index_byte_range(&self) -> Range<usize> {
         let start = self.first_glyph_index_byte_range().end;
-        start..start + GlyphId::RAW_BYTE_LEN
+        start..start + GlyphId16::RAW_BYTE_LEN
     }
     fn additional_offset_to_index_subtable_byte_range(&self) -> Range<usize> {
         let start = self.last_glyph_index_byte_range().end;
@@ -759,8 +759,8 @@ impl IndexSubtableArrayMarker {
 impl<'a> FontRead<'a> for IndexSubtableArray<'a> {
     fn read(data: FontData<'a>) -> Result<Self, ReadError> {
         let mut cursor = data.cursor();
-        cursor.advance::<GlyphId>();
-        cursor.advance::<GlyphId>();
+        cursor.advance::<GlyphId16>();
+        cursor.advance::<GlyphId16>();
         cursor.advance::<u32>();
         cursor.finish(IndexSubtableArrayMarker {})
     }
@@ -771,13 +771,13 @@ pub type IndexSubtableArray<'a> = TableRef<'a, IndexSubtableArrayMarker>;
 
 impl<'a> IndexSubtableArray<'a> {
     /// First glyph ID of this range.
-    pub fn first_glyph_index(&self) -> GlyphId {
+    pub fn first_glyph_index(&self) -> GlyphId16 {
         let range = self.shape.first_glyph_index_byte_range();
         self.data.read_at(range.start).unwrap()
     }
 
     /// Last glyph ID of this range (inclusive).
-    pub fn last_glyph_index(&self) -> GlyphId {
+    pub fn last_glyph_index(&self) -> GlyphId16 {
         let range = self.shape.last_glyph_index_byte_range();
         self.data.read_at(range.start).unwrap()
     }
@@ -825,6 +825,17 @@ pub enum IndexSubtable<'a> {
 }
 
 impl<'a> IndexSubtable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+            Self::Format5(item) => item.offset_data(),
+        }
+    }
+
     /// Format of this IndexSubTable.
     pub fn index_format(&self) -> u16 {
         match self {
@@ -1333,14 +1344,14 @@ impl<'a> std::fmt::Debug for IndexSubtable4<'a> {
 #[repr(packed)]
 pub struct GlyphIdOffsetPair {
     /// Glyph ID of glyph present.
-    pub glyph_id: BigEndian<GlyphId>,
+    pub glyph_id: BigEndian<GlyphId16>,
     /// Location in EBDT.
     pub sbit_offset: BigEndian<u16>,
 }
 
 impl GlyphIdOffsetPair {
     /// Glyph ID of glyph present.
-    pub fn glyph_id(&self) -> GlyphId {
+    pub fn glyph_id(&self) -> GlyphId16 {
         self.glyph_id.get()
     }
 
@@ -1351,7 +1362,7 @@ impl GlyphIdOffsetPair {
 }
 
 impl FixedSize for GlyphIdOffsetPair {
-    const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
+    const RAW_BYTE_LEN: usize = GlyphId16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
@@ -1423,7 +1434,7 @@ impl<'a> FontRead<'a> for IndexSubtable5<'a> {
         cursor.advance_by(big_metrics_byte_len);
         let num_glyphs: u32 = cursor.read()?;
         let glyph_array_byte_len = (num_glyphs as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(glyph_array_byte_len);
         cursor.finish(IndexSubtable5Marker {
@@ -1474,7 +1485,7 @@ impl<'a> IndexSubtable5<'a> {
     }
 
     /// One per glyph, sorted by glyhph ID.
-    pub fn glyph_array(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn glyph_array(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.glyph_array_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -1519,7 +1530,7 @@ impl<'a> std::fmt::Debug for IndexSubtable5<'a> {
 #[repr(packed)]
 pub struct BdtComponent {
     /// Component glyph ID.
-    pub glyph_id: BigEndian<GlyphId>,
+    pub glyph_id: BigEndian<GlyphId16>,
     /// Position of component left.
     pub x_offset: BigEndian<i8>,
     /// Position of component top.
@@ -1528,7 +1539,7 @@ pub struct BdtComponent {
 
 impl BdtComponent {
     /// Component glyph ID.
-    pub fn glyph_id(&self) -> GlyphId {
+    pub fn glyph_id(&self) -> GlyphId16 {
         self.glyph_id.get()
     }
 
@@ -1544,7 +1555,7 @@ impl BdtComponent {
 }
 
 impl FixedSize for BdtComponent {
-    const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + i8::RAW_BYTE_LEN + i8::RAW_BYTE_LEN;
+    const RAW_BYTE_LEN: usize = GlyphId16::RAW_BYTE_LEN + i8::RAW_BYTE_LEN + i8::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
diff --git a/read-fonts/generated/generated_cmap.rs b/read-fonts/generated/generated_cmap.rs
index f5623b94..5bca4800 100644
--- a/read-fonts/generated/generated_cmap.rs
+++ b/read-fonts/generated/generated_cmap.rs
@@ -229,6 +229,21 @@ pub enum CmapSubtable<'a> {
 }
 
 impl<'a> CmapSubtable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+            Self::Format6(item) => item.offset_data(),
+            Self::Format8(item) => item.offset_data(),
+            Self::Format10(item) => item.offset_data(),
+            Self::Format12(item) => item.offset_data(),
+            Self::Format13(item) => item.offset_data(),
+            Self::Format14(item) => item.offset_data(),
+        }
+    }
+
     /// Format number is set to 0.
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_colr.rs b/read-fonts/generated/generated_colr.rs
index a6bb075b..a595c283 100644
--- a/read-fonts/generated/generated_colr.rs
+++ b/read-fonts/generated/generated_colr.rs
@@ -310,7 +310,7 @@ impl<'a> std::fmt::Debug for Colr<'a> {
 #[repr(packed)]
 pub struct BaseGlyph {
     /// Glyph ID of the base glyph.
-    pub glyph_id: BigEndian<GlyphId>,
+    pub glyph_id: BigEndian<GlyphId16>,
     /// Index (base 0) into the layerRecords array.
     pub first_layer_index: BigEndian<u16>,
     /// Number of color layers associated with this glyph.
@@ -319,7 +319,7 @@ pub struct BaseGlyph {
 
 impl BaseGlyph {
     /// Glyph ID of the base glyph.
-    pub fn glyph_id(&self) -> GlyphId {
+    pub fn glyph_id(&self) -> GlyphId16 {
         self.glyph_id.get()
     }
 
@@ -335,7 +335,7 @@ impl BaseGlyph {
 }
 
 impl FixedSize for BaseGlyph {
-    const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
+    const RAW_BYTE_LEN: usize = GlyphId16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
@@ -360,14 +360,14 @@ impl<'a> SomeRecord<'a> for BaseGlyph {
 #[repr(packed)]
 pub struct Layer {
     /// Glyph ID of the glyph used for a given layer.
-    pub glyph_id: BigEndian<GlyphId>,
+    pub glyph_id: BigEndian<GlyphId16>,
     /// Index (base 0) for a palette entry in the CPAL table.
     pub palette_index: BigEndian<u16>,
 }
 
 impl Layer {
     /// Glyph ID of the glyph used for a given layer.
-    pub fn glyph_id(&self) -> GlyphId {
+    pub fn glyph_id(&self) -> GlyphId16 {
         self.glyph_id.get()
     }
 
@@ -378,7 +378,7 @@ impl Layer {
 }
 
 impl FixedSize for Layer {
-    const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
+    const RAW_BYTE_LEN: usize = GlyphId16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
@@ -480,14 +480,14 @@ impl<'a> std::fmt::Debug for BaseGlyphList<'a> {
 #[repr(packed)]
 pub struct BaseGlyphPaint {
     /// Glyph ID of the base glyph.
-    pub glyph_id: BigEndian<GlyphId>,
+    pub glyph_id: BigEndian<GlyphId16>,
     /// Offset to a Paint table, from the beginning of the [`BaseGlyphList`] table.
     pub paint_offset: BigEndian<Offset32>,
 }
 
 impl BaseGlyphPaint {
     /// Glyph ID of the base glyph.
-    pub fn glyph_id(&self) -> GlyphId {
+    pub fn glyph_id(&self) -> GlyphId16 {
         self.glyph_id.get()
     }
 
@@ -506,7 +506,7 @@ impl BaseGlyphPaint {
 }
 
 impl FixedSize for BaseGlyphPaint {
-    const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN;
+    const RAW_BYTE_LEN: usize = GlyphId16::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
@@ -709,21 +709,21 @@ impl<'a> std::fmt::Debug for ClipList<'a> {
 #[repr(packed)]
 pub struct Clip {
     /// First glyph ID in the range.
-    pub start_glyph_id: BigEndian<GlyphId>,
+    pub start_glyph_id: BigEndian<GlyphId16>,
     /// Last glyph ID in the range.
-    pub end_glyph_id: BigEndian<GlyphId>,
+    pub end_glyph_id: BigEndian<GlyphId16>,
     /// Offset to a ClipBox table, from the beginning of the [`ClipList`] table.
     pub clip_box_offset: BigEndian<Offset24>,
 }
 
 impl Clip {
     /// First glyph ID in the range.
-    pub fn start_glyph_id(&self) -> GlyphId {
+    pub fn start_glyph_id(&self) -> GlyphId16 {
         self.start_glyph_id.get()
     }
 
     /// Last glyph ID in the range.
-    pub fn end_glyph_id(&self) -> GlyphId {
+    pub fn end_glyph_id(&self) -> GlyphId16 {
         self.end_glyph_id.get()
     }
 
@@ -743,7 +743,7 @@ impl Clip {
 
 impl FixedSize for Clip {
     const RAW_BYTE_LEN: usize =
-        GlyphId::RAW_BYTE_LEN + GlyphId::RAW_BYTE_LEN + Offset24::RAW_BYTE_LEN;
+        GlyphId16::RAW_BYTE_LEN + GlyphId16::RAW_BYTE_LEN + Offset24::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
@@ -773,6 +773,14 @@ pub enum ClipBox<'a> {
 }
 
 impl<'a> ClipBox<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Set to 1.
     pub fn format(&self) -> u8 {
         match self {
@@ -1533,6 +1541,44 @@ pub enum Paint<'a> {
 }
 
 impl<'a> Paint<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::ColrLayers(item) => item.offset_data(),
+            Self::Solid(item) => item.offset_data(),
+            Self::VarSolid(item) => item.offset_data(),
+            Self::LinearGradient(item) => item.offset_data(),
+            Self::VarLinearGradient(item) => item.offset_data(),
+            Self::RadialGradient(item) => item.offset_data(),
+            Self::VarRadialGradient(item) => item.offset_data(),
+            Self::SweepGradient(item) => item.offset_data(),
+            Self::VarSweepGradient(item) => item.offset_data(),
+            Self::Glyph(item) => item.offset_data(),
+            Self::ColrGlyph(item) => item.offset_data(),
+            Self::Transform(item) => item.offset_data(),
+            Self::VarTransform(item) => item.offset_data(),
+            Self::Translate(item) => item.offset_data(),
+            Self::VarTranslate(item) => item.offset_data(),
+            Self::Scale(item) => item.offset_data(),
+            Self::VarScale(item) => item.offset_data(),
+            Self::ScaleAroundCenter(item) => item.offset_data(),
+            Self::VarScaleAroundCenter(item) => item.offset_data(),
+            Self::ScaleUniform(item) => item.offset_data(),
+            Self::VarScaleUniform(item) => item.offset_data(),
+            Self::ScaleUniformAroundCenter(item) => item.offset_data(),
+            Self::VarScaleUniformAroundCenter(item) => item.offset_data(),
+            Self::Rotate(item) => item.offset_data(),
+            Self::VarRotate(item) => item.offset_data(),
+            Self::RotateAroundCenter(item) => item.offset_data(),
+            Self::VarRotateAroundCenter(item) => item.offset_data(),
+            Self::Skew(item) => item.offset_data(),
+            Self::VarSkew(item) => item.offset_data(),
+            Self::SkewAroundCenter(item) => item.offset_data(),
+            Self::VarSkewAroundCenter(item) => item.offset_data(),
+            Self::Composite(item) => item.offset_data(),
+        }
+    }
+
     /// Set to 1.
     pub fn format(&self) -> u8 {
         match self {
@@ -2853,7 +2899,7 @@ impl PaintGlyphMarker {
     }
     fn glyph_id_byte_range(&self) -> Range<usize> {
         let start = self.paint_offset_byte_range().end;
-        start..start + GlyphId::RAW_BYTE_LEN
+        start..start + GlyphId16::RAW_BYTE_LEN
     }
 }
 
@@ -2862,7 +2908,7 @@ impl<'a> FontRead<'a> for PaintGlyph<'a> {
         let mut cursor = data.cursor();
         cursor.advance::<u8>();
         cursor.advance::<Offset24>();
-        cursor.advance::<GlyphId>();
+        cursor.advance::<GlyphId16>();
         cursor.finish(PaintGlyphMarker {})
     }
 }
@@ -2890,7 +2936,7 @@ impl<'a> PaintGlyph<'a> {
     }
 
     /// Glyph ID for the source outline.
-    pub fn glyph_id(&self) -> GlyphId {
+    pub fn glyph_id(&self) -> GlyphId16 {
         let range = self.shape.glyph_id_byte_range();
         self.data.read_at(range.start).unwrap()
     }
@@ -2937,7 +2983,7 @@ impl PaintColrGlyphMarker {
     }
     fn glyph_id_byte_range(&self) -> Range<usize> {
         let start = self.format_byte_range().end;
-        start..start + GlyphId::RAW_BYTE_LEN
+        start..start + GlyphId16::RAW_BYTE_LEN
     }
 }
 
@@ -2945,7 +2991,7 @@ impl<'a> FontRead<'a> for PaintColrGlyph<'a> {
     fn read(data: FontData<'a>) -> Result<Self, ReadError> {
         let mut cursor = data.cursor();
         cursor.advance::<u8>();
-        cursor.advance::<GlyphId>();
+        cursor.advance::<GlyphId16>();
         cursor.finish(PaintColrGlyphMarker {})
     }
 }
@@ -2961,7 +3007,7 @@ impl<'a> PaintColrGlyph<'a> {
     }
 
     /// Glyph ID for a BaseGlyphList base glyph.
-    pub fn glyph_id(&self) -> GlyphId {
+    pub fn glyph_id(&self) -> GlyphId16 {
         let range = self.shape.glyph_id_byte_range();
         self.data.read_at(range.start).unwrap()
     }
diff --git a/read-fonts/generated/generated_gdef.rs b/read-fonts/generated/generated_gdef.rs
index a0f26a07..b85a696d 100644
--- a/read-fonts/generated/generated_gdef.rs
+++ b/read-fonts/generated/generated_gdef.rs
@@ -664,6 +664,15 @@ pub enum CaretValue<'a> {
 }
 
 impl<'a> CaretValue<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn caret_value_format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_glyf.rs b/read-fonts/generated/generated_glyf.rs
index 5be60544..f1877ae3 100644
--- a/read-fonts/generated/generated_glyf.rs
+++ b/read-fonts/generated/generated_glyf.rs
@@ -1126,6 +1126,14 @@ pub enum Glyph<'a> {
 }
 
 impl<'a> Glyph<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Simple(item) => item.offset_data(),
+            Self::Composite(item) => item.offset_data(),
+        }
+    }
+
     /// If the number of contours is greater than or equal to zero,
     /// this is a simple glyph. If negative, this is a composite glyph
     /// — the value -1 should be used for composite glyphs.
diff --git a/read-fonts/generated/generated_gpos.rs b/read-fonts/generated/generated_gpos.rs
index 7e6bfffd..a44b990b 100644
--- a/read-fonts/generated/generated_gpos.rs
+++ b/read-fonts/generated/generated_gpos.rs
@@ -598,6 +598,15 @@ pub enum AnchorTable<'a> {
 }
 
 impl<'a> AnchorTable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier, = 1
     pub fn anchor_format(&self) -> u16 {
         match self {
@@ -1100,6 +1109,14 @@ pub enum SinglePos<'a> {
 }
 
 impl<'a> SinglePos<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn pos_format(&self) -> u16 {
         match self {
@@ -1411,6 +1428,14 @@ pub enum PairPos<'a> {
 }
 
 impl<'a> PairPos<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn pos_format(&self) -> u16 {
         match self {
@@ -1763,7 +1788,7 @@ impl<'a> std::fmt::Debug for PairSet<'a> {
 pub struct PairValueRecord {
     /// Glyph ID of second glyph in the pair (first glyph is listed in
     /// the Coverage table).
-    pub second_glyph: BigEndian<GlyphId>,
+    pub second_glyph: BigEndian<GlyphId16>,
     /// Positioning data for the first glyph in the pair.
     pub value_record1: ValueRecord,
     /// Positioning data for the second glyph in the pair.
@@ -1773,7 +1798,7 @@ pub struct PairValueRecord {
 impl PairValueRecord {
     /// Glyph ID of second glyph in the pair (first glyph is listed in
     /// the Coverage table).
-    pub fn second_glyph(&self) -> GlyphId {
+    pub fn second_glyph(&self) -> GlyphId16 {
         self.second_glyph.get()
     }
 
@@ -1798,7 +1823,7 @@ impl ComputeSize for PairValueRecord {
         let (value_format1, value_format2) = *args;
         let mut result = 0usize;
         result = result
-            .checked_add(GlyphId::RAW_BYTE_LEN)
+            .checked_add(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         result = result
             .checked_add(<ValueRecord as ComputeSize>::compute_size(&value_format1)?)
diff --git a/read-fonts/generated/generated_gsub.rs b/read-fonts/generated/generated_gsub.rs
index 732491b7..650e25bb 100644
--- a/read-fonts/generated/generated_gsub.rs
+++ b/read-fonts/generated/generated_gsub.rs
@@ -249,6 +249,14 @@ pub enum SingleSubst<'a> {
 }
 
 impl<'a> SingleSubst<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn subst_format(&self) -> u16 {
         match self {
@@ -431,7 +439,7 @@ impl<'a> FontRead<'a> for SingleSubstFormat2<'a> {
         cursor.advance::<Offset16>();
         let glyph_count: u16 = cursor.read()?;
         let substitute_glyph_ids_byte_len = (glyph_count as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(substitute_glyph_ids_byte_len);
         cursor.finish(SingleSubstFormat2Marker {
@@ -470,7 +478,7 @@ impl<'a> SingleSubstFormat2<'a> {
     }
 
     /// Array of substitute glyph IDs — ordered by Coverage index
-    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.substitute_glyph_ids_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -657,7 +665,7 @@ impl<'a> FontRead<'a> for Sequence<'a> {
         let mut cursor = data.cursor();
         let glyph_count: u16 = cursor.read()?;
         let substitute_glyph_ids_byte_len = (glyph_count as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(substitute_glyph_ids_byte_len);
         cursor.finish(SequenceMarker {
@@ -678,7 +686,7 @@ impl<'a> Sequence<'a> {
     }
 
     /// String of glyph IDs to substitute
-    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.substitute_glyph_ids_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -863,7 +871,7 @@ impl<'a> FontRead<'a> for AlternateSet<'a> {
         let mut cursor = data.cursor();
         let glyph_count: u16 = cursor.read()?;
         let alternate_glyph_ids_byte_len = (glyph_count as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(alternate_glyph_ids_byte_len);
         cursor.finish(AlternateSetMarker {
@@ -883,7 +891,7 @@ impl<'a> AlternateSet<'a> {
     }
 
     /// Array of alternate glyph IDs, in arbitrary order
-    pub fn alternate_glyph_ids(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn alternate_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.alternate_glyph_ids_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -1143,7 +1151,7 @@ pub struct LigatureMarker {
 impl LigatureMarker {
     fn ligature_glyph_byte_range(&self) -> Range<usize> {
         let start = 0;
-        start..start + GlyphId::RAW_BYTE_LEN
+        start..start + GlyphId16::RAW_BYTE_LEN
     }
     fn component_count_byte_range(&self) -> Range<usize> {
         let start = self.ligature_glyph_byte_range().end;
@@ -1158,10 +1166,10 @@ impl LigatureMarker {
 impl<'a> FontRead<'a> for Ligature<'a> {
     fn read(data: FontData<'a>) -> Result<Self, ReadError> {
         let mut cursor = data.cursor();
-        cursor.advance::<GlyphId>();
+        cursor.advance::<GlyphId16>();
         let component_count: u16 = cursor.read()?;
         let component_glyph_ids_byte_len = (transforms::subtract(component_count, 1_usize))
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(component_glyph_ids_byte_len);
         cursor.finish(LigatureMarker {
@@ -1175,7 +1183,7 @@ pub type Ligature<'a> = TableRef<'a, LigatureMarker>;
 
 impl<'a> Ligature<'a> {
     /// glyph ID of ligature to substitute
-    pub fn ligature_glyph(&self) -> GlyphId {
+    pub fn ligature_glyph(&self) -> GlyphId16 {
         let range = self.shape.ligature_glyph_byte_range();
         self.data.read_at(range.start).unwrap()
     }
@@ -1188,7 +1196,7 @@ impl<'a> Ligature<'a> {
 
     /// Array of component glyph IDs — start with the second
     /// component, ordered in writing direction
-    pub fn component_glyph_ids(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn component_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.component_glyph_ids_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -1497,7 +1505,7 @@ impl<'a> FontRead<'a> for ReverseChainSingleSubstFormat1<'a> {
         cursor.advance_by(lookahead_coverage_offsets_byte_len);
         let glyph_count: u16 = cursor.read()?;
         let substitute_glyph_ids_byte_len = (glyph_count as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(substitute_glyph_ids_byte_len);
         cursor.finish(ReverseChainSingleSubstFormat1Marker {
@@ -1578,7 +1586,7 @@ impl<'a> ReverseChainSingleSubstFormat1<'a> {
     }
 
     /// Array of substitute glyph IDs — ordered by Coverage index.
-    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn substitute_glyph_ids(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.substitute_glyph_ids_byte_range();
         self.data.read_array(range).unwrap()
     }
diff --git a/read-fonts/generated/generated_layout.rs b/read-fonts/generated/generated_layout.rs
index 6a4c101b..bd8055c4 100644
--- a/read-fonts/generated/generated_layout.rs
+++ b/read-fonts/generated/generated_layout.rs
@@ -982,7 +982,7 @@ impl<'a> FontRead<'a> for CoverageFormat1<'a> {
         cursor.advance::<u16>();
         let glyph_count: u16 = cursor.read()?;
         let glyph_array_byte_len = (glyph_count as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(glyph_array_byte_len);
         cursor.finish(CoverageFormat1Marker {
@@ -1008,7 +1008,7 @@ impl<'a> CoverageFormat1<'a> {
     }
 
     /// Array of glyph IDs — in numerical order
-    pub fn glyph_array(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn glyph_array(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.glyph_array_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -1135,21 +1135,21 @@ impl<'a> std::fmt::Debug for CoverageFormat2<'a> {
 #[repr(packed)]
 pub struct RangeRecord {
     /// First glyph ID in the range
-    pub start_glyph_id: BigEndian<GlyphId>,
+    pub start_glyph_id: BigEndian<GlyphId16>,
     /// Last glyph ID in the range
-    pub end_glyph_id: BigEndian<GlyphId>,
+    pub end_glyph_id: BigEndian<GlyphId16>,
     /// Coverage Index of first glyph ID in range
     pub start_coverage_index: BigEndian<u16>,
 }
 
 impl RangeRecord {
     /// First glyph ID in the range
-    pub fn start_glyph_id(&self) -> GlyphId {
+    pub fn start_glyph_id(&self) -> GlyphId16 {
         self.start_glyph_id.get()
     }
 
     /// Last glyph ID in the range
-    pub fn end_glyph_id(&self) -> GlyphId {
+    pub fn end_glyph_id(&self) -> GlyphId16 {
         self.end_glyph_id.get()
     }
 
@@ -1160,7 +1160,8 @@ impl RangeRecord {
 }
 
 impl FixedSize for RangeRecord {
-    const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + GlyphId::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
+    const RAW_BYTE_LEN: usize =
+        GlyphId16::RAW_BYTE_LEN + GlyphId16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
@@ -1190,6 +1191,14 @@ pub enum CoverageTable<'a> {
 }
 
 impl<'a> CoverageTable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — format = 1
     pub fn coverage_format(&self) -> u16 {
         match self {
@@ -1255,7 +1264,7 @@ impl ClassDefFormat1Marker {
     }
     fn start_glyph_id_byte_range(&self) -> Range<usize> {
         let start = self.class_format_byte_range().end;
-        start..start + GlyphId::RAW_BYTE_LEN
+        start..start + GlyphId16::RAW_BYTE_LEN
     }
     fn glyph_count_byte_range(&self) -> Range<usize> {
         let start = self.start_glyph_id_byte_range().end;
@@ -1271,7 +1280,7 @@ impl<'a> FontRead<'a> for ClassDefFormat1<'a> {
     fn read(data: FontData<'a>) -> Result<Self, ReadError> {
         let mut cursor = data.cursor();
         cursor.advance::<u16>();
-        cursor.advance::<GlyphId>();
+        cursor.advance::<GlyphId16>();
         let glyph_count: u16 = cursor.read()?;
         let class_value_array_byte_len = (glyph_count as usize)
             .checked_mul(u16::RAW_BYTE_LEN)
@@ -1294,7 +1303,7 @@ impl<'a> ClassDefFormat1<'a> {
     }
 
     /// First glyph ID of the classValueArray
-    pub fn start_glyph_id(&self) -> GlyphId {
+    pub fn start_glyph_id(&self) -> GlyphId16 {
         let range = self.shape.start_glyph_id_byte_range();
         self.data.read_at(range.start).unwrap()
     }
@@ -1434,21 +1443,21 @@ impl<'a> std::fmt::Debug for ClassDefFormat2<'a> {
 #[repr(packed)]
 pub struct ClassRangeRecord {
     /// First glyph ID in the range
-    pub start_glyph_id: BigEndian<GlyphId>,
+    pub start_glyph_id: BigEndian<GlyphId16>,
     /// Last glyph ID in the range
-    pub end_glyph_id: BigEndian<GlyphId>,
+    pub end_glyph_id: BigEndian<GlyphId16>,
     /// Applied to all glyphs in the range
     pub class: BigEndian<u16>,
 }
 
 impl ClassRangeRecord {
     /// First glyph ID in the range
-    pub fn start_glyph_id(&self) -> GlyphId {
+    pub fn start_glyph_id(&self) -> GlyphId16 {
         self.start_glyph_id.get()
     }
 
     /// Last glyph ID in the range
-    pub fn end_glyph_id(&self) -> GlyphId {
+    pub fn end_glyph_id(&self) -> GlyphId16 {
         self.end_glyph_id.get()
     }
 
@@ -1459,7 +1468,8 @@ impl ClassRangeRecord {
 }
 
 impl FixedSize for ClassRangeRecord {
-    const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + GlyphId::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
+    const RAW_BYTE_LEN: usize =
+        GlyphId16::RAW_BYTE_LEN + GlyphId16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN;
 }
 
 #[cfg(feature = "traversal")]
@@ -1486,6 +1496,14 @@ pub enum ClassDef<'a> {
 }
 
 impl<'a> ClassDef<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — format = 1
     pub fn class_format(&self) -> u16 {
         match self {
@@ -1828,7 +1846,7 @@ impl<'a> FontRead<'a> for SequenceRule<'a> {
         let glyph_count: u16 = cursor.read()?;
         let seq_lookup_count: u16 = cursor.read()?;
         let input_sequence_byte_len = (transforms::subtract(glyph_count, 1_usize))
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(input_sequence_byte_len);
         let seq_lookup_records_byte_len = (seq_lookup_count as usize)
@@ -1859,7 +1877,7 @@ impl<'a> SequenceRule<'a> {
     }
 
     /// Array of input glyph IDs—starting with the second glyph
-    pub fn input_sequence(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn input_sequence(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.input_sequence_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -2409,6 +2427,15 @@ pub enum SequenceContext<'a> {
 }
 
 impl<'a> SequenceContext<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn format(&self) -> u16 {
         match self {
@@ -2737,17 +2764,17 @@ impl<'a> FontRead<'a> for ChainedSequenceRule<'a> {
         let mut cursor = data.cursor();
         let backtrack_glyph_count: u16 = cursor.read()?;
         let backtrack_sequence_byte_len = (backtrack_glyph_count as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(backtrack_sequence_byte_len);
         let input_glyph_count: u16 = cursor.read()?;
         let input_sequence_byte_len = (transforms::subtract(input_glyph_count, 1_usize))
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(input_sequence_byte_len);
         let lookahead_glyph_count: u16 = cursor.read()?;
         let lookahead_sequence_byte_len = (lookahead_glyph_count as usize)
-            .checked_mul(GlyphId::RAW_BYTE_LEN)
+            .checked_mul(GlyphId16::RAW_BYTE_LEN)
             .ok_or(ReadError::OutOfBounds)?;
         cursor.advance_by(lookahead_sequence_byte_len);
         let seq_lookup_count: u16 = cursor.read()?;
@@ -2775,7 +2802,7 @@ impl<'a> ChainedSequenceRule<'a> {
     }
 
     /// Array of backtrack glyph IDs
-    pub fn backtrack_sequence(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn backtrack_sequence(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.backtrack_sequence_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -2787,7 +2814,7 @@ impl<'a> ChainedSequenceRule<'a> {
     }
 
     /// Array of input glyph IDs—start with second glyph
-    pub fn input_sequence(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn input_sequence(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.input_sequence_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -2799,7 +2826,7 @@ impl<'a> ChainedSequenceRule<'a> {
     }
 
     /// Array of lookahead glyph IDs
-    pub fn lookahead_sequence(&self) -> &'a [BigEndian<GlyphId>] {
+    pub fn lookahead_sequence(&self) -> &'a [BigEndian<GlyphId16>] {
         let range = self.shape.lookahead_sequence_byte_range();
         self.data.read_array(range).unwrap()
     }
@@ -3585,6 +3612,15 @@ pub enum ChainedSequenceContext<'a> {
 }
 
 impl<'a> ChainedSequenceContext<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn format(&self) -> u16 {
         match self {
@@ -3873,6 +3909,16 @@ pub enum DeviceOrVariationIndex<'a> {
     VariationIndex(VariationIndex<'a>),
 }
 
+impl<'a> DeviceOrVariationIndex<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Device(item) => item.offset_data(),
+            Self::VariationIndex(item) => item.offset_data(),
+        }
+    }
+}
+
 impl<'a> FontRead<'a> for DeviceOrVariationIndex<'a> {
     fn read(data: FontData<'a>) -> Result<Self, ReadError> {
         let format: DeltaFormat = data.read_at(4usize)?;
@@ -4192,6 +4238,17 @@ pub enum Condition<'a> {
 }
 
 impl<'a> Condition<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1AxisRange(item) => item.offset_data(),
+            Self::Format2VariableValue(item) => item.offset_data(),
+            Self::Format3And(item) => item.offset_data(),
+            Self::Format4Or(item) => item.offset_data(),
+            Self::Format5Negate(item) => item.offset_data(),
+        }
+    }
+
     /// Format, = 1
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_postscript.rs b/read-fonts/generated/generated_postscript.rs
index 3975819e..1994b1b8 100644
--- a/read-fonts/generated/generated_postscript.rs
+++ b/read-fonts/generated/generated_postscript.rs
@@ -208,6 +208,15 @@ pub enum FdSelect<'a> {
 }
 
 impl<'a> FdSelect<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+        }
+    }
+
     /// Format = 0.
     pub fn format(&self) -> u8 {
         match self {
diff --git a/read-fonts/generated/generated_stat.rs b/read-fonts/generated/generated_stat.rs
index 9e0fffec..523b2a0a 100644
--- a/read-fonts/generated/generated_stat.rs
+++ b/read-fonts/generated/generated_stat.rs
@@ -348,6 +348,16 @@ pub enum AxisValue<'a> {
 }
 
 impl<'a> AxisValue<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — set to 1.
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_variations.rs b/read-fonts/generated/generated_variations.rs
index ee983ec3..fab580b7 100644
--- a/read-fonts/generated/generated_variations.rs
+++ b/read-fonts/generated/generated_variations.rs
@@ -398,6 +398,14 @@ pub enum DeltaSetIndexMap<'a> {
 }
 
 impl<'a> DeltaSetIndexMap<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format1(item) => item.offset_data(),
+        }
+    }
+
     /// DeltaSetIndexMap format: set to 0.
     pub fn format(&self) -> u8 {
         match self {
diff --git a/read-fonts/src/read.rs b/read-fonts/src/read.rs
index e8cfc03f..e71a4455 100644
--- a/read-fonts/src/read.rs
+++ b/read-fonts/src/read.rs
@@ -111,7 +111,6 @@ pub enum ReadError {
     TableIsMissing(Tag),
     MetricIsMissing(Tag),
     MalformedData(&'static str),
-    BigGlyphIdsNotSupported(u32),
 }
 
 impl std::fmt::Display for ReadError {
@@ -132,9 +131,6 @@ impl std::fmt::Display for ReadError {
             ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
             ReadError::MetricIsMissing(tag) => write!(f, "the {tag} metric is missing"),
             ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
-            ReadError::BigGlyphIdsNotSupported(gid) => {
-                write!(f, "{gid} is too big! Implement fontations#784")
-            }
         }
     }
 }
diff --git a/read-fonts/src/tables/aat.rs b/read-fonts/src/tables/aat.rs
index 6a5729fa..cbb854d7 100644
--- a/read-fonts/src/tables/aat.rs
+++ b/read-fonts/src/tables/aat.rs
@@ -235,20 +235,20 @@ impl LookupValue for u32 {
     }
 }
 
-impl LookupValue for GlyphId {
+impl LookupValue for GlyphId16 {
     fn from_u16(v: u16) -> Self {
-        GlyphId::from(v)
+        GlyphId16::from(v)
     }
 
     fn from_u32(v: u32) -> Self {
         // intentionally truncates
-        GlyphId::from(v as u16)
+        GlyphId16::from(v as u16)
     }
 }
 
 pub type LookupU16<'a> = TypedLookup<'a, u16>;
 pub type LookupU32<'a> = TypedLookup<'a, u32>;
-pub type LookupGlyphId<'a> = TypedLookup<'a, GlyphId>;
+pub type LookupGlyphId<'a> = TypedLookup<'a, GlyphId16>;
 
 /// Empty data type for a state table entry with no payload.
 ///
@@ -301,7 +301,7 @@ pub struct StateTable<'a> {
 
 impl<'a> StateTable<'a> {
     /// Returns the class table entry for the given glyph identifier.
-    pub fn class(&self, glyph_id: GlyphId) -> Result<u8, ReadError> {
+    pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> {
         let glyph_id = glyph_id.to_u16();
         if glyph_id == 0xFFFF {
             return Ok(class::DELETED_GLYPH);
@@ -380,7 +380,7 @@ pub struct ExtendedStateTable<'a, T = ()> {
 
 impl<'a, T: bytemuck::AnyBitPattern + FixedSize> ExtendedStateTable<'a, T> {
     /// Returns the class table entry for the given glyph identifier.
-    pub fn class(&self, glyph_id: GlyphId) -> Result<u16, ReadError> {
+    pub fn class(&self, glyph_id: GlyphId16) -> Result<u16, ReadError> {
         let glyph_id = glyph_id.to_u16();
         if glyph_id == 0xFFFF {
             return Ok(class::DELETED_GLYPH as u16);
@@ -642,7 +642,7 @@ mod tests {
         let table = ExtendedStateTable::<ContextualData>::read(buf.font_data()).unwrap();
         // check class lookups
         let [class_50, class_80, class_201] =
-            [50, 80, 201].map(|gid| table.class(GlyphId::from(gid)).unwrap());
+            [50, 80, 201].map(|gid| table.class(GlyphId16::from(gid)).unwrap());
         assert_eq!(class_50, 4);
         assert_eq!(class_80, 5);
         assert_eq!(class_201, 4);
@@ -715,7 +715,7 @@ mod tests {
         let table = StateTable::read(buf.font_data()).unwrap();
         // check class lookups
         for i in 0..4u8 {
-            assert_eq!(table.class(GlyphId::from(i as u16 + 3)).unwrap(), i + 1);
+            assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
         }
         // (state, class) -> (new_state, flags)
         let cases = [
diff --git a/read-fonts/src/tables/bitmap.rs b/read-fonts/src/tables/bitmap.rs
index c447d99c..0b865a74 100644
--- a/read-fonts/src/tables/bitmap.rs
+++ b/read-fonts/src/tables/bitmap.rs
@@ -29,7 +29,7 @@ impl BitmapSize {
             }
             // glyph index relative to the first glyph in the subtable
             let glyph_ix =
-                glyph_id.to_u16() as usize - subtable.first_glyph_index.to_u16() as usize;
+                glyph_id.to_u32() as usize - subtable.first_glyph_index.to_u32() as usize;
             match &subtable.kind {
                 IndexSubtable::Format1(st) => {
                     location.format = st.image_format();
@@ -77,11 +77,11 @@ impl BitmapSize {
                 IndexSubtable::Format4(st) => {
                     location.format = st.image_format();
                     let array = st.glyph_array();
-                    let array_ix = match array.binary_search_by(|x| x.glyph_id().cmp(&glyph_id)) {
+                    let array_ix = match array
+                        .binary_search_by(|x| x.glyph_id().to_u32().cmp(&glyph_id.to_u32()))
+                    {
                         Ok(ix) => ix,
-                        _ => {
-                            return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u16() as u32))
-                        }
+                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
                     };
                     let start = array[array_ix].sbit_offset() as usize;
                     let end = array
@@ -97,11 +97,11 @@ impl BitmapSize {
                 IndexSubtable::Format5(st) => {
                     location.format = st.image_format();
                     let array = st.glyph_array();
-                    let array_ix = match array.binary_search_by(|gid| gid.get().cmp(&glyph_id)) {
+                    let array_ix = match array
+                        .binary_search_by(|gid| gid.get().to_u32().cmp(&glyph_id.to_u32()))
+                    {
                         Ok(ix) => ix,
-                        _ => {
-                            return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u16() as u32))
-                        }
+                        _ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
                     };
                     let data_size = st.image_size() as usize;
                     location.data_size = data_size;
@@ -140,8 +140,8 @@ impl BitmapSize {
 }
 
 struct BitmapSizeSubtable<'a> {
-    pub first_glyph_index: GlyphId,
-    pub last_glyph_index: GlyphId,
+    pub first_glyph_index: GlyphId16,
+    pub last_glyph_index: GlyphId16,
     pub kind: IndexSubtable<'a>,
 }
 
diff --git a/read-fonts/src/tables/cbdt.rs b/read-fonts/src/tables/cbdt.rs
index 55783aef..b9c53190 100644
--- a/read-fonts/src/tables/cbdt.rs
+++ b/read-fonts/src/tables/cbdt.rs
@@ -13,7 +13,10 @@ impl<'a> Cbdt<'a> {
 #[cfg(test)]
 mod tests {
     use super::super::bitmap::{BitmapDataFormat, SmallGlyphMetrics};
-    use crate::{types::GlyphId, FontRef, TableProvider};
+    use crate::{
+        types::{GlyphId, GlyphId16},
+        FontRef, TableProvider,
+    };
 
     #[test]
     fn read_cblc_1_cbdt_17() {
@@ -29,8 +32,8 @@ mod tests {
                 && size.vert.ascender() == 101
                 && size.vert.descender() == -27
                 && size.vert.width_max() == 136
-                && size.start_glyph_index() == GlyphId::new(4)
-                && size.end_glyph_index() == GlyphId::new(4)
+                && size.start_glyph_index() == GlyphId16::new(4)
+                && size.end_glyph_index() == GlyphId16::new(4)
                 && size.ppem_x() == 109
                 && size.ppem_y() == 109
                 && size.bit_depth() == 32
diff --git a/read-fonts/src/tables/cmap.rs b/read-fonts/src/tables/cmap.rs
index 7ee170b3..8b67591b 100644
--- a/read-fonts/src/tables/cmap.rs
+++ b/read-fonts/src/tables/cmap.rs
@@ -99,12 +99,12 @@ impl<'a> Cmap4<'a> {
         let delta = deltas.get(index)?.get() as i32;
         let range_offset = range_offsets.get(index)?.get() as usize;
         if range_offset == 0 {
-            return Some(GlyphId::new((codepoint as i32 + delta) as u16));
+            return Some(GlyphId::from((codepoint as i32 + delta) as u16));
         }
         let mut offset = range_offset / 2 + (codepoint - start_code) as usize;
         offset = offset.saturating_sub(range_offsets.len() - index);
         let gid = self.glyph_id_array().get(offset)?.get();
-        (gid != 0).then_some(GlyphId::new((gid as i32 + delta) as u16))
+        (gid != 0).then_some(GlyphId::from((gid as i32 + delta) as u16))
     }
 
     /// Returns the [start_code, end_code] range at the given index.
@@ -209,7 +209,7 @@ impl<'a> Cmap12<'a> {
         start_char_code: u32,
         start_glyph_id: u32,
     ) -> GlyphId {
-        GlyphId::new(start_glyph_id.wrapping_add(codepoint.wrapping_sub(start_char_code)) as u16)
+        GlyphId::new(start_glyph_id.wrapping_add(codepoint.wrapping_sub(start_char_code)))
     }
 
     /// Returns the codepoint range and start glyph id for the group
@@ -280,7 +280,7 @@ impl<'a> Iterator for Cmap12Iter<'a> {
                 // that the start code of next group is at least
                 // current_end + 1.
                 // This ensures we only ever generate a maximum of
-                // 0..=char::MAX results.
+                // char::MAX + 1 results.
                 if next_group.range.start() <= group.range.end() {
                     next_group.range = *group.range.end() + 1..=*next_group.range.end();
                 }
@@ -341,7 +341,7 @@ impl<'a> Cmap14<'a> {
                 map_codepoint.cmp(&codepoint)
             })
             .ok()?;
-        Some(MapVariant::Variant(GlyphId::new(
+        Some(MapVariant::Variant(GlyphId::from(
             mapping.get(ix)?.glyph_id(),
         )))
     }
@@ -391,7 +391,7 @@ impl<'a> Cmap14<'a> {
                         .uvs_mapping()
                         .iter()
                         .filter(|m| unicodes.contains(m.unicode_value().to_u32()))
-                        .map(|m| GlyphId::new(m.glyph_id())),
+                        .map(|m| m.glyph_id().into()),
                 );
             }
         }
@@ -436,7 +436,7 @@ impl<'a> Iterator for Cmap14Iter<'a> {
             }
             if let Some(non_default_uvs) = self.non_default_uvs.as_mut() {
                 if let Some((codepoint, variant)) = non_default_uvs.next() {
-                    return Some((codepoint, selector, MapVariant::Variant(variant)));
+                    return Some((codepoint, selector, MapVariant::Variant(variant.into())));
                 }
             }
             self.cur_selector_ix += 1;
@@ -499,12 +499,12 @@ impl<'a> NonDefaultUvsIter<'a> {
 }
 
 impl<'a> Iterator for NonDefaultUvsIter<'a> {
-    type Item = (u32, GlyphId);
+    type Item = (u32, GlyphId16);
 
     fn next(&mut self) -> Option<Self::Item> {
         let mapping = self.iter.next()?;
         let codepoint: u32 = mapping.unicode_value().into();
-        let glyph_id = GlyphId::new(mapping.glyph_id());
+        let glyph_id = GlyphId16::new(mapping.glyph_id());
         Some((codepoint, glyph_id))
     }
 }
@@ -677,7 +677,7 @@ mod tests {
         let cmap = font.cmap().unwrap();
         // ranges: [SequentialMapGroup { start_char_code: 170, end_char_code: 1330926671, start_glyph_id: 328960 }]
         let cmap12 = find_cmap12(&cmap).unwrap();
-        assert!(cmap12.iter().count() <= char::MAX as usize);
+        assert!(cmap12.iter().count() <= char::MAX as usize + 1);
     }
 
     #[test]
@@ -700,8 +700,8 @@ mod tests {
             .collect::<Vec<_>>();
         // These groups overlap and extend to the whole u32 range
         assert_eq!(ranges, &[(0, 16777215), (255, u32::MAX)]);
-        // But we produce fewer than char::MAX results (some codepoints map to 0)
-        assert!(cmap12.iter().count() < char::MAX as usize);
+        // But we produce at most char::MAX + 1 results
+        assert!(cmap12.iter().count() <= char::MAX as usize + 1);
     }
 
     #[test]
diff --git a/read-fonts/src/tables/colr.rs b/read-fonts/src/tables/colr.rs
index fb39e412..11f208e8 100644
--- a/read-fonts/src/tables/colr.rs
+++ b/read-fonts/src/tables/colr.rs
@@ -15,6 +15,9 @@ impl<'a> Colr<'a> {
     /// and palette color indices.
     pub fn v0_base_glyph(&self, glyph_id: GlyphId) -> Result<Option<Range<usize>>, ReadError> {
         let records = self.base_glyph_records().ok_or(ReadError::NullOffset)??;
+        let Ok(glyph_id) = glyph_id.try_into() else {
+            return Ok(None);
+        };
         let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
             Ok(ix) => &records[ix],
             _ => return Ok(None),
@@ -28,7 +31,7 @@ impl<'a> Colr<'a> {
     ///
     /// The layer is represented by a tuple containing the glyph identifier of
     /// the associated outline and the palette color index.
-    pub fn v0_layer(&self, index: usize) -> Result<(GlyphId, u16), ReadError> {
+    pub fn v0_layer(&self, index: usize) -> Result<(GlyphId16, u16), ReadError> {
         let layers = self.layer_records().ok_or(ReadError::NullOffset)??;
         let layer = layers.get(index).ok_or(ReadError::OutOfBounds)?;
         Ok((layer.glyph_id(), layer.palette_index()))
@@ -42,6 +45,9 @@ impl<'a> Colr<'a> {
         &self,
         glyph_id: GlyphId,
     ) -> Result<Option<(Paint<'a>, PaintId)>, ReadError> {
+        let Ok(glyph_id) = glyph_id.try_into() else {
+            return Ok(None);
+        };
         let list = self.base_glyph_list().ok_or(ReadError::NullOffset)??;
         let records = list.base_glyph_paint_records();
         let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
@@ -75,7 +81,10 @@ impl<'a> Colr<'a> {
 
     /// Returns the COLRv1 clip box for the given glyph identifier.
     pub fn v1_clip_box(&self, glyph_id: GlyphId) -> Result<Option<ClipBox<'a>>, ReadError> {
-        use std::cmp::Ordering;
+        use core::cmp::Ordering;
+        let Ok(glyph_id): Result<GlyphId16, _> = glyph_id.try_into() else {
+            return Ok(None);
+        };
         let list = self.clip_list().ok_or(ReadError::NullOffset)??;
         let clips = list.clips();
         let clip = match clips.binary_search_by(|clip| {
diff --git a/read-fonts/src/tables/ebdt.rs b/read-fonts/src/tables/ebdt.rs
index b0f643b9..8052bb34 100644
--- a/read-fonts/src/tables/ebdt.rs
+++ b/read-fonts/src/tables/ebdt.rs
@@ -16,7 +16,10 @@ mod tests {
         BigGlyphMetrics, BitmapContent, BitmapData, BitmapDataFormat, BitmapMetrics,
         SmallGlyphMetrics,
     };
-    use crate::{types::GlyphId, FontRef, TableProvider};
+    use crate::{
+        types::{GlyphId, GlyphId16},
+        FontRef, TableProvider,
+    };
 
     impl<'a> BitmapContent<'a> {
         pub(crate) fn extract_data(&self) -> (BitmapDataFormat, &'a [u8]) {
@@ -51,8 +54,8 @@ mod tests {
                 && size.hori.min_after_bl() == -2
                 && size.vert.ascender() == 6
                 && size.vert.descender() == 2
-                && size.start_glyph_index() == GlyphId::new(1)
-                && size.end_glyph_index() == GlyphId::new(2)
+                && size.start_glyph_index() == GlyphId16::new(1)
+                && size.end_glyph_index() == GlyphId16::new(2)
                 && size.ppem_x() == 7
                 && size.ppem_y() == 7
                 && size.bit_depth() == 1
@@ -108,8 +111,8 @@ mod tests {
                 && size.hori.min_after_bl() == -5
                 && size.vert.ascender() == 12
                 && size.vert.descender() == 5
-                && size.start_glyph_index() == GlyphId::new(3)
-                && size.end_glyph_index() == GlyphId::new(3)
+                && size.start_glyph_index() == GlyphId16::new(3)
+                && size.end_glyph_index() == GlyphId16::new(3)
                 && size.ppem_x() == 15
                 && size.ppem_y() == 15
                 && size.bit_depth() == 1
diff --git a/read-fonts/src/tables/glyf.rs b/read-fonts/src/tables/glyf.rs
index 9cab494c..82e102e2 100644
--- a/read-fonts/src/tables/glyf.rs
+++ b/read-fonts/src/tables/glyf.rs
@@ -476,7 +476,7 @@ pub struct Component {
     /// Component flags.
     pub flags: CompositeGlyphFlags,
     /// Glyph identifier.
-    pub glyph: GlyphId,
+    pub glyph: GlyphId16,
     /// Anchor for component placement.
     pub anchor: Anchor,
     /// Component transformation matrix.
@@ -504,7 +504,7 @@ impl<'a> CompositeGlyph<'a> {
     /// component in the composite glyph.
     pub fn component_glyphs_and_flags(
         &self,
-    ) -> impl Iterator<Item = (GlyphId, CompositeGlyphFlags)> + 'a + Clone {
+    ) -> impl Iterator<Item = (GlyphId16, CompositeGlyphFlags)> + 'a + Clone {
         ComponentGlyphIdFlagsIter {
             cur_flags: CompositeGlyphFlags::empty(),
             done: false,
@@ -561,7 +561,7 @@ impl Iterator for ComponentIter<'_> {
         }
         let flags: CompositeGlyphFlags = self.cursor.read().ok()?;
         self.cur_flags = flags;
-        let glyph = self.cursor.read::<GlyphId>().ok()?;
+        let glyph = self.cursor.read::<GlyphId16>().ok()?;
         let args_are_words = flags.contains(CompositeGlyphFlags::ARG_1_AND_2_ARE_WORDS);
         let args_are_xy_values = flags.contains(CompositeGlyphFlags::ARGS_ARE_XY_VALUES);
         let anchor = match (args_are_xy_values, args_are_words) {
@@ -618,7 +618,7 @@ struct ComponentGlyphIdFlagsIter<'a> {
 }
 
 impl Iterator for ComponentGlyphIdFlagsIter<'_> {
-    type Item = (GlyphId, CompositeGlyphFlags);
+    type Item = (GlyphId16, CompositeGlyphFlags);
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.done {
@@ -626,7 +626,7 @@ impl Iterator for ComponentGlyphIdFlagsIter<'_> {
         }
         let flags: CompositeGlyphFlags = self.cursor.read().ok()?;
         self.cur_flags = flags;
-        let glyph = self.cursor.read::<GlyphId>().ok()?;
+        let glyph = self.cursor.read::<GlyphId16>().ok()?;
         let args_are_words = flags.contains(CompositeGlyphFlags::ARG_1_AND_2_ARE_WORDS);
         if args_are_words {
             self.cursor.advance_by(4);
@@ -1234,7 +1234,7 @@ mod tests {
         let font = FontRef::new(font_data).unwrap();
         let loca = font.loca(None).unwrap();
         let glyf = font.glyf().unwrap();
-        let glyph_count = font.maxp().unwrap().num_glyphs();
+        let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
         (0..glyph_count).map(move |gid| loca.get_glyf(GlyphId::new(gid), &glyf).unwrap())
     }
 
diff --git a/read-fonts/src/tables/gsub/closure.rs b/read-fonts/src/tables/gsub/closure.rs
index f02431af..55dfd246 100644
--- a/read-fonts/src/tables/gsub/closure.rs
+++ b/read-fonts/src/tables/gsub/closure.rs
@@ -5,7 +5,7 @@
 
 use std::collections::HashSet;
 
-use font_types::GlyphId;
+use font_types::GlyphId16;
 
 use crate::{
     tables::layout::{
@@ -25,15 +25,15 @@ use super::{
 /// A trait for tables which participate in closure
 pub(crate) trait GlyphClosure {
     /// Update the set of glyphs with any glyphs reachable via substitution.
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError>;
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError>;
 }
 
 impl<'a> Gsub<'a> {
     /// Return the set of glyphs reachable from the input set via any substituion.
     pub fn closure_glyphs(
         &self,
-        mut glyphs: HashSet<GlyphId>,
-    ) -> Result<HashSet<GlyphId>, ReadError> {
+        mut glyphs: HashSet<GlyphId16>,
+    ) -> Result<HashSet<GlyphId16>, ReadError> {
         // we need to do this iteratively, since any glyph found in one pass
         // over the lookups could also be the target of substitutions.
 
@@ -52,7 +52,7 @@ impl<'a> Gsub<'a> {
         Ok(glyphs)
     }
 
-    fn closure_glyphs_once(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn closure_glyphs_once(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         let lookups_to_use = self.find_reachable_lookups(glyphs)?;
         let lookup_list = self.lookup_list()?;
         for (i, lookup) in lookup_list.lookups().iter().enumerate() {
@@ -65,7 +65,10 @@ impl<'a> Gsub<'a> {
         Ok(())
     }
 
-    fn find_reachable_lookups(&self, glyphs: &HashSet<GlyphId>) -> Result<HashSet<u16>, ReadError> {
+    fn find_reachable_lookups(
+        &self,
+        glyphs: &HashSet<GlyphId16>,
+    ) -> Result<HashSet<u16>, ReadError> {
         let feature_list = self.feature_list()?;
         let lookup_list = self.lookup_list()?;
         // first we want to get the lookups that are directly referenced by a feature
@@ -120,7 +123,7 @@ impl<'a> Gsub<'a> {
 }
 
 impl<'a> GlyphClosure for SubstitutionSubtables<'a> {
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         match self {
             SubstitutionSubtables::Single(tables) => tables.add_reachable_glyphs(glyphs),
             SubstitutionSubtables::Multiple(tables) => tables.add_reachable_glyphs(glyphs),
@@ -135,14 +138,14 @@ impl<'a> GlyphClosure for SubstitutionSubtables<'a> {
 impl<'a, T: FontRead<'a> + GlyphClosure + 'a, Ext: ExtensionLookup<'a, T> + 'a> GlyphClosure
     for Subtables<'a, T, Ext>
 {
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         self.iter()
             .try_for_each(|t| t?.add_reachable_glyphs(glyphs))
     }
 }
 
 impl<'a> GlyphClosure for SingleSubst<'a> {
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         for (target, replacement) in self.iter_subs()? {
             if glyphs.contains(&target) {
                 glyphs.insert(replacement);
@@ -153,7 +156,7 @@ impl<'a> GlyphClosure for SingleSubst<'a> {
 }
 
 impl<'a> SingleSubst<'a> {
-    fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId, GlyphId)> + '_, ReadError> {
+    fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
         let (left, right) = match self {
             SingleSubst::Format1(t) => (Some(t.iter_subs()?), None),
             SingleSubst::Format2(t) => (None, Some(t.iter_subs()?)),
@@ -166,19 +169,19 @@ impl<'a> SingleSubst<'a> {
 }
 
 impl<'a> SingleSubstFormat1<'a> {
-    fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId, GlyphId)> + '_, ReadError> {
+    fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
         let delta = self.delta_glyph_id();
         let coverage = self.coverage()?;
         Ok(coverage.iter().filter_map(move |gid| {
             let raw = (gid.to_u16() as i32).checked_add(delta as i32);
             let raw = raw.and_then(|raw| u16::try_from(raw).ok())?;
-            Some((gid, GlyphId::new(raw)))
+            Some((gid, GlyphId16::new(raw)))
         }))
     }
 }
 
 impl<'a> SingleSubstFormat2<'a> {
-    fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId, GlyphId)> + '_, ReadError> {
+    fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
         let coverage = self.coverage()?;
         let subs = self.substitute_glyph_ids();
         Ok(coverage.iter().zip(subs.iter().map(|id| id.get())))
@@ -186,7 +189,7 @@ impl<'a> SingleSubstFormat2<'a> {
 }
 
 impl<'a> GlyphClosure for MultipleSubstFormat1<'a> {
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         let coverage = self.coverage()?;
         let sequences = self.sequences();
         for (gid, replacements) in coverage.iter().zip(sequences.iter()) {
@@ -205,7 +208,7 @@ impl<'a> GlyphClosure for MultipleSubstFormat1<'a> {
 }
 
 impl<'a> GlyphClosure for AlternateSubstFormat1<'a> {
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         let coverage = self.coverage()?;
         let alts = self.alternate_sets();
         for (gid, alts) in coverage.iter().zip(alts.iter()) {
@@ -219,7 +222,7 @@ impl<'a> GlyphClosure for AlternateSubstFormat1<'a> {
 }
 
 impl<'a> GlyphClosure for LigatureSubstFormat1<'a> {
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         let coverage = self.coverage()?;
         let ligs = self.ligature_sets();
         for (gid, lig_set) in coverage.iter().zip(ligs.iter()) {
@@ -242,7 +245,7 @@ impl<'a> GlyphClosure for LigatureSubstFormat1<'a> {
 }
 
 impl GlyphClosure for ReverseChainSingleSubstFormat1<'_> {
-    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId>) -> Result<(), ReadError> {
+    fn add_reachable_glyphs(&self, glyphs: &mut HashSet<GlyphId16>) -> Result<(), ReadError> {
         for coverage in self
             .backtrack_coverages()
             .iter()
@@ -266,7 +269,7 @@ impl GlyphClosure for ReverseChainSingleSubstFormat1<'_> {
 impl SequenceContext<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         match self {
@@ -280,7 +283,7 @@ impl SequenceContext<'_> {
 impl SequenceContextFormat1<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         let coverage = self.coverage()?;
@@ -311,7 +314,7 @@ impl SequenceContextFormat1<'_> {
 impl SequenceContextFormat2<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         let classdef = self.class_def()?;
@@ -344,7 +347,7 @@ impl SequenceContextFormat2<'_> {
 impl SequenceContextFormat3<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         for coverage in self.coverages().iter() {
@@ -364,7 +367,7 @@ impl SequenceContextFormat3<'_> {
 impl ChainedSequenceContext<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         match self {
@@ -378,7 +381,7 @@ impl ChainedSequenceContext<'_> {
 impl ChainedSequenceContextFormat1<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         let coverage = self.coverage()?;
@@ -411,7 +414,7 @@ impl ChainedSequenceContextFormat1<'_> {
 impl ChainedSequenceContextFormat2<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         let input = self.input_class_def()?;
@@ -457,7 +460,7 @@ impl ChainedSequenceContextFormat2<'_> {
 impl ChainedSequenceContextFormat3<'_> {
     fn add_reachable_lookups(
         &self,
-        glyphs: &HashSet<GlyphId>,
+        glyphs: &HashSet<GlyphId16>,
         lookups: &mut HashSet<u16>,
     ) -> Result<(), ReadError> {
         for coverage in self
@@ -479,7 +482,7 @@ impl ChainedSequenceContextFormat3<'_> {
     }
 }
 
-fn make_class_set(glyphs: &HashSet<GlyphId>, classdef: &ClassDef) -> HashSet<u16> {
+fn make_class_set(glyphs: &HashSet<GlyphId16>, classdef: &ClassDef) -> HashSet<u16> {
     glyphs.iter().map(|gid| classdef.get(*gid)).collect()
 }
 
@@ -493,8 +496,8 @@ mod tests {
     use font_test_data::closure as test_data;
 
     struct GlyphMap {
-        to_gid: HashMap<&'static str, GlyphId>,
-        from_gid: HashMap<GlyphId, &'static str>,
+        to_gid: HashMap<&'static str, GlyphId16>,
+        from_gid: HashMap<GlyphId16, &'static str>,
     }
 
     impl GlyphMap {
@@ -504,17 +507,17 @@ mod tests {
                 .map(|line| line.trim())
                 .filter(|line| !(line.starts_with('#') || line.is_empty()))
                 .enumerate()
-                .map(|(gid, name)| (name, GlyphId::new(gid.try_into().unwrap())))
+                .map(|(gid, name)| (name, GlyphId16::new(gid.try_into().unwrap())))
                 .collect();
             let from_gid = to_gid.iter().map(|(name, gid)| (*gid, *name)).collect();
             GlyphMap { from_gid, to_gid }
         }
 
-        fn get_gid(&self, name: &str) -> Option<GlyphId> {
+        fn get_gid(&self, name: &str) -> Option<GlyphId16> {
             self.to_gid.get(name).copied()
         }
 
-        fn get_name(&self, gid: GlyphId) -> Option<&str> {
+        fn get_name(&self, gid: GlyphId16) -> Option<&str> {
             self.from_gid.get(&gid).copied()
         }
     }
@@ -524,7 +527,7 @@ mod tests {
         font.gsub().unwrap()
     }
 
-    fn compute_closure(gsub: &Gsub, glyph_map: &GlyphMap, input: &[&str]) -> HashSet<GlyphId> {
+    fn compute_closure(gsub: &Gsub, glyph_map: &GlyphMap, input: &[&str]) -> HashSet<GlyphId16> {
         let input_glyphs = input
             .iter()
             .map(|name| glyph_map.get_gid(name).unwrap())
diff --git a/read-fonts/src/tables/gvar.rs b/read-fonts/src/tables/gvar.rs
index 7a651679..06159748 100644
--- a/read-fonts/src/tables/gvar.rs
+++ b/read-fonts/src/tables/gvar.rs
@@ -57,7 +57,7 @@ impl<'a> GlyphVariationDataHeader<'a> {
 
 impl<'a> Gvar<'a> {
     fn data_for_gid(&self, gid: GlyphId) -> Result<FontData<'a>, ReadError> {
-        let start_idx = gid.to_u16() as usize;
+        let start_idx = gid.to_u32() as usize;
         let end_idx = start_idx + 1;
         let data_start = self.glyph_variation_data_array_offset();
         let start = data_start + self.glyph_variation_data_offsets().get(start_idx)?.get();
diff --git a/read-fonts/src/tables/hmtx.rs b/read-fonts/src/tables/hmtx.rs
index 4bc4d296..a4469d6b 100644
--- a/read-fonts/src/tables/hmtx.rs
+++ b/read-fonts/src/tables/hmtx.rs
@@ -16,7 +16,7 @@ impl<'a> Hmtx<'a> {
 
 pub(super) fn advance(metrics: &[LongMetric], glyph_id: GlyphId) -> Option<u16> {
     metrics
-        .get(glyph_id.to_u16() as usize)
+        .get(glyph_id.to_u32() as usize)
         .or_else(|| metrics.last())
         .map(|metric| metric.advance())
 }
@@ -26,7 +26,7 @@ pub(super) fn side_bearing(
     side_bearings: &[BigEndian<i16>],
     glyph_id: GlyphId,
 ) -> Option<i16> {
-    let ix = glyph_id.to_u16() as usize;
+    let ix = glyph_id.to_u32() as usize;
     metrics
         .get(ix)
         .map(|metric| metric.side_bearing())
diff --git a/read-fonts/src/tables/hvar.rs b/read-fonts/src/tables/hvar.rs
index 0c77dd26..03b19ee7 100644
--- a/read-fonts/src/tables/hvar.rs
+++ b/read-fonts/src/tables/hvar.rs
@@ -102,7 +102,7 @@ mod tests {
         // We expect the last 10 glyphs to have the same advance width delta as the last mapped glyph.
         // Crucially, hvar.advance_width_delta() should not return OutOfBounds for these glyphs.
         for idx in last_mapped_gid..num_glyphs {
-            let gid = GlyphId::new(idx);
+            let gid = GlyphId::new(idx as _);
             assert_eq!(
                 hvar.advance_width_delta(gid, &[F2Dot14::from_f32(1.0)])
                     .unwrap(),
diff --git a/read-fonts/src/tables/layout.rs b/read-fonts/src/tables/layout.rs
index 8279b637..9b003e58 100644
--- a/read-fonts/src/tables/layout.rs
+++ b/read-fonts/src/tables/layout.rs
@@ -145,7 +145,7 @@ impl FeatureTableSubstitutionRecord {
 }
 
 impl<'a> CoverageTable<'a> {
-    pub fn iter(&self) -> impl Iterator<Item = GlyphId> + 'a {
+    pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + 'a {
         // all one expression so that we have a single return type
         let (iter1, iter2) = match self {
             CoverageTable::Format1(t) => (Some(t.glyph_array().iter().map(|g| g.get())), None),
@@ -162,7 +162,7 @@ impl<'a> CoverageTable<'a> {
     }
 
     /// If this glyph is in the coverage table, returns its index
-    pub fn get(&self, gid: GlyphId) -> Option<u16> {
+    pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
         match self {
             CoverageTable::Format1(sub) => sub.get(gid),
             CoverageTable::Format2(sub) => sub.get(gid),
@@ -172,8 +172,9 @@ impl<'a> CoverageTable<'a> {
 
 impl CoverageFormat1<'_> {
     /// If this glyph is in the coverage table, returns its index
-    pub fn get(&self, gid: GlyphId) -> Option<u16> {
-        let be_glyph: BigEndian<GlyphId> = gid.into();
+    pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
+        let gid16: GlyphId16 = gid.into().try_into().ok()?;
+        let be_glyph: BigEndian<GlyphId16> = gid16.into();
         self.glyph_array()
             .binary_search(&be_glyph)
             .ok()
@@ -183,7 +184,8 @@ impl CoverageFormat1<'_> {
 
 impl CoverageFormat2<'_> {
     /// If this glyph is in the coverage table, returns its index
-    pub fn get(&self, gid: GlyphId) -> Option<u16> {
+    pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
+        let gid: GlyphId16 = gid.into().try_into().ok()?;
         self.range_records()
             .binary_search_by(|rec| {
                 if rec.end_glyph_id() < gid {
@@ -203,8 +205,8 @@ impl CoverageFormat2<'_> {
 }
 
 impl RangeRecord {
-    fn iter(&self) -> impl Iterator<Item = GlyphId> + '_ {
-        (self.start_glyph_id().to_u16()..=self.end_glyph_id().to_u16()).map(GlyphId::new)
+    fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
+        (self.start_glyph_id().to_u16()..=self.end_glyph_id().to_u16()).map(GlyphId16::new)
     }
 }
 
@@ -234,7 +236,7 @@ impl From<DeltaFormat> for i64 {
 
 impl<'a> ClassDefFormat1<'a> {
     /// Get the class for this glyph id
-    pub fn get(&self, gid: GlyphId) -> u16 {
+    pub fn get(&self, gid: GlyphId16) -> u16 {
         if gid < self.start_glyph_id() {
             return 0;
         }
@@ -246,21 +248,21 @@ impl<'a> ClassDefFormat1<'a> {
     }
 
     /// Iterate over each glyph and its class.
-    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, u16)> + 'a {
+    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + 'a {
         let start = self.start_glyph_id();
         self.class_value_array()
             .iter()
             .enumerate()
             .map(move |(i, val)| {
                 let gid = start.to_u16().saturating_add(i as u16);
-                (GlyphId::new(gid), val.get())
+                (GlyphId16::new(gid), val.get())
             })
     }
 }
 
 impl<'a> ClassDefFormat2<'a> {
     /// Get the class for this glyph id
-    pub fn get(&self, gid: GlyphId) -> u16 {
+    pub fn get(&self, gid: GlyphId16) -> u16 {
         let records = self.class_range_records();
         let ix = match records.binary_search_by(|rec| rec.start_glyph_id().cmp(&gid)) {
             Ok(ix) => ix,
@@ -275,18 +277,18 @@ impl<'a> ClassDefFormat2<'a> {
     }
 
     /// Iterate over each glyph and its class.
-    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, u16)> + 'a {
+    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + 'a {
         self.class_range_records().iter().flat_map(|range| {
             let start = range.start_glyph_id().to_u16();
             let end = range.end_glyph_id().to_u16();
-            (start..=end).map(|gid| (GlyphId::new(gid), range.class()))
+            (start..=end).map(|gid| (GlyphId16::new(gid), range.class()))
         })
     }
 }
 
 impl ClassDef<'_> {
     /// Get the class for this glyph id
-    pub fn get(&self, gid: GlyphId) -> u16 {
+    pub fn get(&self, gid: GlyphId16) -> u16 {
         match self {
             ClassDef::Format1(table) => table.get(gid),
             ClassDef::Format2(table) => table.get(gid),
@@ -296,7 +298,7 @@ impl ClassDef<'_> {
     /// Iterate over each glyph and its class.
     ///
     /// This will not include class 0 unless it has been explicitly assigned.
-    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, u16)> + '_ {
+    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
         let (one, two) = match self {
             ClassDef::Format1(inner) => (Some(inner.iter()), None),
             ClassDef::Format2(inner) => (None, Some(inner.iter())),
@@ -414,7 +416,7 @@ mod tests {
             (661, 2),
         ];
         for (gid, class) in gid_class_pairs {
-            assert_eq!(classdef.get(GlyphId::new(gid)), class);
+            assert_eq!(classdef.get(GlyphId16::new(gid)), class);
         }
         for (gid, class) in classdef.iter() {
             assert_eq!(classdef.get(gid), class);
diff --git a/read-fonts/src/tables/loca.rs b/read-fonts/src/tables/loca.rs
index a8561330..3df3721a 100644
--- a/read-fonts/src/tables/loca.rs
+++ b/read-fonts/src/tables/loca.rs
@@ -54,7 +54,7 @@ impl<'a> Loca<'a> {
         gid: GlyphId,
         glyf: &super::glyf::Glyf<'a>,
     ) -> Result<Option<super::glyf::Glyph<'a>>, ReadError> {
-        let idx = gid.to_u16() as usize;
+        let idx = gid.to_u32() as usize;
         let start = self.get_raw(idx).ok_or(ReadError::OutOfBounds)?;
         let end = self.get_raw(idx + 1).ok_or(ReadError::OutOfBounds)?;
         if start == end {
diff --git a/read-fonts/src/tables/post.rs b/read-fonts/src/tables/post.rs
index fe4f465e..7f1cd4f4 100644
--- a/read-fonts/src/tables/post.rs
+++ b/read-fonts/src/tables/post.rs
@@ -12,7 +12,7 @@ impl<'a> Post<'a> {
         }
     }
 
-    pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&str> {
+    pub fn glyph_name(&self, glyph_id: GlyphId16) -> Option<&str> {
         let glyph_id = glyph_id.to_u16() as usize;
         match self.version() {
             Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.get(glyph_id).copied(),
@@ -132,11 +132,11 @@ mod tests {
         let table = Post::read(test_data::SIMPLE.into()).unwrap();
         assert_eq!(table.version(), Version16Dot16::VERSION_2_0);
         assert_eq!(table.underline_position(), FWord::new(-75));
-        assert_eq!(table.glyph_name(GlyphId::new(1)), Some(".notdef"));
-        assert_eq!(table.glyph_name(GlyphId::new(2)), Some("space"));
-        assert_eq!(table.glyph_name(GlyphId::new(7)), Some("hello"));
-        assert_eq!(table.glyph_name(GlyphId::new(8)), Some("hi"));
-        assert_eq!(table.glyph_name(GlyphId::new(9)), Some("hola"));
+        assert_eq!(table.glyph_name(GlyphId16::new(1)), Some(".notdef"));
+        assert_eq!(table.glyph_name(GlyphId16::new(2)), Some("space"));
+        assert_eq!(table.glyph_name(GlyphId16::new(7)), Some("hello"));
+        assert_eq!(table.glyph_name(GlyphId16::new(8)), Some("hi"));
+        assert_eq!(table.glyph_name(GlyphId16::new(9)), Some("hola"));
     }
 
     #[test]
diff --git a/read-fonts/src/tables/postscript/fd_select.rs b/read-fonts/src/tables/postscript/fd_select.rs
index c1ac5cb0..b960e1c5 100644
--- a/read-fonts/src/tables/postscript/fd_select.rs
+++ b/read-fonts/src/tables/postscript/fd_select.rs
@@ -11,13 +11,13 @@ impl<'a> FdSelect<'a> {
             // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-11-fdselect-format-0>
             Self::Format0(fds) => fds
                 .fds()
-                .get(glyph_id.to_u16() as usize)
+                .get(glyph_id.to_u32() as usize)
                 .map(|fd| *fd as u16),
             // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-12-fdselect-format-3>
             Self::Format3(fds) => {
                 let ranges = fds.ranges();
-                let gid = glyph_id.to_u16();
-                let ix = match ranges.binary_search_by(|range| range.first().cmp(&gid)) {
+                let gid = glyph_id.to_u32();
+                let ix = match ranges.binary_search_by(|range| (range.first() as u32).cmp(&gid)) {
                     Ok(ix) => ix,
                     Err(ix) => ix.saturating_sub(1),
                 };
@@ -26,7 +26,7 @@ impl<'a> FdSelect<'a> {
             // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-14-fdselect-format-4>
             Self::Format4(fds) => {
                 let ranges = fds.ranges();
-                let gid = glyph_id.to_u16() as u32;
+                let gid = glyph_id.to_u32();
                 let ix = match ranges.binary_search_by(|range| range.first().cmp(&gid)) {
                     Ok(ix) => ix,
                     Err(ix) => ix.saturating_sub(1),
@@ -59,7 +59,7 @@ mod tests {
             for (range, font_index) in map {
                 for gid in range.clone() {
                     assert_eq!(
-                        fd_select.font_index(GlyphId::new(gid)).unwrap() as u8,
+                        fd_select.font_index(GlyphId::from(gid)).unwrap() as u8,
                         *font_index
                     )
                 }
diff --git a/read-fonts/src/tables/sbix.rs b/read-fonts/src/tables/sbix.rs
index 3c2c9b64..d91ece94 100644
--- a/read-fonts/src/tables/sbix.rs
+++ b/read-fonts/src/tables/sbix.rs
@@ -5,7 +5,7 @@ include!("../../generated/generated_sbix.rs");
 impl<'a> Strike<'a> {
     pub fn glyph_data(&self, glyph_id: GlyphId) -> Result<Option<GlyphData<'a>>, ReadError> {
         let offsets = self.glyph_data_offsets();
-        let start_ix = glyph_id.to_u16() as usize;
+        let start_ix = glyph_id.to_u32() as usize;
         let start = offsets.get(start_ix).ok_or(ReadError::OutOfBounds)?.get() as usize;
         let end = offsets
             .get(start_ix + 1)
diff --git a/read-fonts/src/tables/varc.rs b/read-fonts/src/tables/varc.rs
index ff5b1b83..44364d66 100644
--- a/read-fonts/src/tables/varc.rs
+++ b/read-fonts/src/tables/varc.rs
@@ -102,13 +102,9 @@ impl<'a> VarcComponent<'a> {
 
         // This is a GlyphID16 if GID_IS_24BIT bit of flags is clear, else GlyphID24.
         let gid = if flags.contains(VarcFlags::GID_IS_24BIT) {
-            let gid = cursor.read::<Uint24>()?.to_u32();
-            if gid > u16::MAX as u32 {
-                return Err(ReadError::BigGlyphIdsNotSupported(gid));
-            }
-            GlyphId::new(gid as u16)
+            GlyphId::new(cursor.read::<Uint24>()?.to_u32())
         } else {
-            GlyphId::new(cursor.read::<u16>()?)
+            GlyphId::from(cursor.read::<u16>()?)
         };
 
         let condition_index = if flags.contains(VarcFlags::HAVE_CONDITION) {
@@ -326,7 +322,7 @@ impl<'a> MultiItemVariationData<'a> {
 
 #[cfg(test)]
 mod tests {
-    use types::GlyphId;
+    use types::GlyphId16;
 
     use crate::{FontRef, ReadError, TableProvider};
 
@@ -499,7 +495,7 @@ mod tests {
 
         let glyph = table.glyph(idx as usize).unwrap();
         assert_eq!(
-            vec![GlyphId::new(2), GlyphId::new(5), GlyphId::new(7)],
+            vec![GlyphId16::new(2), GlyphId16::new(5), GlyphId16::new(7)],
             glyph
                 .components()
                 .map(|c| c.unwrap().gid)
diff --git a/read-fonts/src/tables/variations.rs b/read-fonts/src/tables/variations.rs
index d4cea2c5..509fade8 100644
--- a/read-fonts/src/tables/variations.rs
+++ b/read-fonts/src/tables/variations.rs
@@ -1149,12 +1149,12 @@ pub(crate) fn advance_delta(
     glyph_id: GlyphId,
     coords: &[F2Dot14],
 ) -> Result<Fixed, ReadError> {
-    let gid = glyph_id.to_u16();
+    let gid = glyph_id.to_u32();
     let ix = match dsim {
-        Some(Ok(dsim)) => dsim.get(gid as u32)?,
+        Some(Ok(dsim)) => dsim.get(gid)?,
         _ => DeltaSetIndex {
             outer: 0,
-            inner: gid,
+            inner: gid as _,
         },
     };
     Ok(Fixed::from_i32(ivs?.compute_delta(ix, coords)?))
@@ -1166,9 +1166,9 @@ pub(crate) fn item_delta(
     glyph_id: GlyphId,
     coords: &[F2Dot14],
 ) -> Result<Fixed, ReadError> {
-    let gid = glyph_id.to_u16();
+    let gid = glyph_id.to_u32();
     let ix = match dsim {
-        Some(Ok(dsim)) => dsim.get(gid as u32)?,
+        Some(Ok(dsim)) => dsim.get(gid)?,
         _ => return Err(ReadError::NullOffset),
     };
     Ok(Fixed::from_i32(ivs?.compute_delta(ix, coords)?))
diff --git a/read-fonts/src/traversal.rs b/read-fonts/src/traversal.rs
index a2c71b40..3d3172a8 100644
--- a/read-fonts/src/traversal.rs
+++ b/read-fonts/src/traversal.rs
@@ -11,7 +11,7 @@
 use std::{fmt::Debug, ops::Deref};
 
 use types::{
-    BigEndian, F2Dot14, FWord, Fixed, GlyphId, LongDateTime, MajorMinor, NameId, Nullable,
+    BigEndian, F2Dot14, FWord, Fixed, GlyphId16, LongDateTime, MajorMinor, NameId, Nullable,
     Offset16, Offset24, Offset32, Scalar, Tag, UfWord, Uint24, Version16Dot16,
 };
 
@@ -40,7 +40,7 @@ pub enum FieldType<'a> {
     F2Dot14(F2Dot14),
     Fixed(Fixed),
     LongDateTime(LongDateTime),
-    GlyphId(GlyphId),
+    GlyphId16(GlyphId16),
     NameId(NameId),
     BareOffset(OffsetType),
     ResolvedOffset(ResolvedOffset<'a>),
@@ -529,7 +529,7 @@ impl<'a> Debug for FieldType<'a> {
             Self::F2Dot14(arg0) => arg0.fmt(f),
             Self::Fixed(arg0) => arg0.fmt(f),
             Self::LongDateTime(arg0) => arg0.as_secs().fmt(f),
-            Self::GlyphId(arg0) => {
+            Self::GlyphId16(arg0) => {
                 write!(f, "g")?;
                 arg0.to_u16().fmt(f)
             }
@@ -705,9 +705,9 @@ impl<'a> From<Version16Dot16> for FieldType<'a> {
     }
 }
 
-impl<'a> From<GlyphId> for FieldType<'a> {
-    fn from(src: GlyphId) -> FieldType<'a> {
-        FieldType::GlyphId(src)
+impl<'a> From<GlyphId16> for FieldType<'a> {
+    fn from(src: GlyphId16) -> FieldType<'a> {
+        FieldType::GlyphId16(src)
     }
 }
 
diff --git a/resources/codegen_inputs/bitmap.rs b/resources/codegen_inputs/bitmap.rs
index 83f0e4f1..93e53a2e 100644
--- a/resources/codegen_inputs/bitmap.rs
+++ b/resources/codegen_inputs/bitmap.rs
@@ -15,9 +15,9 @@ record BitmapSize {
     /// Line metrics for text rendered vertically.
     vert: SbitLineMetrics,
     /// Lowest glyph index for this size.
-    start_glyph_index: GlyphId,
+    start_glyph_index: GlyphId16,
     /// Highest glyph index for this size.
-    end_glyph_index: GlyphId,
+    end_glyph_index: GlyphId16,
     /// Horizontal pixels per em.
     ppem_x: u8,
     /// Vertical pixels per em.
@@ -90,9 +90,9 @@ record SmallGlyphMetrics {
 /// [IndexSubtableArray](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtablearray) table.
 table IndexSubtableArray {
     /// First glyph ID of this range.
-    first_glyph_index: GlyphId,
+    first_glyph_index: GlyphId16,
     /// Last glyph ID of this range (inclusive).
-    last_glyph_index: GlyphId,
+    last_glyph_index: GlyphId16,
     /// Add to indexSubTableArrayOffset to get offset from beginning of EBLC.
     additional_offset_to_index_subtable: u32,
 }
@@ -167,7 +167,7 @@ table IndexSubtable4 {
 /// [GlyphIdOffsetPair](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#glyphidoffsetpair-record) record.
 record GlyphIdOffsetPair {
     /// Glyph ID of glyph present.
-    glyph_id: GlyphId,
+    glyph_id: GlyphId16,
     /// Location in EBDT.
     sbit_offset: u16,
 }
@@ -190,13 +190,13 @@ table IndexSubtable5 {
     num_glyphs: u32,
     /// One per glyph, sorted by glyhph ID.
     #[count($num_glyphs)]
-    glyph_array: [GlyphId],
+    glyph_array: [GlyphId16],
 }
 
 /// [EbdtComponent](https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#ebdtcomponent-record) record.
 record BdtComponent {
     /// Component glyph ID.
-    glyph_id: GlyphId,
+    glyph_id: GlyphId16,
     /// Position of component left.
     x_offset: i8,
     /// Position of component top.
diff --git a/resources/codegen_inputs/colr.rs b/resources/codegen_inputs/colr.rs
index 8d92b866..f7c03e7a 100644
--- a/resources/codegen_inputs/colr.rs
+++ b/resources/codegen_inputs/colr.rs
@@ -44,7 +44,7 @@ table Colr {
 /// [BaseGlyph](https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyph-and-layer-records) record
 record BaseGlyph {
     /// Glyph ID of the base glyph.
-    glyph_id: GlyphId,
+    glyph_id: GlyphId16,
     /// Index (base 0) into the layerRecords array.
     first_layer_index: u16,
     /// Number of color layers associated with this glyph.
@@ -54,7 +54,7 @@ record BaseGlyph {
 /// [Layer](https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyph-and-layer-records) record
 record Layer {
     /// Glyph ID of the glyph used for a given layer.
-    glyph_id: GlyphId,
+    glyph_id: GlyphId16,
     /// Index (base 0) for a palette entry in the CPAL table.
     palette_index: u16,
 }
@@ -69,7 +69,7 @@ table BaseGlyphList {
 /// [BaseGlyphPaint](https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyphlist-layerlist-and-cliplist) record
 record BaseGlyphPaint {
     /// Glyph ID of the base glyph.
-    glyph_id: GlyphId,
+    glyph_id: GlyphId16,
     /// Offset to a Paint table, from the beginning of the [`BaseGlyphList`] table.
     paint_offset: Offset32<Paint>,
 }
@@ -96,9 +96,9 @@ table ClipList {
 /// [Clip](https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyphlist-layerlist-and-cliplist) record
 record Clip {
     /// First glyph ID in the range.
-    start_glyph_id: GlyphId,
+    start_glyph_id: GlyphId16,
     /// Last glyph ID in the range.
-    end_glyph_id: GlyphId,
+    end_glyph_id: GlyphId16,
     /// Offset to a ClipBox table, from the beginning of the [`ClipList`] table.
     clip_box_offset: Offset24<ClipBox>,
 }
@@ -429,7 +429,7 @@ table PaintGlyph {
     /// Offset to a Paint table.
     paint_offset: Offset24<Paint>,
     /// Glyph ID for the source outline.
-    glyph_id: GlyphId,
+    glyph_id: GlyphId16,
 }
 
 /// [PaintColrGlyph](https://learn.microsoft.com/en-us/typography/opentype/spec/colr#format-11-paintcolrglyph) table
@@ -438,7 +438,7 @@ table PaintColrGlyph {
     #[format = 11]
     format: u8,
     /// Glyph ID for a BaseGlyphList base glyph.
-    glyph_id: GlyphId,
+    glyph_id: GlyphId16,
 }
 
 /// [PaintTransform](https://learn.microsoft.com/en-us/typography/opentype/spec/colr#formats-12-and-13-painttransform-paintvartransform) table
diff --git a/resources/codegen_inputs/gpos.rs b/resources/codegen_inputs/gpos.rs
index 88f9369d..f3254aea 100644
--- a/resources/codegen_inputs/gpos.rs
+++ b/resources/codegen_inputs/gpos.rs
@@ -223,7 +223,7 @@ table PairSet {
 record PairValueRecord {
     /// Glyph ID of second glyph in the pair (first glyph is listed in
     /// the Coverage table).
-    second_glyph: GlyphId,
+    second_glyph: GlyphId16,
     /// Positioning data for the first glyph in the pair.
     #[read_with($value_format1)]
     value_record1: ValueRecord,
diff --git a/resources/codegen_inputs/gsub.rs b/resources/codegen_inputs/gsub.rs
index b6f5490e..873c9c44 100644
--- a/resources/codegen_inputs/gsub.rs
+++ b/resources/codegen_inputs/gsub.rs
@@ -64,7 +64,7 @@ table SingleSubstFormat2 {
     glyph_count: u16,
     /// Array of substitute glyph IDs — ordered by Coverage index
     #[count($glyph_count)]
-    substitute_glyph_ids: [GlyphId],
+    substitute_glyph_ids: [GlyphId16],
 }
 
 /// [Multiple Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#21-multiple-substitution-format-1)
@@ -92,7 +92,7 @@ table Sequence {
     glyph_count: u16,
     /// String of glyph IDs to substitute
     #[count($glyph_count)]
-    substitute_glyph_ids: [GlyphId],
+    substitute_glyph_ids: [GlyphId16],
 }
 
 /// [Alternate Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#31-alternate-substitution-format-1)
@@ -119,7 +119,7 @@ table AlternateSet {
     glyph_count: u16,
     /// Array of alternate glyph IDs, in arbitrary order
     #[count($glyph_count)]
-    alternate_glyph_ids: [GlyphId],
+    alternate_glyph_ids: [GlyphId16],
 }
 
 /// [Ligature Substitution Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#41-ligature-substitution-format-1)
@@ -153,14 +153,14 @@ table LigatureSet {
 /// Part of [LigatureSubstFormat1]
 table Ligature {
     /// glyph ID of ligature to substitute
-    ligature_glyph: GlyphId,
+    ligature_glyph: GlyphId16,
     /// Number of components in the ligature
     #[compile(plus_one($component_glyph_ids.len()))]
     component_count: u16,
     /// Array of component glyph IDs — start with the second
     /// component, ordered in writing direction
     #[count(subtract($component_count, 1))]
-    component_glyph_ids: [GlyphId],
+    component_glyph_ids: [GlyphId16],
 }
 
 /// [Extension Substitution Subtable Format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#71-extension-substitution-subtable-format-1)
@@ -217,6 +217,6 @@ table ReverseChainSingleSubstFormat1 {
     glyph_count: u16,
     /// Array of substitute glyph IDs — ordered by Coverage index.
     #[count($glyph_count)]
-    substitute_glyph_ids: [GlyphId],
+    substitute_glyph_ids: [GlyphId16],
 }
 
diff --git a/resources/codegen_inputs/layout.rs b/resources/codegen_inputs/layout.rs
index aea94d17..1890d070 100644
--- a/resources/codegen_inputs/layout.rs
+++ b/resources/codegen_inputs/layout.rs
@@ -144,7 +144,7 @@ table CoverageFormat1 {
     glyph_count: u16,
     /// Array of glyph IDs — in numerical order
     #[count($glyph_count)]
-    glyph_array: [GlyphId],
+    glyph_array: [GlyphId16],
 }
 
 /// [Coverage Format 2](https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2)
@@ -163,9 +163,9 @@ table CoverageFormat2 {
 /// Used in [CoverageFormat2]
 record RangeRecord {
     /// First glyph ID in the range
-    start_glyph_id: GlyphId,
+    start_glyph_id: GlyphId16,
     /// Last glyph ID in the range
-    end_glyph_id: GlyphId,
+    end_glyph_id: GlyphId16,
     /// Coverage Index of first glyph ID in range
     start_coverage_index: u16,
 }
@@ -182,7 +182,7 @@ table ClassDefFormat1 {
     #[format = 1]
     class_format: u16,
     /// First glyph ID of the classValueArray
-    start_glyph_id: GlyphId,
+    start_glyph_id: GlyphId16,
     /// Size of the classValueArray
     #[compile(array_len($class_value_array))]
     glyph_count: u16,
@@ -208,9 +208,9 @@ table ClassDefFormat2 {
 record ClassRangeRecord {
     /// First glyph ID in the range
     #[validate(validate_glyph_range)]
-    start_glyph_id: GlyphId,
+    start_glyph_id: GlyphId16,
     /// Last glyph ID in the range
-    end_glyph_id: GlyphId,
+    end_glyph_id: GlyphId16,
     /// Applied to all glyphs in the range
     class: u16,
 }
@@ -268,7 +268,7 @@ table SequenceRule {
     seq_lookup_count: u16,
     /// Array of input glyph IDs—starting with the second glyph
     #[count(subtract($glyph_count, 1))]
-    input_sequence: [GlyphId],
+    input_sequence: [GlyphId16],
     /// Array of Sequence lookup records
     #[count($seq_lookup_count)]
     seq_lookup_records: [SequenceLookupRecord],
@@ -385,19 +385,19 @@ table ChainedSequenceRule {
     backtrack_glyph_count: u16,
     /// Array of backtrack glyph IDs
     #[count($backtrack_glyph_count)]
-    backtrack_sequence: [GlyphId],
+    backtrack_sequence: [GlyphId16],
     /// Number of glyphs in the input sequence
     #[compile(plus_one($input_sequence.len()))]
     input_glyph_count: u16,
     /// Array of input glyph IDs—start with second glyph
     #[count(subtract($input_glyph_count, 1))]
-    input_sequence: [GlyphId],
+    input_sequence: [GlyphId16],
     /// Number of glyphs in the lookahead sequence
     #[compile(array_len($lookahead_sequence))]
     lookahead_glyph_count: u16,
     /// Array of lookahead glyph IDs
     #[count($lookahead_glyph_count)]
-    lookahead_sequence: [GlyphId],
+    lookahead_sequence: [GlyphId16],
     /// Number of SequenceLookupRecords
     #[compile(array_len($seq_lookup_records))]
     seq_lookup_count: u16,
diff --git a/skrifa/src/color/instance.rs b/skrifa/src/color/instance.rs
index 153f5222..7516e53b 100644
--- a/skrifa/src/color/instance.rs
+++ b/skrifa/src/color/instance.rs
@@ -8,7 +8,7 @@ use read_fonts::{
             ItemVariationStore,
         },
     },
-    types::{BoundingBox, F2Dot14, GlyphId, Point},
+    types::{BoundingBox, F2Dot14, GlyphId16, Point},
     ReadError,
 };
 
@@ -234,11 +234,11 @@ pub enum ResolvedPaint<'a> {
         extend: Extend,
     },
     Glyph {
-        glyph_id: GlyphId,
+        glyph_id: GlyphId16,
         paint: Paint<'a>,
     },
     ColrGlyph {
-        glyph_id: GlyphId,
+        glyph_id: GlyphId16,
     },
     Transform {
         xx: f32,
diff --git a/skrifa/src/color/mod.rs b/skrifa/src/color/mod.rs
index 20f9078e..e2516d19 100644
--- a/skrifa/src/color/mod.rs
+++ b/skrifa/src/color/mod.rs
@@ -5,8 +5,7 @@
 //!
 //! ```
 //! # use core::result::Result;
-//! # use skrifa::{instance::{Size, Location}, color::{ColorGlyphFormat, ColorPainter, PaintError}, MetadataProvider};
-//! # use read_fonts::types::GlyphId;
+//! # use skrifa::{instance::{Size, Location}, color::{ColorGlyphFormat, ColorPainter, PaintError}, GlyphId, MetadataProvider};
 //! # fn get_colr_bb(font: read_fonts::FontRef, color_painter_impl : &mut impl ColorPainter, glyph_id : GlyphId, size: Size) -> Result<(), PaintError> {
 //! match font.color_glyphs()
 //!       .get_with_format(glyph_id, ColorGlyphFormat::ColrV1)
@@ -27,8 +26,7 @@
 //! ## Paint a COLRv1 glyph given a font, and a glyph id and a [`ColorPainter`] implementation:
 //! ```
 //! # use core::result::Result;
-//! # use skrifa::{instance::{Size, Location}, color::{ColorGlyphFormat, ColorPainter, PaintError}, MetadataProvider};
-//! # use read_fonts::types::GlyphId;
+//! # use skrifa::{instance::{Size, Location}, color::{ColorGlyphFormat, ColorPainter, PaintError}, GlyphId, MetadataProvider};
 //! # fn paint_colr(font: read_fonts::FontRef, color_painter_impl : &mut impl ColorPainter, glyph_id : GlyphId) -> Result<(), PaintError> {
 //! let color_glyph = font.color_glyphs()
 //!                     .get_with_format(glyph_id, ColorGlyphFormat::ColrV1)
@@ -204,14 +202,17 @@ pub trait ColorPainter {
     /// Push the specified transform by concatenating it to the current
     /// transformation matrix.
     fn push_transform(&mut self, transform: Transform);
+
     /// Restore the transformation matrix to the state before the previous
     /// [`push_transform`](ColorPainter::push_transform) call.
     fn pop_transform(&mut self);
 
     /// Apply a clip path in the shape of glyph specified by `glyph_id`.
     fn push_clip_glyph(&mut self, glyph_id: GlyphId);
+
     /// Apply a clip rectangle specified by `clip_rect`.
     fn push_clip_box(&mut self, clip_box: BoundingBox<f32>);
+
     /// Restore the clip state to the state before a previous
     /// [`push_clip_glyph`](ColorPainter::push_clip_glyph) or
     /// [`push_clip_box`](ColorPainter::push_clip_box) call.
diff --git a/skrifa/src/color/traversal.rs b/skrifa/src/color/traversal.rs
index 6b591bf5..401e4e4d 100644
--- a/skrifa/src/color/traversal.rs
+++ b/skrifa/src/color/traversal.rs
@@ -467,7 +467,8 @@ pub(crate) fn traverse_with_callbacks(
         }
 
         ResolvedPaint::Glyph { glyph_id, paint } => {
-            let mut optimizer = CollectFillGlyphPainter::new(painter, *glyph_id);
+            let glyph_id = (*glyph_id).into();
+            let mut optimizer = CollectFillGlyphPainter::new(painter, glyph_id);
             let mut result = traverse_with_callbacks(
                 &resolve_paint(instance, paint)?,
                 instance,
@@ -478,7 +479,7 @@ pub(crate) fn traverse_with_callbacks(
 
             // In case the optimization was not successful, just push a clip, and continue unoptimized traversal.
             if !optimizer.optimization_success {
-                painter.push_clip_glyph(*glyph_id);
+                painter.push_clip_glyph(glyph_id);
                 result = traverse_with_callbacks(
                     &resolve_paint(instance, paint)?,
                     instance,
@@ -491,40 +492,43 @@ pub(crate) fn traverse_with_callbacks(
 
             result
         }
-        ResolvedPaint::ColrGlyph { glyph_id } => match (*instance).v1_base_glyph(*glyph_id)? {
-            Some((base_glyph, base_glyph_paint_id)) => {
-                if !visited_set.insert(base_glyph_paint_id) {
-                    return Err(PaintError::PaintCycleDetected);
-                }
-
-                let draw_result = painter.paint_cached_color_glyph(*glyph_id)?;
-                let result = match draw_result {
-                    PaintCachedColorGlyph::Ok => Ok(()),
-                    PaintCachedColorGlyph::Unimplemented => {
-                        let clipbox = get_clipbox_font_units(instance, *glyph_id);
-
-                        if let Some(rect) = clipbox {
-                            painter.push_clip_box(rect);
-                        }
+        ResolvedPaint::ColrGlyph { glyph_id } => {
+            let glyph_id = (*glyph_id).into();
+            match (*instance).v1_base_glyph(glyph_id)? {
+                Some((base_glyph, base_glyph_paint_id)) => {
+                    if !visited_set.insert(base_glyph_paint_id) {
+                        return Err(PaintError::PaintCycleDetected);
+                    }
 
-                        let result = traverse_with_callbacks(
-                            &resolve_paint(instance, &base_glyph)?,
-                            instance,
-                            painter,
-                            visited_set,
-                            recurse_depth + 1,
-                        );
-                        if clipbox.is_some() {
-                            painter.pop_clip();
+                    let draw_result = painter.paint_cached_color_glyph(glyph_id)?;
+                    let result = match draw_result {
+                        PaintCachedColorGlyph::Ok => Ok(()),
+                        PaintCachedColorGlyph::Unimplemented => {
+                            let clipbox = get_clipbox_font_units(instance, glyph_id);
+
+                            if let Some(rect) = clipbox {
+                                painter.push_clip_box(rect);
+                            }
+
+                            let result = traverse_with_callbacks(
+                                &resolve_paint(instance, &base_glyph)?,
+                                instance,
+                                painter,
+                                visited_set,
+                                recurse_depth + 1,
+                            );
+                            if clipbox.is_some() {
+                                painter.pop_clip();
+                            }
+                            result
                         }
-                        result
-                    }
-                };
-                visited_set.remove(&base_glyph_paint_id);
-                result
+                    };
+                    visited_set.remove(&base_glyph_paint_id);
+                    result
+                }
+                None => Err(PaintError::GlyphNotFound(glyph_id)),
             }
-            None => Err(PaintError::GlyphNotFound(*glyph_id)),
-        },
+        }
         ResolvedPaint::Transform {
             paint: next_paint, ..
         }
@@ -586,9 +590,9 @@ pub(crate) fn traverse_v0_range(
     painter: &mut impl ColorPainter,
 ) -> Result<(), PaintError> {
     for layer_index in range.clone() {
-        let (layer_index, palette_index) = (*instance).v0_layer(layer_index)?;
+        let (layer_glyph, palette_index) = (*instance).v0_layer(layer_index)?;
         painter.fill_glyph(
-            layer_index,
+            layer_glyph.into(),
             None,
             Brush::Solid {
                 palette_index,
diff --git a/skrifa/src/metrics.rs b/skrifa/src/metrics.rs
index 83862f90..f65df908 100644
--- a/skrifa/src/metrics.rs
+++ b/skrifa/src/metrics.rs
@@ -222,7 +222,7 @@ impl Metrics {
 /// Glyph specific metrics.
 #[derive(Clone)]
 pub struct GlyphMetrics<'a> {
-    glyph_count: u16,
+    glyph_count: u32,
     fixed_scale: FixedScaleFactor,
     h_metrics: &'a [LongMetric],
     default_advance_width: u16,
@@ -243,7 +243,7 @@ impl<'a> GlyphMetrics<'a> {
     ) -> Self {
         let glyph_count = font
             .maxp()
-            .map(|maxp| maxp.num_glyphs())
+            .map(|maxp| maxp.num_glyphs() as u32)
             .unwrap_or_default();
         let upem = font
             .head()
@@ -281,7 +281,7 @@ impl<'a> GlyphMetrics<'a> {
     }
 
     /// Returns the number of available glyphs in the font.
-    pub fn glyph_count(&self) -> u16 {
+    pub fn glyph_count(&self) -> u32 {
         self.glyph_count
     }
 
@@ -293,12 +293,12 @@ impl<'a> GlyphMetrics<'a> {
     /// Returns `None` if `glyph_id >= self.glyph_count()` or the underlying font
     /// data is invalid.
     pub fn advance_width(&self, glyph_id: GlyphId) -> Option<f32> {
-        if glyph_id.to_u16() >= self.glyph_count {
+        if glyph_id.to_u32() >= self.glyph_count {
             return None;
         }
         let mut advance = self
             .h_metrics
-            .get(glyph_id.to_u16() as usize)
+            .get(glyph_id.to_u32() as usize)
             .map(|metric| metric.advance())
             .unwrap_or(self.default_advance_width) as i32;
         if let Some(hvar) = &self.hvar {
@@ -322,10 +322,10 @@ impl<'a> GlyphMetrics<'a> {
     /// Returns `None` if `glyph_id >= self.glyph_count()` or the underlying font
     /// data is invalid.
     pub fn left_side_bearing(&self, glyph_id: GlyphId) -> Option<f32> {
-        if glyph_id.to_u16() >= self.glyph_count {
+        if glyph_id.to_u32() >= self.glyph_count {
             return None;
         }
-        let gid_index = glyph_id.to_u16() as usize;
+        let gid_index = glyph_id.to_u32() as usize;
         let mut lsb = self
             .h_metrics
             .get(gid_index)
@@ -471,7 +471,8 @@ impl<'a> GvarMetricDeltas<'a> {
                         .flags
                         .contains(CompositeGlyphFlags::USE_MY_METRICS)
                     {
-                        return self.find_glyph_and_point_count(component.glyph, recurse_depth + 1);
+                        return self
+                            .find_glyph_and_point_count(component.glyph.into(), recurse_depth + 1);
                     }
                 }
                 Some((glyph_id, count))
@@ -559,7 +560,7 @@ mod tests {
         ];
         let result = (0..4)
             .map(|i| {
-                let gid = GlyphId::new(i as u16);
+                let gid = GlyphId::new(i as u32);
                 let advance_width = glyph_metrics.advance_width(gid).unwrap();
                 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
                 (advance_width, lsb)
@@ -605,7 +606,7 @@ mod tests {
         ];
         let result = (0..4)
             .map(|i| {
-                let gid = GlyphId::new(i as u16);
+                let gid = GlyphId::new(i as u32);
                 let advance_width = glyph_metrics.advance_width(gid).unwrap();
                 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
                 (advance_width, lsb)
@@ -627,7 +628,7 @@ mod tests {
             // Setting hvar to None forces use of gvar for metric deltas
             glyph_metrics_no_hvar.hvar = None;
             for gid in 0..glyph_count {
-                let gid = GlyphId::new(gid);
+                let gid = GlyphId::from(gid);
                 assert_eq!(
                     glyph_metrics.advance_width(gid),
                     glyph_metrics_no_hvar.advance_width(gid)
diff --git a/skrifa/src/outline/autohint/style.rs b/skrifa/src/outline/autohint/style.rs
index 80a249e7..a0cd3228 100644
--- a/skrifa/src/outline/autohint/style.rs
+++ b/skrifa/src/outline/autohint/style.rs
@@ -64,7 +64,7 @@ pub(super) fn compute_mapped_styles(
     // avoid a binary search per character
     let mut last_range: Option<(usize, ScriptRange)> = None;
     for (ch, gid) in charmap {
-        let Some(style) = styles.get_mut(gid.to_u16() as usize) else {
+        let Some(style) = styles.get_mut(gid.to_u32() as usize) else {
             continue;
         };
         // Charmaps enumerate in order so we're likely to encounter at least
diff --git a/skrifa/src/outline/cff/mod.rs b/skrifa/src/outline/cff/mod.rs
index 18ff9d75..d5e90e00 100644
--- a/skrifa/src/outline/cff/mod.rs
+++ b/skrifa/src/outline/cff/mod.rs
@@ -214,7 +214,7 @@ impl<'a> Outlines<'a> {
             .charstrings
             .as_ref()
             .ok_or(Error::MissingCharstrings)?
-            .get(glyph_id.to_u16() as usize)?;
+            .get(glyph_id.to_u32() as usize)?;
         let subrs = subfont.subrs(self)?;
         let blend_state = subfont.blend_state(self, coords)?;
         let mut pen_sink = charstring::PenSink::new(pen);
diff --git a/skrifa/src/outline/glyf/hint/error.rs b/skrifa/src/outline/glyf/hint/error.rs
index f8ba3e96..925c8c96 100644
--- a/skrifa/src/outline/glyf/hint/error.rs
+++ b/skrifa/src/outline/glyf/hint/error.rs
@@ -109,7 +109,7 @@ impl core::fmt::Display for HintError {
             Program::Glyph => write!(f, "glyf")?,
         }
         if let Some(glyph_id) = self.glyph_id {
-            write!(f, "[{}]", glyph_id.to_u16())?;
+            write!(f, "[{}]", glyph_id.to_u32())?;
         }
         let (opcode, colon) = match self.opcode {
             Some(opcode) => (opcode.name(), ":"),
diff --git a/skrifa/src/outline/glyf/mod.rs b/skrifa/src/outline/glyf/mod.rs
index e34fef45..5dfed987 100644
--- a/skrifa/src/outline/glyf/mod.rs
+++ b/skrifa/src/outline/glyf/mod.rs
@@ -195,7 +195,7 @@ impl<'a> Outlines<'a> {
                 let point_base = outline.points;
                 for (component, flags) in composite.component_glyphs_and_flags() {
                     outline.has_overlaps |= flags.contains(CompositeGlyphFlags::OVERLAP_COMPOUND);
-                    let component_glyph = self.loca.get_glyf(component, &self.glyf)?;
+                    let component_glyph = self.loca.get_glyf(component.into(), &self.glyf)?;
                     let Some(component_glyph) = component_glyph else {
                         continue;
                     };
@@ -724,8 +724,8 @@ impl<'a> Scaler for FreeTypeScaler<'a> {
             let component_glyph = self
                 .outlines
                 .loca
-                .get_glyf(component.glyph, &self.outlines.glyf)?;
-            self.load(&component_glyph, component.glyph, recurse_depth + 1)?;
+                .get_glyf(component.glyph.into(), &self.outlines.glyf)?;
+            self.load(&component_glyph, component.glyph.into(), recurse_depth + 1)?;
             let end_point = self.point_count;
             if !component
                 .flags
@@ -1106,8 +1106,8 @@ impl<'a> Scaler for HarfBuzzScaler<'a> {
             let component_glyph = self
                 .outlines
                 .loca
-                .get_glyf(component.glyph, &self.outlines.glyf)?;
-            self.load(&component_glyph, component.glyph, recurse_depth + 1)?;
+                .get_glyf(component.glyph.into(), &self.outlines.glyf)?;
+            self.load(&component_glyph, component.glyph.into(), recurse_depth + 1)?;
             let end_point = self.point_count;
             if !component
                 .flags
@@ -1217,7 +1217,7 @@ mod tests {
         assert_eq!(
             expected_gids_with_overlap,
             (0..glyph_count)
-                .filter(|gid| scaler.outline(GlyphId::new(*gid)).unwrap().has_overlaps)
+                .filter(|gid| scaler.outline(GlyphId::from(*gid)).unwrap().has_overlaps)
                 .collect::<Vec<_>>()
         );
     }
diff --git a/skrifa/src/outline/mod.rs b/skrifa/src/outline/mod.rs
index 99ac1000..fc97d401 100644
--- a/skrifa/src/outline/mod.rs
+++ b/skrifa/src/outline/mod.rs
@@ -504,7 +504,7 @@ impl<'a> OutlineGlyphCollection<'a> {
         } as u16;
         let copy = self.clone();
         (0..len).filter_map(move |gid| {
-            let gid = GlyphId::new(gid);
+            let gid = GlyphId::from(gid);
             let glyph = copy.get(gid)?;
             Some((gid, glyph))
         })
@@ -632,7 +632,7 @@ mod tests {
             expected_gids_with_overlap,
             (0..glyph_count)
                 .filter(
-                    |gid| outlines.get(GlyphId::new(*gid)).unwrap().has_overlaps() == Some(true)
+                    |gid| outlines.get(GlyphId::from(*gid)).unwrap().has_overlaps() == Some(true)
                 )
                 .collect::<Vec<_>>()
         );
diff --git a/write-fonts/generated/generated_gpos.rs b/write-fonts/generated/generated_gpos.rs
index d77c07e6..13ed4ce7 100644
--- a/write-fonts/generated/generated_gpos.rs
+++ b/write-fonts/generated/generated_gpos.rs
@@ -1051,7 +1051,7 @@ impl<'a> FromTableRef<read_fonts::tables::gpos::PairSet<'a>> for PairSet {}
 pub struct PairValueRecord {
     /// Glyph ID of second glyph in the pair (first glyph is listed in
     /// the Coverage table).
-    pub second_glyph: GlyphId,
+    pub second_glyph: GlyphId16,
     /// Positioning data for the first glyph in the pair.
     pub value_record1: ValueRecord,
     /// Positioning data for the second glyph in the pair.
@@ -1061,7 +1061,7 @@ pub struct PairValueRecord {
 impl PairValueRecord {
     /// Construct a new `PairValueRecord`
     pub fn new(
-        second_glyph: GlyphId,
+        second_glyph: GlyphId16,
         value_record1: ValueRecord,
         value_record2: ValueRecord,
     ) -> Self {
diff --git a/write-fonts/generated/generated_gsub.rs b/write-fonts/generated/generated_gsub.rs
index 0132c4dd..8b446c08 100644
--- a/write-fonts/generated/generated_gsub.rs
+++ b/write-fonts/generated/generated_gsub.rs
@@ -208,7 +208,7 @@ impl SingleSubst {
     }
 
     /// Construct a new `SingleSubstFormat2` subtable
-    pub fn format_2(coverage: CoverageTable, substitute_glyph_ids: Vec<GlyphId>) -> Self {
+    pub fn format_2(coverage: CoverageTable, substitute_glyph_ids: Vec<GlyphId16>) -> Self {
         Self::Format2(SingleSubstFormat2::new(coverage, substitute_glyph_ids))
     }
 }
@@ -342,12 +342,12 @@ pub struct SingleSubstFormat2 {
     /// subtable
     pub coverage: OffsetMarker<CoverageTable>,
     /// Array of substitute glyph IDs — ordered by Coverage index
-    pub substitute_glyph_ids: Vec<GlyphId>,
+    pub substitute_glyph_ids: Vec<GlyphId16>,
 }
 
 impl SingleSubstFormat2 {
     /// Construct a new `SingleSubstFormat2`
-    pub fn new(coverage: CoverageTable, substitute_glyph_ids: Vec<GlyphId>) -> Self {
+    pub fn new(coverage: CoverageTable, substitute_glyph_ids: Vec<GlyphId16>) -> Self {
         Self {
             coverage: coverage.into(),
             substitute_glyph_ids: substitute_glyph_ids.into_iter().map(Into::into).collect(),
@@ -476,12 +476,12 @@ impl<'a> FontRead<'a> for MultipleSubstFormat1 {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct Sequence {
     /// String of glyph IDs to substitute
-    pub substitute_glyph_ids: Vec<GlyphId>,
+    pub substitute_glyph_ids: Vec<GlyphId16>,
 }
 
 impl Sequence {
     /// Construct a new `Sequence`
-    pub fn new(substitute_glyph_ids: Vec<GlyphId>) -> Self {
+    pub fn new(substitute_glyph_ids: Vec<GlyphId16>) -> Self {
         Self {
             substitute_glyph_ids: substitute_glyph_ids.into_iter().map(Into::into).collect(),
         }
@@ -608,12 +608,12 @@ impl<'a> FontRead<'a> for AlternateSubstFormat1 {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct AlternateSet {
     /// Array of alternate glyph IDs, in arbitrary order
-    pub alternate_glyph_ids: Vec<GlyphId>,
+    pub alternate_glyph_ids: Vec<GlyphId16>,
 }
 
 impl AlternateSet {
     /// Construct a new `AlternateSet`
-    pub fn new(alternate_glyph_ids: Vec<GlyphId>) -> Self {
+    pub fn new(alternate_glyph_ids: Vec<GlyphId16>) -> Self {
         Self {
             alternate_glyph_ids: alternate_glyph_ids.into_iter().map(Into::into).collect(),
         }
@@ -792,15 +792,15 @@ impl<'a> FontRead<'a> for LigatureSet {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct Ligature {
     /// glyph ID of ligature to substitute
-    pub ligature_glyph: GlyphId,
+    pub ligature_glyph: GlyphId16,
     /// Array of component glyph IDs — start with the second
     /// component, ordered in writing direction
-    pub component_glyph_ids: Vec<GlyphId>,
+    pub component_glyph_ids: Vec<GlyphId16>,
 }
 
 impl Ligature {
     /// Construct a new `Ligature`
-    pub fn new(ligature_glyph: GlyphId, component_glyph_ids: Vec<GlyphId>) -> Self {
+    pub fn new(ligature_glyph: GlyphId16, component_glyph_ids: Vec<GlyphId16>) -> Self {
         Self {
             ligature_glyph,
             component_glyph_ids: component_glyph_ids.into_iter().map(Into::into).collect(),
@@ -1005,7 +1005,7 @@ pub struct ReverseChainSingleSubstFormat1 {
     /// glyph sequence order.
     pub lookahead_coverages: Vec<OffsetMarker<CoverageTable>>,
     /// Array of substitute glyph IDs — ordered by Coverage index.
-    pub substitute_glyph_ids: Vec<GlyphId>,
+    pub substitute_glyph_ids: Vec<GlyphId16>,
 }
 
 impl ReverseChainSingleSubstFormat1 {
@@ -1014,7 +1014,7 @@ impl ReverseChainSingleSubstFormat1 {
         coverage: CoverageTable,
         backtrack_coverages: Vec<CoverageTable>,
         lookahead_coverages: Vec<CoverageTable>,
-        substitute_glyph_ids: Vec<GlyphId>,
+        substitute_glyph_ids: Vec<GlyphId16>,
     ) -> Self {
         Self {
             coverage: coverage.into(),
diff --git a/write-fonts/generated/generated_layout.rs b/write-fonts/generated/generated_layout.rs
index bb742c39..25afcacc 100644
--- a/write-fonts/generated/generated_layout.rs
+++ b/write-fonts/generated/generated_layout.rs
@@ -616,12 +616,12 @@ where
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct CoverageFormat1 {
     /// Array of glyph IDs — in numerical order
-    pub glyph_array: Vec<GlyphId>,
+    pub glyph_array: Vec<GlyphId16>,
 }
 
 impl CoverageFormat1 {
     /// Construct a new `CoverageFormat1`
-    pub fn new(glyph_array: Vec<GlyphId>) -> Self {
+    pub fn new(glyph_array: Vec<GlyphId16>) -> Self {
         Self {
             glyph_array: glyph_array.into_iter().map(Into::into).collect(),
         }
@@ -735,16 +735,20 @@ impl<'a> FontRead<'a> for CoverageFormat2 {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct RangeRecord {
     /// First glyph ID in the range
-    pub start_glyph_id: GlyphId,
+    pub start_glyph_id: GlyphId16,
     /// Last glyph ID in the range
-    pub end_glyph_id: GlyphId,
+    pub end_glyph_id: GlyphId16,
     /// Coverage Index of first glyph ID in range
     pub start_coverage_index: u16,
 }
 
 impl RangeRecord {
     /// Construct a new `RangeRecord`
-    pub fn new(start_glyph_id: GlyphId, end_glyph_id: GlyphId, start_coverage_index: u16) -> Self {
+    pub fn new(
+        start_glyph_id: GlyphId16,
+        end_glyph_id: GlyphId16,
+        start_coverage_index: u16,
+    ) -> Self {
         Self {
             start_glyph_id,
             end_glyph_id,
@@ -788,7 +792,7 @@ pub enum CoverageTable {
 
 impl CoverageTable {
     /// Construct a new `CoverageFormat1` subtable
-    pub fn format_1(glyph_array: Vec<GlyphId>) -> Self {
+    pub fn format_1(glyph_array: Vec<GlyphId16>) -> Self {
         Self::Format1(CoverageFormat1::new(glyph_array))
     }
 
@@ -864,14 +868,14 @@ impl From<CoverageFormat2> for CoverageTable {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct ClassDefFormat1 {
     /// First glyph ID of the classValueArray
-    pub start_glyph_id: GlyphId,
+    pub start_glyph_id: GlyphId16,
     /// Array of Class Values — one per glyph ID
     pub class_value_array: Vec<u16>,
 }
 
 impl ClassDefFormat1 {
     /// Construct a new `ClassDefFormat1`
-    pub fn new(start_glyph_id: GlyphId, class_value_array: Vec<u16>) -> Self {
+    pub fn new(start_glyph_id: GlyphId16, class_value_array: Vec<u16>) -> Self {
         Self {
             start_glyph_id,
             class_value_array: class_value_array.into_iter().map(Into::into).collect(),
@@ -988,16 +992,16 @@ impl<'a> FontRead<'a> for ClassDefFormat2 {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct ClassRangeRecord {
     /// First glyph ID in the range
-    pub start_glyph_id: GlyphId,
+    pub start_glyph_id: GlyphId16,
     /// Last glyph ID in the range
-    pub end_glyph_id: GlyphId,
+    pub end_glyph_id: GlyphId16,
     /// Applied to all glyphs in the range
     pub class: u16,
 }
 
 impl ClassRangeRecord {
     /// Construct a new `ClassRangeRecord`
-    pub fn new(start_glyph_id: GlyphId, end_glyph_id: GlyphId, class: u16) -> Self {
+    pub fn new(start_glyph_id: GlyphId16, end_glyph_id: GlyphId16, class: u16) -> Self {
         Self {
             start_glyph_id,
             end_glyph_id,
@@ -1047,7 +1051,7 @@ pub enum ClassDef {
 
 impl ClassDef {
     /// Construct a new `ClassDefFormat1` subtable
-    pub fn format_1(start_glyph_id: GlyphId, class_value_array: Vec<u16>) -> Self {
+    pub fn format_1(start_glyph_id: GlyphId16, class_value_array: Vec<u16>) -> Self {
         Self::Format1(ClassDefFormat1::new(start_glyph_id, class_value_array))
     }
 
@@ -1301,7 +1305,7 @@ impl<'a> FontRead<'a> for SequenceRuleSet {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct SequenceRule {
     /// Array of input glyph IDs—starting with the second glyph
-    pub input_sequence: Vec<GlyphId>,
+    pub input_sequence: Vec<GlyphId16>,
     /// Array of Sequence lookup records
     pub seq_lookup_records: Vec<SequenceLookupRecord>,
 }
@@ -1309,7 +1313,7 @@ pub struct SequenceRule {
 impl SequenceRule {
     /// Construct a new `SequenceRule`
     pub fn new(
-        input_sequence: Vec<GlyphId>,
+        input_sequence: Vec<GlyphId16>,
         seq_lookup_records: Vec<SequenceLookupRecord>,
     ) -> Self {
         Self {
@@ -1928,11 +1932,11 @@ impl<'a> FontRead<'a> for ChainedSequenceRuleSet {
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct ChainedSequenceRule {
     /// Array of backtrack glyph IDs
-    pub backtrack_sequence: Vec<GlyphId>,
+    pub backtrack_sequence: Vec<GlyphId16>,
     /// Array of input glyph IDs—start with second glyph
-    pub input_sequence: Vec<GlyphId>,
+    pub input_sequence: Vec<GlyphId16>,
     /// Array of lookahead glyph IDs
-    pub lookahead_sequence: Vec<GlyphId>,
+    pub lookahead_sequence: Vec<GlyphId16>,
     /// Array of SequenceLookupRecords
     pub seq_lookup_records: Vec<SequenceLookupRecord>,
 }
@@ -1940,9 +1944,9 @@ pub struct ChainedSequenceRule {
 impl ChainedSequenceRule {
     /// Construct a new `ChainedSequenceRule`
     pub fn new(
-        backtrack_sequence: Vec<GlyphId>,
-        input_sequence: Vec<GlyphId>,
-        lookahead_sequence: Vec<GlyphId>,
+        backtrack_sequence: Vec<GlyphId16>,
+        input_sequence: Vec<GlyphId16>,
+        lookahead_sequence: Vec<GlyphId16>,
         seq_lookup_records: Vec<SequenceLookupRecord>,
     ) -> Self {
         Self {
diff --git a/write-fonts/src/graph.rs b/write-fonts/src/graph.rs
index de6c9fb1..20af11eb 100644
--- a/write-fonts/src/graph.rs
+++ b/write-fonts/src/graph.rs
@@ -1187,7 +1187,7 @@ impl Default for Priority {
 mod tests {
     use std::ops::Range;
 
-    use font_types::GlyphId;
+    use font_types::GlyphId16;
 
     use crate::TableWriter;
 
@@ -1621,13 +1621,13 @@ mod tests {
         let rsub_rules = (0u16..NUM_SUBTABLES as u16)
             .map(|id| {
                 // Each rule will use unique coverage tables, so nothing is shared.
-                let coverage = std::iter::once(GlyphId::new(id)).collect();
-                let backtrack = [id + 1, id + 3].into_iter().map(GlyphId::new).collect();
+                let coverage = std::iter::once(GlyphId16::new(id)).collect();
+                let backtrack = [id + 1, id + 3].into_iter().map(GlyphId16::new).collect();
                 gsub::ReverseChainSingleSubstFormat1::new(
                     coverage,
                     vec![backtrack],
                     vec![],
-                    vec![GlyphId::new(id + 1)],
+                    vec![GlyphId16::new(id + 1)],
                 )
             })
             .collect();
@@ -1677,7 +1677,7 @@ mod tests {
         let _ = env_logger::builder().is_test(true).try_init();
 
         fn make_big_pair_pos(glyph_range: Range<u16>) -> gpos::PositionLookup {
-            let coverage = glyph_range.clone().map(GlyphId::new).collect();
+            let coverage = glyph_range.clone().map(GlyphId16::new).collect();
             let pair_sets = glyph_range
                 .map(|id| {
                     let value_rec = gpos::ValueRecord::new().with_x_advance(id as _);
@@ -1685,7 +1685,7 @@ mod tests {
                         (id..id + 165)
                             .map(|id2| {
                                 gpos::PairValueRecord::new(
-                                    GlyphId::new(id2),
+                                    GlyphId16::new(id2),
                                     value_rec.clone(),
                                     gpos::ValueRecord::default(),
                                 )
diff --git a/write-fonts/src/graph/splitting.rs b/write-fonts/src/graph/splitting.rs
index 09dc2ddd..e94b11ce 100644
--- a/write-fonts/src/graph/splitting.rs
+++ b/write-fonts/src/graph/splitting.rs
@@ -11,7 +11,7 @@
 
 use std::collections::HashMap;
 
-use font_types::GlyphId;
+use font_types::GlyphId16;
 use read_fonts::tables::{
     gpos::{self as rgpos},
     layout as rlayout,
@@ -143,8 +143,8 @@ fn split_range_record(
 
     let end_glyph = start_glyph + range_len;
     Some(wlayout::RangeRecord::new(
-        GlyphId::new(start_glyph),
-        GlyphId::new(end_glyph),
+        GlyphId16::new(start_glyph),
+        GlyphId16::new(end_glyph),
         new_cov_start,
     ))
 }
@@ -169,8 +169,8 @@ mod tests {
 
     fn make_read_record(start_coverage_index: u16, glyphs: Range<u16>) -> rlayout::RangeRecord {
         rlayout::RangeRecord {
-            start_glyph_id: GlyphId::new(glyphs.start).into(),
-            end_glyph_id: GlyphId::new(glyphs.end).into(),
+            start_glyph_id: GlyphId16::new(glyphs.start).into(),
+            end_glyph_id: GlyphId16::new(glyphs.end).into(),
             start_coverage_index: start_coverage_index.into(),
         }
     }
diff --git a/write-fonts/src/graph/splitting/mark2base.rs b/write-fonts/src/graph/splitting/mark2base.rs
index 0ed01e16..6ac0bd72 100644
--- a/write-fonts/src/graph/splitting/mark2base.rs
+++ b/write-fonts/src/graph/splitting/mark2base.rs
@@ -287,7 +287,7 @@ fn get_class_info(graph: &Graph, subtable: ObjectId) -> Vec<Mark2BaseClassInfo>
 mod tests {
     use std::ops::Range;
 
-    use font_types::GlyphId;
+    use font_types::GlyphId16;
     use read_fonts::{
         tables::{gpos::PositionSubtables, layout::LookupFlag},
         FontRead,
@@ -383,10 +383,10 @@ mod tests {
         const FIRST_MARK_GLYPH: u16 = 2000;
 
         let mark_coverage = (FIRST_MARK_GLYPH..FIRST_MARK_GLYPH + N_MARKS)
-            .map(GlyphId::new)
+            .map(GlyphId16::new)
             .collect();
         let base_coverage = (FIRST_BASE_GLYPH..FIRST_BASE_GLYPH + N_BASES)
-            .map(GlyphId::new)
+            .map(GlyphId16::new)
             .collect();
         let mark_array = make_mark_array(MARK_CLASS_COUNT, MARKS_PER_CLASS, false);
         let base_array = make_base_array(N_BASES, MARK_CLASS_COUNT, false);
@@ -429,8 +429,8 @@ mod tests {
         // this is a closure for comparing the pre-and-post split values
         let compare_old_and_new = |base_gid, mark_gid| {
             // now let's manually check one of the records.
-            let base_gid = GlyphId::new(base_gid);
-            let mark_gid = GlyphId::new(mark_gid);
+            let base_gid = GlyphId16::new(base_gid);
+            let mark_gid = GlyphId16::new(mark_gid);
 
             // find the values in the original table:
             let old_subtable = &lookup.subtables[0];
@@ -513,10 +513,10 @@ mod tests {
         const FIRST_MARK_GLYPH: u16 = 2000;
 
         let mark_coverage = (FIRST_MARK_GLYPH..FIRST_MARK_GLYPH + N_MARKS)
-            .map(GlyphId::new)
+            .map(GlyphId16::new)
             .collect();
         let base_coverage = (FIRST_BASE_GLYPH..FIRST_BASE_GLYPH + N_BASES)
-            .map(GlyphId::new)
+            .map(GlyphId16::new)
             .collect();
         let mark_array = make_mark_array(MARK_CLASS_COUNT, MARKS_PER_CLASS, true);
         let base_array = make_base_array(N_BASES, MARK_CLASS_COUNT, true);
@@ -554,7 +554,7 @@ mod tests {
             _ => panic!("very bad assumptions"),
         };
 
-        let gid = GlyphId::new(FIRST_MARK_GLYPH + mark_cov_idx_to_test as u16);
+        let gid = GlyphId16::new(FIRST_MARK_GLYPH + mark_cov_idx_to_test as u16);
         let subtable = subtables
             .iter()
             .find(|sub| sub.mark_coverage().unwrap().get(gid).is_some())
diff --git a/write-fonts/src/graph/splitting/pairpos.rs b/write-fonts/src/graph/splitting/pairpos.rs
index a0942820..cd128a8a 100644
--- a/write-fonts/src/graph/splitting/pairpos.rs
+++ b/write-fonts/src/graph/splitting/pairpos.rs
@@ -2,7 +2,7 @@
 
 use std::collections::{BTreeSet, HashMap, HashSet};
 
-use font_types::{FixedSize, GlyphId, Offset16};
+use font_types::{FixedSize, GlyphId16, Offset16};
 use read_fonts::tables::{
     gpos::{self as rgpos, ValueFormat},
     layout as rlayout,
@@ -385,7 +385,7 @@ fn copy_value_rec(
 struct ClassDefSizeEstimator {
     consecutive_gids: bool,
     num_ranges_per_class: HashMap<u16, u16>,
-    glyphs_per_class: HashMap<u16, BTreeSet<GlyphId>>,
+    glyphs_per_class: HashMap<u16, BTreeSet<GlyphId16>>,
 }
 
 const GLYPH_SIZE: usize = std::mem::size_of::<u16>();
@@ -449,7 +449,7 @@ impl ClassDefSizeEstimator {
     }
 }
 
-fn count_num_ranges(glyphs: &BTreeSet<GlyphId>) -> u16 {
+fn count_num_ranges(glyphs: &BTreeSet<GlyphId16>) -> u16 {
     let mut count = 0;
     let mut last = None;
     for gid in glyphs {
@@ -518,7 +518,7 @@ mod tests {
     fn split_pair_pos1() {
         let _ = env_logger::builder().is_test(true).try_init();
 
-        struct KernPair(GlyphId, GlyphId, i16);
+        struct KernPair(GlyphId16, GlyphId16, i16);
         fn make_pair_pos(pairs: Vec<KernPair>) -> PairPos {
             let mut records = BTreeMap::new();
             for KernPair(one, two, kern) in pairs {
@@ -548,10 +548,26 @@ mod tests {
 
         let mut pairs = Vec::new();
         for (advance, g1) in (0u16..N_GLYPHS).enumerate() {
-            pairs.push(KernPair(GlyphId::new(g1), GlyphId::new(5), advance as _));
-            pairs.push(KernPair(GlyphId::new(g1), GlyphId::new(6), advance as _));
-            pairs.push(KernPair(GlyphId::new(g1), GlyphId::new(7), advance as _));
-            pairs.push(KernPair(GlyphId::new(g1), GlyphId::new(8), advance as _));
+            pairs.push(KernPair(
+                GlyphId16::new(g1),
+                GlyphId16::new(5),
+                advance as _,
+            ));
+            pairs.push(KernPair(
+                GlyphId16::new(g1),
+                GlyphId16::new(6),
+                advance as _,
+            ));
+            pairs.push(KernPair(
+                GlyphId16::new(g1),
+                GlyphId16::new(7),
+                advance as _,
+            ));
+            pairs.push(KernPair(
+                GlyphId16::new(g1),
+                GlyphId16::new(8),
+                advance as _,
+            ));
         }
 
         let table = make_pair_pos(pairs);
@@ -576,7 +592,7 @@ mod tests {
             .unwrap()
             .iter()
             .chain(sub2.coverage().unwrap().iter())
-            .map(GlyphId::to_u16)
+            .map(GlyphId16::to_u16)
             .collect::<Vec<_>>();
         assert_eq!(gids.len(), N_GLYPHS as _);
 
@@ -608,13 +624,13 @@ mod tests {
                             u16::MAX - val - 1,
                         ))
                         .with_x_advance_device(VariationIndex::new(u16::MAX - val, u16::MAX - val));
-                    PairValueRecord::new(GlyphId::new(gid2), valrec, valrec2)
+                    PairValueRecord::new(GlyphId16::new(gid2), valrec, valrec2)
                 })
                 .collect();
             pairsets.push(PairSet::new(records));
         }
 
-        let coverage = (1u16..=g1_count).map(GlyphId::new).collect();
+        let coverage = (1u16..=g1_count).map(GlyphId16::new).collect();
         PairPos::format_1(coverage, pairsets)
     }
 
@@ -670,8 +686,8 @@ mod tests {
 
     #[test]
     fn count_glyph_ranges() {
-        fn make_input(glyphs: &[u16]) -> BTreeSet<GlyphId> {
-            glyphs.iter().copied().map(GlyphId::new).collect()
+        fn make_input(glyphs: &[u16]) -> BTreeSet<GlyphId16> {
+            glyphs.iter().copied().map(GlyphId16::new).collect()
         }
 
         assert_eq!(count_num_ranges(&make_input(&[])), 0);
@@ -691,7 +707,7 @@ mod tests {
         (first_gid..first_gid + n_glyphs)
             .map(|gid| {
                 let class = (gid - 1) / n_glyphs_per_class;
-                (GlyphId::new(gid), class)
+                (GlyphId16::new(gid), class)
             })
             .collect()
     }
diff --git a/write-fonts/src/tables/cmap.rs b/write-fonts/src/tables/cmap.rs
index 8ba42531..b6492870 100644
--- a/write-fonts/src/tables/cmap.rs
+++ b/write-fonts/src/tables/cmap.rs
@@ -40,7 +40,12 @@ impl CmapSubtable {
 
         let mut prev = (u16::MAX - 1, u16::MAX - 1);
         for (cp, gid) in mappings {
-            let gid = gid.to_u16();
+            let gid = gid.to_u32();
+            if gid > 0xFFFF {
+                // Should we just fail here?
+                continue;
+            }
+            let gid = gid as u16;
             if *cp > '\u{FFFF}' {
                 // mappings is sorted, so the rest will be beyond the BMP too.
                 break;
@@ -126,7 +131,7 @@ impl CmapSubtable {
     fn create_format_12(mappings: &[(char, GlyphId)]) -> Self {
         let (mut char_codes, gids): (Vec<u32>, Vec<u32>) = mappings
             .iter()
-            .map(|(cp, gid)| (*cp as u32, gid.to_u16() as u32))
+            .map(|(cp, gid)| (*cp as u32, gid.to_u32()))
             .unzip();
         let cmap: HashMap<_, _> = char_codes.iter().cloned().zip(gids).collect();
         char_codes.dedup();
@@ -329,7 +334,7 @@ mod tests {
             .chain(30..=90)
             .chain(153..=480)
             .enumerate()
-            .map(|(idx, codepoint)| (char::from_u32(codepoint).unwrap(), GlyphId::new(idx as u16)))
+            .map(|(idx, codepoint)| (char::from_u32(codepoint).unwrap(), GlyphId::new(idx as u32)))
             .collect()
     }
 
@@ -519,7 +524,7 @@ mod tests {
         let mut mappings = non_bmp_cmap_mappings();
         // add an additional mapping to a different glyphId
         let (ch, gid1) = mappings[0];
-        let gid2 = GlyphId::new(gid1.to_u16() + 1);
+        let gid2 = GlyphId::new(gid1.to_u32() + 1);
         mappings.push((ch, gid2));
 
         let result = write::Cmap::from_mappings(mappings);
diff --git a/write-fonts/src/tables/glyf/composite.rs b/write-fonts/src/tables/glyf/composite.rs
index 0ca6a3ba..85461350 100644
--- a/write-fonts/src/tables/glyf/composite.rs
+++ b/write-fonts/src/tables/glyf/composite.rs
@@ -5,7 +5,7 @@ use crate::{
     FontWrite,
 };
 
-use read_fonts::{tables::glyf::CompositeGlyphFlags, types::GlyphId, FontRead};
+use read_fonts::{tables::glyf::CompositeGlyphFlags, types::GlyphId16, FontRead};
 
 use super::Bbox;
 
@@ -22,7 +22,7 @@ pub struct CompositeGlyph {
 /// A single component glyph (part of a [`CompositeGlyph`]).
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Component {
-    pub glyph: GlyphId,
+    pub glyph: GlyphId16,
     pub anchor: Anchor,
     pub flags: ComponentFlags,
     pub transform: Transform,
@@ -95,7 +95,7 @@ impl<'a> FontRead<'a> for CompositeGlyph {
 impl Component {
     /// Create a new component.
     pub fn new(
-        glyph: GlyphId,
+        glyph: GlyphId16,
         anchor: Anchor,
         transform: Transform,
         flags: impl Into<ComponentFlags>,
diff --git a/write-fonts/src/tables/glyf/glyf_loca_builder.rs b/write-fonts/src/tables/glyf/glyf_loca_builder.rs
index be401ae6..8df8cd92 100644
--- a/write-fonts/src/tables/glyf/glyf_loca_builder.rs
+++ b/write-fonts/src/tables/glyf/glyf_loca_builder.rs
@@ -124,7 +124,7 @@ mod tests {
 
         let mut glyph3 = CompositeGlyph::new(
             Component::new(
-                gid1,
+                gid1.try_into().unwrap(),
                 Anchor::Offset { x: 0, y: 0 },
                 Transform::default(),
                 ComponentFlags::default(),
@@ -133,7 +133,7 @@ mod tests {
         );
         glyph3.add_component(
             Component::new(
-                gid2,
+                gid2.try_into().unwrap(),
                 Anchor::Offset { x: 0, y: 0 },
                 Transform::default(),
                 ComponentFlags::default(),
diff --git a/write-fonts/src/tables/gpos.rs b/write-fonts/src/tables/gpos.rs
index e2e9e614..01a8e629 100644
--- a/write-fonts/src/tables/gpos.rs
+++ b/write-fonts/src/tables/gpos.rs
@@ -408,8 +408,8 @@ mod tests {
     // adapted from/motivated by https://github.com/fonttools/fonttools/issues/471
     #[test]
     fn gpos_1_zero() {
-        let cov_one = CoverageTable::format_1(vec![GlyphId::new(2)]);
-        let cov_two = CoverageTable::format_1(vec![GlyphId::new(4)]);
+        let cov_one = CoverageTable::format_1(vec![GlyphId16::new(2)]);
+        let cov_two = CoverageTable::format_1(vec![GlyphId16::new(4)]);
         let sub1 = SinglePos::format_1(cov_one, ValueRecord::default());
         let sub2 = SinglePos::format_1(cov_two, ValueRecord::default().with_x_advance(500));
         let lookup = Lookup::new(LookupFlag::default(), vec![sub1, sub2]);
@@ -453,9 +453,9 @@ mod tests {
 
     #[test]
     fn compile_devices_pairpos2() {
-        let class1 = ClassDef::from_iter([(GlyphId::new(5), 0), (GlyphId::new(6), 1)]);
+        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
         // class 0 is 'all the rest', here, always implicitly present
-        let class2 = ClassDef::from_iter([(GlyphId::new(8), 1)]);
+        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
 
         // two c1recs, each with two c2recs
         let class1recs = vec![
@@ -498,9 +498,9 @@ mod tests {
     #[should_panic(expected = "all value records should report the same format")]
     #[test]
     fn validate_bad_pairpos2() {
-        let class1 = ClassDef::from_iter([(GlyphId::new(5), 0), (GlyphId::new(6), 1)]);
+        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
         // class 0 is 'all the rest', here, always implicitly present
-        let class2 = ClassDef::from_iter([(GlyphId::new(8), 1)]);
+        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
         let coverage = class1.iter().map(|(gid, _)| gid).collect();
 
         // two c1recs, each with two c2recs
@@ -521,17 +521,17 @@ mod tests {
 
     #[test]
     fn validate_pairpos1() {
-        let coverage: CoverageTable = [1, 2].into_iter().map(GlyphId::new).collect();
+        let coverage: CoverageTable = [1, 2].into_iter().map(GlyphId16::new).collect();
         let good_table = PairPosFormat1::new(
             coverage.clone(),
             vec![
                 PairSet::new(vec![PairValueRecord::new(
-                    GlyphId::new(5),
+                    GlyphId16::new(5),
                     ValueRecord::new().with_x_advance(5),
                     ValueRecord::new(),
                 )]),
                 PairSet::new(vec![PairValueRecord::new(
-                    GlyphId::new(1),
+                    GlyphId16::new(1),
                     ValueRecord::new().with_x_advance(42),
                     ValueRecord::new(),
                 )]),
@@ -542,12 +542,12 @@ mod tests {
             coverage,
             vec![
                 PairSet::new(vec![PairValueRecord::new(
-                    GlyphId::new(5),
+                    GlyphId16::new(5),
                     ValueRecord::new().with_x_advance(5),
                     ValueRecord::new(),
                 )]),
                 PairSet::new(vec![PairValueRecord::new(
-                    GlyphId::new(1),
+                    GlyphId16::new(1),
                     //this is a different format, which is not okay
                     ValueRecord::new().with_x_placement(42),
                     ValueRecord::new(),
diff --git a/write-fonts/src/tables/gvar.rs b/write-fonts/src/tables/gvar.rs
index 04cc56c1..03c1a532 100644
--- a/write-fonts/src/tables/gvar.rs
+++ b/write-fonts/src/tables/gvar.rs
@@ -1219,7 +1219,7 @@ mod tests {
 
         let test_data = make_31_bytes_of_variation_data();
         let a_small_number_of_variations = (0..n_glyphs)
-            .map(|i| GlyphVariations::new(GlyphId::new(i), test_data.clone()))
+            .map(|i| GlyphVariations::new(GlyphId::from(i), test_data.clone()))
             .collect();
 
         let gvar = Gvar::new(a_small_number_of_variations, 2).unwrap();
diff --git a/write-fonts/src/tables/layout.rs b/write-fonts/src/tables/layout.rs
index 11194563..3c97e8ce 100644
--- a/write-fonts/src/tables/layout.rs
+++ b/write-fonts/src/tables/layout.rs
@@ -255,10 +255,10 @@ impl FromObjRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams
 impl FromTableRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams {}
 
 impl ClassDefFormat1 {
-    fn iter(&self) -> impl Iterator<Item = (GlyphId, u16)> + '_ {
+    fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
         self.class_value_array.iter().enumerate().map(|(i, cls)| {
             (
-                GlyphId::new(self.start_glyph_id.to_u16().saturating_add(i as u16)),
+                GlyphId16::new(self.start_glyph_id.to_u16().saturating_add(i as u16)),
                 *cls,
             )
         })
@@ -277,16 +277,16 @@ impl ClassRangeRecord {
 }
 
 impl ClassDefFormat2 {
-    fn iter(&self) -> impl Iterator<Item = (GlyphId, u16)> + '_ {
+    fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
         self.class_range_records.iter().flat_map(|rcd| {
             (rcd.start_glyph_id.to_u16()..=rcd.end_glyph_id.to_u16())
-                .map(|gid| (GlyphId::new(gid), rcd.class))
+                .map(|gid| (GlyphId16::new(gid), rcd.class))
         })
     }
 }
 
 impl ClassDef {
-    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, u16)> + '_ {
+    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
         let (one, two) = match self {
             Self::Format1(table) => (Some(table.iter()), None),
             Self::Format2(table) => (None, Some(table.iter())),
@@ -298,12 +298,12 @@ impl ClassDef {
     /// Return the glyph class for the provided glyph.
     ///
     /// Glyphs which have not been assigned a class are given class 0
-    pub fn get(&self, glyph: GlyphId) -> u16 {
+    pub fn get(&self, glyph: GlyphId16) -> u16 {
         self.get_raw(glyph).unwrap_or(0)
     }
 
     // exposed for testing
-    fn get_raw(&self, glyph: GlyphId) -> Option<u16> {
+    fn get_raw(&self, glyph: GlyphId16) -> Option<u16> {
         match self {
             ClassDef::Format1(table) => glyph
                 .to_u16()
@@ -329,7 +329,7 @@ impl ClassDef {
 }
 
 impl CoverageFormat1 {
-    fn iter(&self) -> impl Iterator<Item = GlyphId> + '_ {
+    fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
         self.glyph_array.iter().copied()
     }
 
@@ -339,7 +339,7 @@ impl CoverageFormat1 {
 }
 
 impl CoverageFormat2 {
-    fn iter(&self) -> impl Iterator<Item = GlyphId> + '_ {
+    fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
         self.range_records
             .iter()
             .flat_map(|rcd| iter_gids(rcd.start_glyph_id, rcd.end_glyph_id))
@@ -359,7 +359,7 @@ impl CoverageFormat2 {
 }
 
 impl CoverageTable {
-    pub fn iter(&self) -> impl Iterator<Item = GlyphId> + '_ {
+    pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
         let (one, two) = match self {
             Self::Format1(table) => (Some(table.iter()), None),
             Self::Format2(table) => (None, Some(table.iter())),
@@ -385,7 +385,7 @@ impl CoverageTable {
 /// This will choose the best format based for the included glyphs.
 #[derive(Debug, PartialEq, Eq)]
 pub struct ClassDefBuilder {
-    pub items: BTreeMap<GlyphId, u16>,
+    pub items: BTreeMap<GlyphId16, u16>,
 }
 
 /// A builder for [CoverageTable] tables.
@@ -394,18 +394,18 @@ pub struct ClassDefBuilder {
 #[derive(Debug, Default, PartialEq, Eq)]
 pub struct CoverageTableBuilder {
     // invariant: is always sorted
-    glyphs: Vec<GlyphId>,
+    glyphs: Vec<GlyphId16>,
 }
 
-impl FromIterator<GlyphId> for CoverageTableBuilder {
-    fn from_iter<T: IntoIterator<Item = GlyphId>>(iter: T) -> Self {
+impl FromIterator<GlyphId16> for CoverageTableBuilder {
+    fn from_iter<T: IntoIterator<Item = GlyphId16>>(iter: T) -> Self {
         let glyphs = iter.into_iter().collect::<Vec<_>>();
         CoverageTableBuilder::from_glyphs(glyphs)
     }
 }
 
-impl FromIterator<GlyphId> for CoverageTable {
-    fn from_iter<T: IntoIterator<Item = GlyphId>>(iter: T) -> Self {
+impl FromIterator<GlyphId16> for CoverageTable {
+    fn from_iter<T: IntoIterator<Item = GlyphId16>>(iter: T) -> Self {
         let glyphs = iter.into_iter().collect::<Vec<_>>();
         CoverageTableBuilder::from_glyphs(glyphs).build()
     }
@@ -413,7 +413,7 @@ impl FromIterator<GlyphId> for CoverageTable {
 
 impl CoverageTableBuilder {
     /// Create a new builder from a vec of `GlyphId`.
-    pub fn from_glyphs(mut glyphs: Vec<GlyphId>) -> Self {
+    pub fn from_glyphs(mut glyphs: Vec<GlyphId16>) -> Self {
         glyphs.sort_unstable();
         glyphs.dedup();
         CoverageTableBuilder { glyphs }
@@ -424,7 +424,7 @@ impl CoverageTableBuilder {
     /// Returns the coverage index of the added glyph.
     ///
     /// If the glyph already exists, this returns its current index.
-    pub fn add(&mut self, glyph: GlyphId) -> u16 {
+    pub fn add(&mut self, glyph: GlyphId16) -> u16 {
         match self.glyphs.binary_search(&glyph) {
             Ok(ix) => ix as u16,
             Err(ix) => {
@@ -451,16 +451,16 @@ impl CoverageTableBuilder {
     }
 }
 
-impl FromIterator<(GlyphId, u16)> for ClassDefBuilder {
-    fn from_iter<T: IntoIterator<Item = (GlyphId, u16)>>(iter: T) -> Self {
+impl FromIterator<(GlyphId16, u16)> for ClassDefBuilder {
+    fn from_iter<T: IntoIterator<Item = (GlyphId16, u16)>>(iter: T) -> Self {
         Self {
             items: iter.into_iter().filter(|(_, cls)| *cls != 0).collect(),
         }
     }
 }
 
-impl FromIterator<(GlyphId, u16)> for ClassDef {
-    fn from_iter<T: IntoIterator<Item = (GlyphId, u16)>>(iter: T) -> Self {
+impl FromIterator<(GlyphId16, u16)> for ClassDef {
+    fn from_iter<T: IntoIterator<Item = (GlyphId16, u16)>>(iter: T) -> Self {
         ClassDefBuilder::from_iter(iter).build()
     }
 }
@@ -491,10 +491,15 @@ impl ClassDefBuilder {
             let first = self.items.keys().next().map(|g| g.to_u16()).unwrap_or(0);
             let last = self.items.keys().next_back().map(|g| g.to_u16());
             let class_value_array = (first..=last.unwrap_or_default())
-                .map(|g| self.items.get(&GlyphId::new(g)).copied().unwrap_or(0))
+                .map(|g| self.items.get(&GlyphId16::new(g)).copied().unwrap_or(0))
                 .collect();
             ClassDef::Format1(ClassDefFormat1 {
-                start_glyph_id: self.items.keys().next().copied().unwrap_or(GlyphId::NOTDEF),
+                start_glyph_id: self
+                    .items
+                    .keys()
+                    .next()
+                    .copied()
+                    .unwrap_or(GlyphId16::NOTDEF),
                 class_value_array,
             })
         } else {
@@ -506,7 +511,7 @@ impl ClassDefBuilder {
 }
 
 fn iter_class_ranges(
-    values: &BTreeMap<GlyphId, u16>,
+    values: &BTreeMap<GlyphId16, u16>,
 ) -> impl Iterator<Item = ClassRangeRecord> + '_ {
     let mut iter = values.iter();
     let mut prev = None;
@@ -538,7 +543,7 @@ fn iter_class_ranges(
     })
 }
 
-fn should_choose_coverage_format_2(glyphs: &[GlyphId]) -> bool {
+fn should_choose_coverage_format_2(glyphs: &[GlyphId16]) -> bool {
     let format2_len = 4 + RangeRecord::iter_for_glyphs(glyphs).count() * 6;
     let format1_len = 4 + glyphs.len() * 2;
     format2_len < format1_len
@@ -550,7 +555,7 @@ impl RangeRecord {
     /// # Note
     ///
     /// this function expects that glyphs are already sorted.
-    pub fn iter_for_glyphs(glyphs: &[GlyphId]) -> impl Iterator<Item = RangeRecord> + '_ {
+    pub fn iter_for_glyphs(glyphs: &[GlyphId16]) -> impl Iterator<Item = RangeRecord> + '_ {
         let mut cur_range = glyphs.first().copied().map(|g| (g, g));
         let mut len = 0u16;
         let mut iter = glyphs.iter().skip(1).copied();
@@ -584,11 +589,11 @@ impl RangeRecord {
     }
 }
 
-fn iter_gids(gid1: GlyphId, gid2: GlyphId) -> impl Iterator<Item = GlyphId> {
-    (gid1.to_u16()..=gid2.to_u16()).map(GlyphId::new)
+fn iter_gids(gid1: GlyphId16, gid2: GlyphId16) -> impl Iterator<Item = GlyphId16> {
+    (gid1.to_u16()..=gid2.to_u16()).map(GlyphId16::new)
 }
 
-fn are_sequential(gid1: GlyphId, gid2: GlyphId) -> bool {
+fn are_sequential(gid1: GlyphId16, gid2: GlyphId16) -> bool {
     gid2.to_u16().saturating_sub(gid1.to_u16()) == 1
 }
 
@@ -693,8 +698,8 @@ mod tests {
     #[should_panic(expected = "larger than end_glyph_id")]
     fn validate_classdef_ranges() {
         let classdef = ClassDefFormat2::new(vec![ClassRangeRecord::new(
-            GlyphId::new(12),
-            GlyphId::new(3),
+            GlyphId16::new(12),
+            GlyphId16::new(3),
             7,
         )]);
 
@@ -704,14 +709,14 @@ mod tests {
     #[test]
     fn classdef_format() {
         let builder: ClassDefBuilder = [(3u16, 4u16), (4, 6), (5, 1), (9, 5), (10, 2), (11, 3)]
-            .map(|(gid, cls)| (GlyphId::new(gid), cls))
+            .map(|(gid, cls)| (GlyphId16::new(gid), cls))
             .into_iter()
             .collect();
 
         assert!(builder.prefer_format_1());
 
         let builder: ClassDefBuilder = [(1u16, 1u16), (3, 4), (9, 5), (10, 2), (11, 3)]
-            .map(|(gid, cls)| (GlyphId::new(gid), cls))
+            .map(|(gid, cls)| (GlyphId16::new(gid), cls))
             .into_iter()
             .collect();
 
@@ -724,8 +729,8 @@ mod tests {
             start: u16,
             end: u16,
             cls: u16,
-        ) -> impl Iterator<Item = (GlyphId, u16)> {
-            (start..=end).map(move |gid| (GlyphId::new(gid), cls))
+        ) -> impl Iterator<Item = (GlyphId16, u16)> {
+            (start..=end).map(move |gid| (GlyphId16::new(gid), cls))
         }
 
         // 3 ranges of 4 glyphs at 6 bytes a range should be smaller than writing
@@ -757,8 +762,8 @@ mod tests {
         assert_eq!(result[0], 0x5540_u16);
     }
 
-    fn make_glyph_vec<const N: usize>(gids: [u16; N]) -> Vec<GlyphId> {
-        gids.into_iter().map(GlyphId::new).collect()
+    fn make_glyph_vec<const N: usize>(gids: [u16; N]) -> Vec<GlyphId16> {
+        gids.into_iter().map(GlyphId16::new).collect()
     }
 
     #[test]
@@ -772,7 +777,7 @@ mod tests {
     fn make_class<const N: usize>(gid_class_pairs: [(u16, u16); N]) -> ClassDef {
         gid_class_pairs
             .iter()
-            .map(|(gid, cls)| (GlyphId::new(*gid), *cls))
+            .map(|(gid, cls)| (GlyphId16::new(*gid), *cls))
             .collect::<ClassDefBuilder>()
             .build()
     }
@@ -781,9 +786,9 @@ mod tests {
     fn class_def_builder_zero() {
         // even if class 0 is provided, we don't need to assign explicit entries for it
         let class = make_class([(4, 0), (5, 1)]);
-        assert!(class.get_raw(GlyphId::new(4)).is_none());
-        assert_eq!(class.get_raw(GlyphId::new(5)), Some(1));
-        assert!(class.get_raw(GlyphId::new(100)).is_none());
+        assert!(class.get_raw(GlyphId16::new(4)).is_none());
+        assert_eq!(class.get_raw(GlyphId16::new(5)), Some(1));
+        assert!(class.get_raw(GlyphId16::new(100)).is_none());
     }
 
     // https://github.com/googlefonts/fontations/issues/923
@@ -809,8 +814,8 @@ mod tests {
             class,
             ClassDef::Format2(ClassDefFormat2 {
                 class_range_records: vec![ClassRangeRecord {
-                    start_glyph_id: GlyphId::new(1),
-                    end_glyph_id: GlyphId::new(3),
+                    start_glyph_id: GlyphId16::new(1),
+                    end_glyph_id: GlyphId16::new(3),
                     class: 1
                 }]
             })
diff --git a/write-fonts/src/tables/post.rs b/write-fonts/src/tables/post.rs
index 120c8a3e..fe16c739 100644
--- a/write-fonts/src/tables/post.rs
+++ b/write-fonts/src/tables/post.rs
@@ -95,8 +95,8 @@ mod tests {
         let dumped = crate::dump_table(&post).unwrap();
         let loaded = read_fonts::tables::post::Post::read(FontData::new(&dumped)).unwrap();
         assert_eq!(loaded.version(), Version16Dot16::VERSION_2_0);
-        assert_eq!(loaded.glyph_name(GlyphId::new(1)), Some("A"));
-        assert_eq!(loaded.glyph_name(GlyphId::new(4)), Some("flarb"));
-        assert_eq!(loaded.glyph_name(GlyphId::new(5)), Some("C"));
+        assert_eq!(loaded.glyph_name(GlyphId16::new(1)), Some("A"));
+        assert_eq!(loaded.glyph_name(GlyphId16::new(4)), Some("flarb"));
+        assert_eq!(loaded.glyph_name(GlyphId16::new(5)), Some("C"));
     }
 }
diff --git a/write-fonts/src/tables/value_record.rs b/write-fonts/src/tables/value_record.rs
index 17a91948..4862e225 100644
--- a/write-fonts/src/tables/value_record.rs
+++ b/write-fonts/src/tables/value_record.rs
@@ -226,7 +226,7 @@ impl FromObjRef<read_fonts::tables::gpos::ValueRecord> for ValueRecord {
 
 #[cfg(test)]
 mod tests {
-    use font_types::GlyphId;
+    use font_types::GlyphId16;
     use read_fonts::FontRead;
 
     use crate::tables::{
@@ -254,7 +254,7 @@ mod tests {
     fn compile_devices() {
         let my_record = ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, 0xee));
         let a_table = SinglePos::format_1(
-            CoverageTableBuilder::from_glyphs(vec![GlyphId::new(42)]).build(),
+            CoverageTableBuilder::from_glyphs(vec![GlyphId16::new(42)]).build(),
             my_record,
         );
 
@@ -288,7 +288,7 @@ mod tests {
         let format = ValueFormat::X_ADVANCE | ValueFormat::X_ADVANCE_DEVICE;
 
         let a_table = SinglePos::format_2(
-            [GlyphId::new(7), GlyphId::new(9)].into_iter().collect(),
+            [GlyphId16::new(7), GlyphId16::new(9)].into_iter().collect(),
             vec![
                 ValueRecord::new()
                     .with_explicit_value_format(format)
diff --git a/write-fonts/src/write.rs b/write-fonts/src/write.rs
index 310b825b..b9519f33 100644
--- a/write-fonts/src/write.rs
+++ b/write-fonts/src/write.rs
@@ -306,7 +306,7 @@ write_be_bytes!(types::LongDateTime);
 write_be_bytes!(types::Tag);
 write_be_bytes!(types::Version16Dot16);
 write_be_bytes!(types::MajorMinor);
-write_be_bytes!(types::GlyphId);
+write_be_bytes!(types::GlyphId16);
 write_be_bytes!(types::NameId);
 
 impl<T: FontWrite> FontWrite for [T] {


Test patch:
diff --git a/font-types/src/serde_test.rs b/font-types/src/serde_test.rs
index fe802f0f..926946a2 100644
--- a/font-types/src/serde_test.rs
+++ b/font-types/src/serde_test.rs
@@ -9,7 +9,7 @@ fn test_serde() {
         f1: Fixed,
         f2: F26Dot6,
         f3: F2Dot14,
-        gid: GlyphId,
+        gid: GlyphId16,
         date: LongDateTime,
         name_id: NameId,
         offset: Offset16,
@@ -23,7 +23,7 @@ fn test_serde() {
         f1: Fixed::from_f64(521.5),
         f2: F26Dot6::from_f64(-1001.1),
         f3: F2Dot14::from_f32(1.2),
-        gid: GlyphId::new(69),
+        gid: GlyphId16::new(69),
         date: LongDateTime::new(1_234_569_101),
         name_id: NameId::new(8214),
         offset: Offset16::new(42),
diff --git a/read-fonts/generated/generated_test_formats.rs b/read-fonts/generated/generated_test_formats.rs
index 7c04da12..37f5e3a0 100644
--- a/read-fonts/generated/generated_test_formats.rs
+++ b/read-fonts/generated/generated_test_formats.rs
@@ -229,6 +229,15 @@ pub enum MyTable<'a> {
 }
 
 impl<'a> MyTable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::MyFormat22(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     pub fn format(&self) -> u16 {
         match self {
             Self::Format1(item) => item.format(),
diff --git a/read-fonts/src/tests/test_gdef.rs b/read-fonts/src/tests/test_gdef.rs
index 8bdc7491..b7770a01 100644
--- a/read-fonts/src/tests/test_gdef.rs
+++ b/read-fonts/src/tests/test_gdef.rs
@@ -1,4 +1,4 @@
-use types::{GlyphId, MajorMinor};
+use types::{GlyphId16, MajorMinor};
 
 use super::*;
 use crate::tables::layout::{ClassDefFormat2, DeltaFormat, DeviceOrVariationIndex};
@@ -16,8 +16,8 @@ fn glyph_class_def_table() {
     let table = ClassDefFormat2::read(test_data::GLYPHCLASSDEF_TABLE.into()).unwrap();
     assert_eq!(table.class_range_count(), 4);
     let last_record = &table.class_range_records()[3];
-    assert_eq!(last_record.start_glyph_id(), GlyphId::new(0x18f));
-    assert_eq!(last_record.end_glyph_id(), GlyphId::new(0x18f));
+    assert_eq!(last_record.start_glyph_id(), GlyphId16::new(0x18f));
+    assert_eq!(last_record.end_glyph_id(), GlyphId16::new(0x18f));
 }
 
 #[test]
diff --git a/read-fonts/src/tests/test_gpos.rs b/read-fonts/src/tests/test_gpos.rs
index a20cf69d..2ed36917 100644
--- a/read-fonts/src/tests/test_gpos.rs
+++ b/read-fonts/src/tests/test_gpos.rs
@@ -53,12 +53,12 @@ fn pairposformat1() {
     let rec1 = set1.pair_value_records().get(0).unwrap();
     let rec2 = set2.pair_value_records().get(0).unwrap();
 
-    assert_eq!(rec1.second_glyph(), GlyphId::new(0x59));
+    assert_eq!(rec1.second_glyph(), GlyphId16::new(0x59));
     assert_eq!(rec1.value_record1().x_advance(), Some(-30));
     assert!(rec1.value_record1().x_placement().is_none());
     assert_eq!(rec1.value_record2().x_placement(), Some(-20));
 
-    assert_eq!(rec2.second_glyph(), GlyphId::new(0x59));
+    assert_eq!(rec2.second_glyph(), GlyphId16::new(0x59));
     assert_eq!(rec2.value_record1().x_advance(), Some(-40));
     assert_eq!(rec2.value_record2().x_placement(), Some(-25));
 }
@@ -79,7 +79,7 @@ fn pairposformat2() {
         ClassDef::Format2(cls) => {
             assert_eq!(
                 cls.class_range_records()[0].start_glyph_id.get(),
-                GlyphId::new(0x6A)
+                GlyphId16::new(0x6A)
             );
         }
     }
diff --git a/read-fonts/src/tests/test_gsub.rs b/read-fonts/src/tests/test_gsub.rs
index eb0f38cf..a193fdf0 100644
--- a/read-fonts/src/tests/test_gsub.rs
+++ b/read-fonts/src/tests/test_gsub.rs
@@ -15,10 +15,10 @@ fn singlesubstformat2() {
     assert_eq!(
         table.substitute_glyph_ids(),
         &[
-            GlyphId::new(305),
-            GlyphId::new(309),
-            GlyphId::new(318),
-            GlyphId::new(323)
+            GlyphId16::new(305),
+            GlyphId16::new(309),
+            GlyphId16::new(318),
+            GlyphId16::new(323)
         ],
     );
 }
@@ -31,7 +31,7 @@ fn multiplesubstformat1() {
     let seq0 = table.sequences().get(0).unwrap();
     assert_eq!(
         seq0.substitute_glyph_ids(),
-        &[GlyphId::new(26), GlyphId::new(26), GlyphId::new(29)]
+        &[GlyphId16::new(26), GlyphId16::new(26), GlyphId16::new(29)]
     );
 }
 
@@ -43,7 +43,7 @@ fn alternatesubstformat1() {
     let altset0 = table.alternate_sets().get(0).unwrap();
     assert_eq!(
         altset0.alternate_glyph_ids(),
-        &[GlyphId::new(0xc9), GlyphId::new(0xca)]
+        &[GlyphId16::new(0xc9), GlyphId16::new(0xca)]
     );
 }
 
@@ -56,18 +56,18 @@ fn ligaturesubstformat1() {
 
     assert_eq!(ligset0.ligatures().len(), 1);
     let lig0 = ligset0.ligatures().get(0).unwrap();
-    assert_eq!(lig0.ligature_glyph(), GlyphId::new(347));
+    assert_eq!(lig0.ligature_glyph(), GlyphId16::new(347));
     assert_eq!(
         lig0.component_glyph_ids(),
-        &[GlyphId::new(0x28), GlyphId::new(0x17)]
+        &[GlyphId16::new(0x28), GlyphId16::new(0x17)]
     );
 
     let ligset1 = table.ligature_sets().get(1).unwrap();
     let lig0 = ligset1.ligatures().get(0).unwrap();
-    assert_eq!(lig0.ligature_glyph(), GlyphId::new(0xf1));
+    assert_eq!(lig0.ligature_glyph(), GlyphId16::new(0xf1));
     assert_eq!(
         lig0.component_glyph_ids(),
-        &[GlyphId::new(0x1a), GlyphId::new(0x1d)]
+        &[GlyphId16::new(0x1a), GlyphId16::new(0x1d)]
     );
 }
 
diff --git a/skrifa/src/color/traversal_tests/mod.rs b/skrifa/src/color/traversal_tests/mod.rs
index 87b2e84b..6dc5a266 100644
--- a/skrifa/src/color/traversal_tests/mod.rs
+++ b/skrifa/src/color/traversal_tests/mod.rs
@@ -26,7 +26,7 @@ use crate::{
 
 #[derive(Serialize, Deserialize, Default, PartialEq)]
 struct PaintDump {
-    glyph_id: u16,
+    glyph_id: u32,
     ops: Vec<PaintOps>,
 }
 
@@ -153,7 +153,7 @@ enum PaintOps {
     },
     PopTransform,
     PushClipGlyph {
-        gid: u16,
+        gid: u32,
     },
     PushClipBox {
         clip_box: BoundingBox<f32>,
@@ -163,7 +163,7 @@ enum PaintOps {
         brush: BrushParams,
     },
     FillGlyph {
-        gid: u16,
+        gid: u32,
         transform: DumpTransform,
         brush: BrushParams,
     },
@@ -185,7 +185,7 @@ impl ColorPainter for PaintDump {
 
     fn push_clip_glyph(&mut self, glyph: GlyphId) {
         self.ops.push(PaintOps::PushClipGlyph {
-            gid: glyph.to_u16(),
+            gid: glyph.to_u32(),
         });
     }
 
@@ -205,7 +205,7 @@ impl ColorPainter for PaintDump {
 
     fn fill_glyph(&mut self, glyph_id: GlyphId, transform: Option<Transform>, brush: Brush) {
         self.ops.push(PaintOps::FillGlyph {
-            gid: glyph_id.to_u16(),
+            gid: glyph_id.to_u32(),
             transform: transform.unwrap_or_default().into(),
             brush: brush.into(),
         });
@@ -222,7 +222,7 @@ impl ColorPainter for PaintDump {
 }
 
 impl PaintDump {
-    pub fn new(gid: u16) -> Self {
+    pub fn new(gid: u32) -> Self {
         Self {
             glyph_id: gid,
             ..Default::default()
@@ -277,7 +277,7 @@ fn colrv1_traversal_test(
         .map(|codepoint| font.charmap().map(*codepoint).unwrap());
 
     let paint_dumps_iter = test_gids.map(|gid| {
-        let mut color_painter = PaintDump::new(gid.to_u16());
+        let mut color_painter = PaintDump::new(gid.to_u32());
 
         let color_glyph = font.color_glyphs().get_with_format(gid, required_format);