diff --git a/font-codegen/src/fields.rs b/font-codegen/src/fields.rs index 20ec12434..ccce0039d 100644 --- a/font-codegen/src/fields.rs +++ b/font-codegen/src/fields.rs @@ -7,8 +7,8 @@ use quote::{quote, ToTokens}; use syn::spanned::Spanned; use super::parsing::{ - logged_syn_error, Attr, Count, CustomCompile, Field, FieldReadArgs, FieldType, FieldValidation, - Fields, NeededWhen, OffsetTarget, Phase, Record, ReferencedFields, + logged_syn_error, Attr, Count, CountArg, CustomCompile, Field, FieldReadArgs, FieldType, + FieldValidation, Fields, NeededWhen, OffsetTarget, Phase, Record, ReferencedFields, }; impl Fields { @@ -401,8 +401,9 @@ fn traversal_arm_for_field( let data = fld.offset_getter_data_src(); quote!(Field::new(#name_str, traversal::FieldType::var_array(#typ_str, self.#name()#maybe_unwrap, #data))) } - // HACK: who wouldn't want to hard-code ValueRecord handling - FieldType::Struct { typ } if typ == "ValueRecord" => { + // See if there are better ways to handle these hardcoded types + // + FieldType::Struct { typ } if typ == "ValueRecord" || typ == "SbitLineMetrics" => { let offset_data = pass_data .cloned() .unwrap_or_else(|| fld.offset_getter_data_src()); @@ -475,10 +476,16 @@ impl Field { } pub(crate) fn is_zerocopy_compatible(&self) -> bool { - matches!( - self.typ, - FieldType::Scalar { .. } | FieldType::Offset { .. } - ) + // hack: we want to add `FieldType::Struct` here but don't want to + // catch `ValueRecord` so use this attribute to ignore it. + // Fields that require args for reading can't be read "zerocopy" + // anyway. + // + self.attrs.read_with_args.is_none() + && matches!( + self.typ, + FieldType::Scalar { .. } | FieldType::Offset { .. } | FieldType::Struct { .. } + ) } pub(crate) fn is_array(&self) -> bool { @@ -1012,7 +1019,13 @@ impl Field { } _ => unreachable!("count not valid here"), }; - quote!( #count_expr * #size_expr ) + match other { + Count::SingleArg(CountArg::Literal(lit)) if lit.base10_digits() == "1" => { + // Prevent identity-op clippy error with `1 * size` + size_expr + } + _ => quote!( #count_expr * #size_expr ), + } } None => quote!(compile_error!("missing count attribute?")), }; diff --git a/font-codegen/src/record.rs b/font-codegen/src/record.rs index 273b02122..ddf5763ea 100644 --- a/font-codegen/src/record.rs +++ b/font-codegen/src/record.rs @@ -52,10 +52,17 @@ pub(crate) fn generate(item: &Record, all_items: &Items) -> syn::Result + if item.name == "SbitLineMetrics" { + use quote::TokenStreamExt; + let mut copy_trait = quote!(Copy,); + copy_trait.append_all(maybe_extra_traits.unwrap_or_default()); + maybe_extra_traits = Some(copy_trait); + } Ok(quote! { #( #docs )* #[derive(Clone, Debug, #maybe_extra_traits)] diff --git a/font-test-data/src/lib.rs b/font-test-data/src/lib.rs index cff1e07cf..dbc3ccc69 100644 --- a/font-test-data/src/lib.rs +++ b/font-test-data/src/lib.rs @@ -37,6 +37,8 @@ pub static CANTARELL_VF_TRIMMED_GLYPHS: &str = pub static CHARSTRING_PATH_OPS: &[u8] = include_bytes!("../test_data/ttf/charstring_path_ops.ttf"); +pub static EMBEDDED_BITMAPS: &[u8] = include_bytes!("../test_data/ttf/embedded_bitmaps.ttf"); + pub mod post { #[rustfmt::skip] diff --git a/font-test-data/test_data/ttf/embedded_bitmaps.ttf b/font-test-data/test_data/ttf/embedded_bitmaps.ttf new file mode 100644 index 000000000..9a06b939f Binary files /dev/null and b/font-test-data/test_data/ttf/embedded_bitmaps.ttf differ diff --git a/font-test-data/test_data/ttx/embedded_bitmaps.ttx b/font-test-data/test_data/ttx/embedded_bitmaps.ttx new file mode 100644 index 000000000..06a4b2696 --- /dev/null +++ b/font-test-data/test_data/ttx/embedded_bitmaps.ttx @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + eeaeea + + + + + + + + + + + + f0f0f0f0 + + + + + + + aabbccdd 00112233 ffee1234 424242aa + 88990011 + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + 89504e47 0d0a1a0a + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/read-fonts/generated/generated_bitmap.rs b/read-fonts/generated/generated_bitmap.rs new file mode 100644 index 000000000..e9144dfcd --- /dev/null +++ b/read-fonts/generated/generated_bitmap.rs @@ -0,0 +1,1564 @@ +// THIS FILE IS AUTOGENERATED. +// Any changes to this file will be overwritten. +// For more information about how codegen works, see font-codegen/README.md + +#[allow(unused_imports)] +use crate::codegen_prelude::*; + +/// [BitmapSize](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#bitmapsize-record) record. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +#[repr(packed)] +pub struct BitmapSize { + /// Offset to index subtable from beginning of EBLC/CBLC. + pub index_subtable_array_offset: BigEndian, + /// Number of bytes in corresponding index subtables and array. + pub index_tables_size: BigEndian, + /// There is an index subtable for each range or format change. + pub number_of_index_subtables: BigEndian, + /// Not used; set to 0. + pub color_ref: BigEndian, + /// Line metrics for text rendered horizontally. + pub hori: SbitLineMetrics, + /// Line metrics for text rendered vertically. + pub vert: SbitLineMetrics, + /// Lowest glyph index for this size. + pub start_glyph_index: BigEndian, + /// Highest glyph index for this size. + pub end_glyph_index: BigEndian, + /// Horizontal pixels per em. + pub ppem_x: u8, + /// Vertical pixels per em. + pub ppem_y: u8, + /// The Microsoft rasterizer v.1.7 or greater supports the following + /// bitDepth values, as described below: 1, 2, 4, and 8 (and 32 for CBLC). + pub bit_depth: u8, + /// Vertical or horizontal. + pub flags: BigEndian, +} + +impl BitmapSize { + /// Offset to index subtable from beginning of EBLC/CBLC. + pub fn index_subtable_array_offset(&self) -> u32 { + self.index_subtable_array_offset.get() + } + + /// Number of bytes in corresponding index subtables and array. + pub fn index_tables_size(&self) -> u32 { + self.index_tables_size.get() + } + + /// There is an index subtable for each range or format change. + pub fn number_of_index_subtables(&self) -> u32 { + self.number_of_index_subtables.get() + } + + /// Not used; set to 0. + pub fn color_ref(&self) -> u32 { + self.color_ref.get() + } + + /// Line metrics for text rendered horizontally. + pub fn hori(&self) -> &SbitLineMetrics { + &self.hori + } + + /// Line metrics for text rendered vertically. + pub fn vert(&self) -> &SbitLineMetrics { + &self.vert + } + + /// Lowest glyph index for this size. + pub fn start_glyph_index(&self) -> GlyphId { + self.start_glyph_index.get() + } + + /// Highest glyph index for this size. + pub fn end_glyph_index(&self) -> GlyphId { + self.end_glyph_index.get() + } + + /// Horizontal pixels per em. + pub fn ppem_x(&self) -> u8 { + self.ppem_x + } + + /// Vertical pixels per em. + pub fn ppem_y(&self) -> u8 { + self.ppem_y + } + + /// The Microsoft rasterizer v.1.7 or greater supports the following + /// bitDepth values, as described below: 1, 2, 4, and 8 (and 32 for CBLC). + pub fn bit_depth(&self) -> u8 { + self.bit_depth + } + + /// Vertical or horizontal. + pub fn flags(&self) -> BitmapFlags { + self.flags.get() + } +} + +impl FixedSize for BitmapSize { + const RAW_BYTE_LEN: usize = u32::RAW_BYTE_LEN + + u32::RAW_BYTE_LEN + + u32::RAW_BYTE_LEN + + u32::RAW_BYTE_LEN + + SbitLineMetrics::RAW_BYTE_LEN + + SbitLineMetrics::RAW_BYTE_LEN + + GlyphId::RAW_BYTE_LEN + + GlyphId::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + BitmapFlags::RAW_BYTE_LEN; +} + +impl sealed::Sealed for BitmapSize {} + +/// SAFETY: see the [`FromBytes`] trait documentation. +unsafe impl FromBytes for BitmapSize { + fn this_trait_should_only_be_implemented_in_generated_code() {} +} + +#[cfg(feature = "traversal")] +impl<'a> SomeRecord<'a> for BitmapSize { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "BitmapSize", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new( + "index_subtable_array_offset", + self.index_subtable_array_offset(), + )), + 1usize => Some(Field::new("index_tables_size", self.index_tables_size())), + 2usize => Some(Field::new( + "number_of_index_subtables", + self.number_of_index_subtables(), + )), + 3usize => Some(Field::new("color_ref", self.color_ref())), + 4usize => Some(Field::new("hori", self.hori().traversal_type(_data))), + 5usize => Some(Field::new("vert", self.vert().traversal_type(_data))), + 6usize => Some(Field::new("start_glyph_index", self.start_glyph_index())), + 7usize => Some(Field::new("end_glyph_index", self.end_glyph_index())), + 8usize => Some(Field::new("ppem_x", self.ppem_x())), + 9usize => Some(Field::new("ppem_y", self.ppem_y())), + 10usize => Some(Field::new("bit_depth", self.bit_depth())), + 11usize => Some(Field::new("flags", self.flags())), + _ => None, + }), + data, + } + } +} + +/// [SbitLineMetrics](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#sbitlinemetrics-record) record. +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +#[repr(packed)] +pub struct SbitLineMetrics { + pub ascender: BigEndian, + pub descender: BigEndian, + pub width_max: u8, + pub caret_slope_numerator: BigEndian, + pub caret_slope_denominator: u8, + pub caret_offset: BigEndian, + pub min_origin_sb: BigEndian, + pub min_advance_sb: BigEndian, + pub max_before_bl: BigEndian, + pub min_after_bl: BigEndian, + pub pad1: BigEndian, + pub pad2: BigEndian, +} + +impl SbitLineMetrics { + pub fn ascender(&self) -> i8 { + self.ascender.get() + } + + pub fn descender(&self) -> i8 { + self.descender.get() + } + + pub fn width_max(&self) -> u8 { + self.width_max + } + + pub fn caret_slope_numerator(&self) -> i8 { + self.caret_slope_numerator.get() + } + + pub fn caret_slope_denominator(&self) -> u8 { + self.caret_slope_denominator + } + + pub fn caret_offset(&self) -> i8 { + self.caret_offset.get() + } + + pub fn min_origin_sb(&self) -> i8 { + self.min_origin_sb.get() + } + + pub fn min_advance_sb(&self) -> i8 { + self.min_advance_sb.get() + } + + pub fn max_before_bl(&self) -> i8 { + self.max_before_bl.get() + } + + pub fn min_after_bl(&self) -> i8 { + self.min_after_bl.get() + } + + pub fn pad1(&self) -> i8 { + self.pad1.get() + } + + pub fn pad2(&self) -> i8 { + self.pad2.get() + } +} + +impl FixedSize for SbitLineMetrics { + const RAW_BYTE_LEN: usize = i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN; +} + +impl sealed::Sealed for SbitLineMetrics {} + +/// SAFETY: see the [`FromBytes`] trait documentation. +unsafe impl FromBytes for SbitLineMetrics { + fn this_trait_should_only_be_implemented_in_generated_code() {} +} + +#[cfg(feature = "traversal")] +impl<'a> SomeRecord<'a> for SbitLineMetrics { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "SbitLineMetrics", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("ascender", self.ascender())), + 1usize => Some(Field::new("descender", self.descender())), + 2usize => Some(Field::new("width_max", self.width_max())), + 3usize => Some(Field::new( + "caret_slope_numerator", + self.caret_slope_numerator(), + )), + 4usize => Some(Field::new( + "caret_slope_denominator", + self.caret_slope_denominator(), + )), + 5usize => Some(Field::new("caret_offset", self.caret_offset())), + 6usize => Some(Field::new("min_origin_sb", self.min_origin_sb())), + 7usize => Some(Field::new("min_advance_sb", self.min_advance_sb())), + 8usize => Some(Field::new("max_before_bl", self.max_before_bl())), + 9usize => Some(Field::new("min_after_bl", self.min_after_bl())), + 10usize => Some(Field::new("pad1", self.pad1())), + 11usize => Some(Field::new("pad2", self.pad2())), + _ => None, + }), + data, + } + } +} + +/// [Bitmap flags](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#bitmap-flags). +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BitmapFlags { + bits: u8, +} + +impl BitmapFlags { + /// Horizontal + pub const HORIZONTAL_METRICS: Self = Self { bits: 0x01 }; + + /// Vertical + pub const VERTICAL_METRICS: Self = Self { bits: 0x02 }; +} + +impl BitmapFlags { + /// Returns an empty set of flags. + #[inline] + pub const fn empty() -> Self { + Self { bits: 0 } + } + + /// Returns the set containing all flags. + #[inline] + pub const fn all() -> Self { + Self { + bits: Self::HORIZONTAL_METRICS.bits | Self::VERTICAL_METRICS.bits, + } + } + + /// Returns the raw value of the flags currently stored. + #[inline] + pub const fn bits(&self) -> u8 { + self.bits + } + + /// Convert from underlying bit representation, unless that + /// representation contains bits that do not correspond to a flag. + #[inline] + pub const fn from_bits(bits: u8) -> Option { + if (bits & !Self::all().bits()) == 0 { + Some(Self { bits }) + } else { + None + } + } + + /// Convert from underlying bit representation, dropping any bits + /// that do not correspond to flags. + #[inline] + pub const fn from_bits_truncate(bits: u8) -> Self { + Self { + bits: bits & Self::all().bits, + } + } + + /// Returns `true` if no flags are currently stored. + #[inline] + pub const fn is_empty(&self) -> bool { + self.bits() == Self::empty().bits() + } + + /// Returns `true` if there are flags common to both `self` and `other`. + #[inline] + pub const fn intersects(&self, other: Self) -> bool { + !(Self { + bits: self.bits & other.bits, + }) + .is_empty() + } + + /// Returns `true` if all of the flags in `other` are contained within `self`. + #[inline] + pub const fn contains(&self, other: Self) -> bool { + (self.bits & other.bits) == other.bits + } + + /// Inserts the specified flags in-place. + #[inline] + pub fn insert(&mut self, other: Self) { + self.bits |= other.bits; + } + + /// Removes the specified flags in-place. + #[inline] + pub fn remove(&mut self, other: Self) { + self.bits &= !other.bits; + } + + /// Toggles the specified flags in-place. + #[inline] + pub fn toggle(&mut self, other: Self) { + self.bits ^= other.bits; + } + + /// Returns the intersection between the flags in `self` and + /// `other`. + /// + /// Specifically, the returned set contains only the flags which are + /// present in *both* `self` *and* `other`. + /// + /// This is equivalent to using the `&` operator (e.g. + /// [`ops::BitAnd`]), as in `flags & other`. + /// + /// [`ops::BitAnd`]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html + #[inline] + #[must_use] + pub const fn intersection(self, other: Self) -> Self { + Self { + bits: self.bits & other.bits, + } + } + + /// Returns the union of between the flags in `self` and `other`. + /// + /// Specifically, the returned set contains all flags which are + /// present in *either* `self` *or* `other`, including any which are + /// present in both. + /// + /// This is equivalent to using the `|` operator (e.g. + /// [`ops::BitOr`]), as in `flags | other`. + /// + /// [`ops::BitOr`]: https://doc.rust-lang.org/std/ops/trait.BitOr.html + #[inline] + #[must_use] + pub const fn union(self, other: Self) -> Self { + Self { + bits: self.bits | other.bits, + } + } + + /// Returns the difference between the flags in `self` and `other`. + /// + /// Specifically, the returned set contains all flags present in + /// `self`, except for the ones present in `other`. + /// + /// It is also conceptually equivalent to the "bit-clear" operation: + /// `flags & !other` (and this syntax is also supported). + /// + /// This is equivalent to using the `-` operator (e.g. + /// [`ops::Sub`]), as in `flags - other`. + /// + /// [`ops::Sub`]: https://doc.rust-lang.org/std/ops/trait.Sub.html + #[inline] + #[must_use] + pub const fn difference(self, other: Self) -> Self { + Self { + bits: self.bits & !other.bits, + } + } +} + +impl std::ops::BitOr for BitmapFlags { + type Output = Self; + + /// Returns the union of the two sets of flags. + #[inline] + fn bitor(self, other: BitmapFlags) -> Self { + Self { + bits: self.bits | other.bits, + } + } +} + +impl std::ops::BitOrAssign for BitmapFlags { + /// Adds the set of flags. + #[inline] + fn bitor_assign(&mut self, other: Self) { + self.bits |= other.bits; + } +} + +impl std::ops::BitXor for BitmapFlags { + type Output = Self; + + /// Returns the left flags, but with all the right flags toggled. + #[inline] + fn bitxor(self, other: Self) -> Self { + Self { + bits: self.bits ^ other.bits, + } + } +} + +impl std::ops::BitXorAssign for BitmapFlags { + /// Toggles the set of flags. + #[inline] + fn bitxor_assign(&mut self, other: Self) { + self.bits ^= other.bits; + } +} + +impl std::ops::BitAnd for BitmapFlags { + type Output = Self; + + /// Returns the intersection between the two sets of flags. + #[inline] + fn bitand(self, other: Self) -> Self { + Self { + bits: self.bits & other.bits, + } + } +} + +impl std::ops::BitAndAssign for BitmapFlags { + /// Disables all flags disabled in the set. + #[inline] + fn bitand_assign(&mut self, other: Self) { + self.bits &= other.bits; + } +} + +impl std::ops::Sub for BitmapFlags { + type Output = Self; + + /// Returns the set difference of the two sets of flags. + #[inline] + fn sub(self, other: Self) -> Self { + Self { + bits: self.bits & !other.bits, + } + } +} + +impl std::ops::SubAssign for BitmapFlags { + /// Disables all flags enabled in the set. + #[inline] + fn sub_assign(&mut self, other: Self) { + self.bits &= !other.bits; + } +} + +impl std::ops::Not for BitmapFlags { + type Output = Self; + + /// Returns the complement of this set of flags. + #[inline] + fn not(self) -> Self { + Self { bits: !self.bits } & Self::all() + } +} + +impl std::fmt::Debug for BitmapFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let members: &[(&str, Self)] = &[ + ("HORIZONTAL_METRICS", Self::HORIZONTAL_METRICS), + ("VERTICAL_METRICS", Self::VERTICAL_METRICS), + ]; + let mut first = true; + for (name, value) in members { + if self.contains(*value) { + if !first { + f.write_str(" | ")?; + } + first = false; + f.write_str(name)?; + } + } + if first { + f.write_str("(empty)")?; + } + Ok(()) + } +} + +impl std::fmt::Binary for BitmapFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Binary::fmt(&self.bits, f) + } +} + +impl std::fmt::Octal for BitmapFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Octal::fmt(&self.bits, f) + } +} + +impl std::fmt::LowerHex for BitmapFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.bits, f) + } +} + +impl std::fmt::UpperHex for BitmapFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::UpperHex::fmt(&self.bits, f) + } +} + +impl font_types::Scalar for BitmapFlags { + type Raw = ::Raw; + fn to_raw(self) -> Self::Raw { + self.bits().to_raw() + } + fn from_raw(raw: Self::Raw) -> Self { + let t = ::from_raw(raw); + Self::from_bits_truncate(t) + } +} + +#[cfg(feature = "traversal")] +impl<'a> From for FieldType<'a> { + fn from(src: BitmapFlags) -> FieldType<'a> { + src.bits().into() + } +} + +/// [BigGlyphMetrics](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#bigglyphmetrics) record. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +#[repr(packed)] +pub struct BigGlyphMetrics { + /// Number of rows of data. + pub height: u8, + /// Number of columns of data. + pub width: u8, + /// Distance in pixels from the horizontal origin to the left edge of the bitmap. + pub hori_bearing_x: BigEndian, + /// Distance in pixels from the horizontal origin to the top edge of the bitmap. + pub hori_bearing_y: BigEndian, + /// Horizontal advance width in pixels. + pub hori_advance: u8, + /// Distance in pixels from the vertical origin to the left edge of the bitmap. + pub vert_bearing_x: BigEndian, + /// Distance in pixels from the vertical origin to the top edge of the bitmap. + pub vert_bearing_y: BigEndian, + /// Vertical advance width in pixels. + pub vert_advance: u8, +} + +impl BigGlyphMetrics { + /// Number of rows of data. + pub fn height(&self) -> u8 { + self.height + } + + /// Number of columns of data. + pub fn width(&self) -> u8 { + self.width + } + + /// Distance in pixels from the horizontal origin to the left edge of the bitmap. + pub fn hori_bearing_x(&self) -> i8 { + self.hori_bearing_x.get() + } + + /// Distance in pixels from the horizontal origin to the top edge of the bitmap. + pub fn hori_bearing_y(&self) -> i8 { + self.hori_bearing_y.get() + } + + /// Horizontal advance width in pixels. + pub fn hori_advance(&self) -> u8 { + self.hori_advance + } + + /// Distance in pixels from the vertical origin to the left edge of the bitmap. + pub fn vert_bearing_x(&self) -> i8 { + self.vert_bearing_x.get() + } + + /// Distance in pixels from the vertical origin to the top edge of the bitmap. + pub fn vert_bearing_y(&self) -> i8 { + self.vert_bearing_y.get() + } + + /// Vertical advance width in pixels. + pub fn vert_advance(&self) -> u8 { + self.vert_advance + } +} + +impl FixedSize for BigGlyphMetrics { + const RAW_BYTE_LEN: usize = u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN; +} + +impl sealed::Sealed for BigGlyphMetrics {} + +/// SAFETY: see the [`FromBytes`] trait documentation. +unsafe impl FromBytes for BigGlyphMetrics { + fn this_trait_should_only_be_implemented_in_generated_code() {} +} + +#[cfg(feature = "traversal")] +impl<'a> SomeRecord<'a> for BigGlyphMetrics { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "BigGlyphMetrics", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("height", self.height())), + 1usize => Some(Field::new("width", self.width())), + 2usize => Some(Field::new("hori_bearing_x", self.hori_bearing_x())), + 3usize => Some(Field::new("hori_bearing_y", self.hori_bearing_y())), + 4usize => Some(Field::new("hori_advance", self.hori_advance())), + 5usize => Some(Field::new("vert_bearing_x", self.vert_bearing_x())), + 6usize => Some(Field::new("vert_bearing_y", self.vert_bearing_y())), + 7usize => Some(Field::new("vert_advance", self.vert_advance())), + _ => None, + }), + data, + } + } +} + +/// [SmallGlyphMetrics](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#smallglyphmetrics) record. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +#[repr(packed)] +pub struct SmallGlyphMetrics { + /// Number of rows of data. + pub height: u8, + /// Number of columns of data. + pub width: u8, + /// Distance in pixels from the horizontal origin to the left edge of the bitmap (for horizontal text); or distance in pixels from the vertical origin to the top edge of the bitmap (for vertical text). + pub bearing_x: BigEndian, + /// Distance in pixels from the horizontal origin to the top edge of the bitmap (for horizontal text); or distance in pixels from the vertical origin to the left edge of the bitmap (for vertical text). + pub bearing_y: BigEndian, + /// Horizontal or vertical advance width in pixels. + pub advance: u8, +} + +impl SmallGlyphMetrics { + /// Number of rows of data. + pub fn height(&self) -> u8 { + self.height + } + + /// Number of columns of data. + pub fn width(&self) -> u8 { + self.width + } + + /// Distance in pixels from the horizontal origin to the left edge of the bitmap (for horizontal text); or distance in pixels from the vertical origin to the top edge of the bitmap (for vertical text). + pub fn bearing_x(&self) -> i8 { + self.bearing_x.get() + } + + /// Distance in pixels from the horizontal origin to the top edge of the bitmap (for horizontal text); or distance in pixels from the vertical origin to the left edge of the bitmap (for vertical text). + pub fn bearing_y(&self) -> i8 { + self.bearing_y.get() + } + + /// Horizontal or vertical advance width in pixels. + pub fn advance(&self) -> u8 { + self.advance + } +} + +impl FixedSize for SmallGlyphMetrics { + const RAW_BYTE_LEN: usize = u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + i8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN; +} + +impl sealed::Sealed for SmallGlyphMetrics {} + +/// SAFETY: see the [`FromBytes`] trait documentation. +unsafe impl FromBytes for SmallGlyphMetrics { + fn this_trait_should_only_be_implemented_in_generated_code() {} +} + +#[cfg(feature = "traversal")] +impl<'a> SomeRecord<'a> for SmallGlyphMetrics { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "SmallGlyphMetrics", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("height", self.height())), + 1usize => Some(Field::new("width", self.width())), + 2usize => Some(Field::new("bearing_x", self.bearing_x())), + 3usize => Some(Field::new("bearing_y", self.bearing_y())), + 4usize => Some(Field::new("advance", self.advance())), + _ => None, + }), + data, + } + } +} + +/// [IndexSubtableArray](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtablearray) table. +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct IndexSubtableArrayMarker {} + +impl IndexSubtableArrayMarker { + fn first_glyph_index_byte_range(&self) -> Range { + let start = 0; + start..start + GlyphId::RAW_BYTE_LEN + } + fn last_glyph_index_byte_range(&self) -> Range { + let start = self.first_glyph_index_byte_range().end; + start..start + GlyphId::RAW_BYTE_LEN + } + fn additional_offset_to_index_subtable_byte_range(&self) -> Range { + let start = self.last_glyph_index_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } +} + +impl<'a> FontRead<'a> for IndexSubtableArray<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + cursor.finish(IndexSubtableArrayMarker {}) + } +} + +/// [IndexSubtableArray](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtablearray) table. +pub type IndexSubtableArray<'a> = TableRef<'a, IndexSubtableArrayMarker>; + +impl<'a> IndexSubtableArray<'a> { + /// First glyph ID of this range. + pub fn first_glyph_index(&self) -> GlyphId { + 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 { + let range = self.shape.last_glyph_index_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Add to indexSubTableArrayOffset to get offset from beginning of EBLC. + pub fn additional_offset_to_index_subtable(&self) -> u32 { + let range = self.shape.additional_offset_to_index_subtable_byte_range(); + self.data.read_at(range.start).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for IndexSubtableArray<'a> { + fn type_name(&self) -> &str { + "IndexSubtableArray" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("first_glyph_index", self.first_glyph_index())), + 1usize => Some(Field::new("last_glyph_index", self.last_glyph_index())), + 2usize => Some(Field::new( + "additional_offset_to_index_subtable", + self.additional_offset_to_index_subtable(), + )), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for IndexSubtableArray<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +/// [IndexSubtables](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtables) format type. +pub enum IndexSubtable<'a> { + Format1(IndexSubtable1<'a>), + Format2(IndexSubtable2<'a>), + Format3(IndexSubtable3<'a>), + Format4(IndexSubtable4<'a>), + Format5(IndexSubtable5<'a>), +} + +impl<'a> FontRead<'a> for IndexSubtable<'a> { + fn read(data: FontData<'a>) -> Result { + let format: u16 = data.read_at(0usize)?; + match format { + IndexSubtable1Marker::FORMAT => Ok(Self::Format1(FontRead::read(data)?)), + IndexSubtable2Marker::FORMAT => Ok(Self::Format2(FontRead::read(data)?)), + IndexSubtable3Marker::FORMAT => Ok(Self::Format3(FontRead::read(data)?)), + IndexSubtable4Marker::FORMAT => Ok(Self::Format4(FontRead::read(data)?)), + IndexSubtable5Marker::FORMAT => Ok(Self::Format5(FontRead::read(data)?)), + other => Err(ReadError::InvalidFormat(other.into())), + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> IndexSubtable<'a> { + fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> { + match self { + Self::Format1(table) => table, + Self::Format2(table) => table, + Self::Format3(table) => table, + Self::Format4(table) => table, + Self::Format5(table) => table, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for IndexSubtable<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.dyn_inner().fmt(f) + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for IndexSubtable<'a> { + fn type_name(&self) -> &str { + self.dyn_inner().type_name() + } + fn get_field(&self, idx: usize) -> Option> { + self.dyn_inner().get_field(idx) + } +} + +impl Format for IndexSubtable1Marker { + const FORMAT: u16 = 1; +} + +/// [IndexSubTable1](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable1-variable-metrics-glyphs-with-4-byte-offsets): variable-metrics glyphs with 4-byte offsets. +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct IndexSubtable1Marker { + sbit_offsets_byte_len: usize, +} + +impl IndexSubtable1Marker { + fn index_format_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn image_format_byte_range(&self) -> Range { + let start = self.index_format_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn image_data_offset_byte_range(&self) -> Range { + let start = self.image_format_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn sbit_offsets_byte_range(&self) -> Range { + let start = self.image_data_offset_byte_range().end; + start..start + self.sbit_offsets_byte_len + } +} + +impl<'a> FontRead<'a> for IndexSubtable1<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let sbit_offsets_byte_len = cursor.remaining_bytes(); + cursor.advance_by(sbit_offsets_byte_len); + cursor.finish(IndexSubtable1Marker { + sbit_offsets_byte_len, + }) + } +} + +/// [IndexSubTable1](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable1-variable-metrics-glyphs-with-4-byte-offsets): variable-metrics glyphs with 4-byte offsets. +pub type IndexSubtable1<'a> = TableRef<'a, IndexSubtable1Marker>; + +impl<'a> IndexSubtable1<'a> { + /// Format of this IndexSubTable. + pub fn index_format(&self) -> u16 { + let range = self.shape.index_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Format of EBDT image data. + pub fn image_format(&self) -> u16 { + let range = self.shape.image_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Offset to image data in EBDT table. + pub fn image_data_offset(&self) -> u32 { + let range = self.shape.image_data_offset_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn sbit_offsets(&self) -> &'a [BigEndian] { + let range = self.shape.sbit_offsets_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for IndexSubtable1<'a> { + fn type_name(&self) -> &str { + "IndexSubtable1" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("index_format", self.index_format())), + 1usize => Some(Field::new("image_format", self.image_format())), + 2usize => Some(Field::new("image_data_offset", self.image_data_offset())), + 3usize => Some(Field::new("sbit_offsets", self.sbit_offsets())), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for IndexSubtable1<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +impl Format for IndexSubtable2Marker { + const FORMAT: u16 = 2; +} + +/// [IndexSubTable2](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable2-all-glyphs-have-identical-metrics): all glyphs have identical metrics. +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct IndexSubtable2Marker { + big_metrics_byte_len: usize, +} + +impl IndexSubtable2Marker { + fn index_format_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn image_format_byte_range(&self) -> Range { + let start = self.index_format_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn image_data_offset_byte_range(&self) -> Range { + let start = self.image_format_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn image_size_byte_range(&self) -> Range { + let start = self.image_data_offset_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn big_metrics_byte_range(&self) -> Range { + let start = self.image_size_byte_range().end; + start..start + self.big_metrics_byte_len + } +} + +impl<'a> FontRead<'a> for IndexSubtable2<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let big_metrics_byte_len = BigGlyphMetrics::RAW_BYTE_LEN; + cursor.advance_by(big_metrics_byte_len); + cursor.finish(IndexSubtable2Marker { + big_metrics_byte_len, + }) + } +} + +/// [IndexSubTable2](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable2-all-glyphs-have-identical-metrics): all glyphs have identical metrics. +pub type IndexSubtable2<'a> = TableRef<'a, IndexSubtable2Marker>; + +impl<'a> IndexSubtable2<'a> { + /// Format of this IndexSubTable. + pub fn index_format(&self) -> u16 { + let range = self.shape.index_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Format of EBDT image data. + pub fn image_format(&self) -> u16 { + let range = self.shape.image_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Offset to image data in EBDT table. + pub fn image_data_offset(&self) -> u32 { + let range = self.shape.image_data_offset_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// All the glyphs are of the same size. + pub fn image_size(&self) -> u32 { + let range = self.shape.image_size_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// All glyphs have the same metrics; glyph data may be compressed, byte-aligned, or bit-aligned. + pub fn big_metrics(&self) -> &'a [BigGlyphMetrics] { + let range = self.shape.big_metrics_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for IndexSubtable2<'a> { + fn type_name(&self) -> &str { + "IndexSubtable2" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("index_format", self.index_format())), + 1usize => Some(Field::new("image_format", self.image_format())), + 2usize => Some(Field::new("image_data_offset", self.image_data_offset())), + 3usize => Some(Field::new("image_size", self.image_size())), + 4usize => Some(Field::new( + "big_metrics", + traversal::FieldType::array_of_records( + stringify!(BigGlyphMetrics), + self.big_metrics(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for IndexSubtable2<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +impl Format for IndexSubtable3Marker { + const FORMAT: u16 = 3; +} + +/// [IndexSubTable3](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable3-variable-metrics-glyphs-with-2-byte-offsets): variable-metrics glyphs with 2-byte offsets. +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct IndexSubtable3Marker { + sbit_offsets_byte_len: usize, +} + +impl IndexSubtable3Marker { + fn index_format_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn image_format_byte_range(&self) -> Range { + let start = self.index_format_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn image_data_offset_byte_range(&self) -> Range { + let start = self.image_format_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn sbit_offsets_byte_range(&self) -> Range { + let start = self.image_data_offset_byte_range().end; + start..start + self.sbit_offsets_byte_len + } +} + +impl<'a> FontRead<'a> for IndexSubtable3<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let sbit_offsets_byte_len = cursor.remaining_bytes(); + cursor.advance_by(sbit_offsets_byte_len); + cursor.finish(IndexSubtable3Marker { + sbit_offsets_byte_len, + }) + } +} + +/// [IndexSubTable3](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable3-variable-metrics-glyphs-with-2-byte-offsets): variable-metrics glyphs with 2-byte offsets. +pub type IndexSubtable3<'a> = TableRef<'a, IndexSubtable3Marker>; + +impl<'a> IndexSubtable3<'a> { + /// Format of this IndexSubTable. + pub fn index_format(&self) -> u16 { + let range = self.shape.index_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Format of EBDT image data. + pub fn image_format(&self) -> u16 { + let range = self.shape.image_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Offset to image data in EBDT table. + pub fn image_data_offset(&self) -> u32 { + let range = self.shape.image_data_offset_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn sbit_offsets(&self) -> &'a [BigEndian] { + let range = self.shape.sbit_offsets_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for IndexSubtable3<'a> { + fn type_name(&self) -> &str { + "IndexSubtable3" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("index_format", self.index_format())), + 1usize => Some(Field::new("image_format", self.image_format())), + 2usize => Some(Field::new("image_data_offset", self.image_data_offset())), + 3usize => Some(Field::new("sbit_offsets", self.sbit_offsets())), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for IndexSubtable3<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +impl Format for IndexSubtable4Marker { + const FORMAT: u16 = 4; +} + +/// [IndexSubTable4](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable3-variable-metrics-glyphs-with-2-byte-offsets): variable-metrics glyphs with sparse glyph codes. +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct IndexSubtable4Marker { + glyph_array_byte_len: usize, +} + +impl IndexSubtable4Marker { + fn index_format_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn image_format_byte_range(&self) -> Range { + let start = self.index_format_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn image_data_offset_byte_range(&self) -> Range { + let start = self.image_format_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn num_glyphs_byte_range(&self) -> Range { + let start = self.image_data_offset_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn glyph_array_byte_range(&self) -> Range { + let start = self.num_glyphs_byte_range().end; + start..start + self.glyph_array_byte_len + } +} + +impl<'a> FontRead<'a> for IndexSubtable4<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let num_glyphs: u32 = cursor.read()?; + let glyph_array_byte_len = + transforms::add(num_glyphs, 1_usize) * GlyphIdOffsetPair::RAW_BYTE_LEN; + cursor.advance_by(glyph_array_byte_len); + cursor.finish(IndexSubtable4Marker { + glyph_array_byte_len, + }) + } +} + +/// [IndexSubTable4](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable3-variable-metrics-glyphs-with-2-byte-offsets): variable-metrics glyphs with sparse glyph codes. +pub type IndexSubtable4<'a> = TableRef<'a, IndexSubtable4Marker>; + +impl<'a> IndexSubtable4<'a> { + /// Format of this IndexSubTable. + pub fn index_format(&self) -> u16 { + let range = self.shape.index_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Format of EBDT image data. + pub fn image_format(&self) -> u16 { + let range = self.shape.image_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Offset to image data in EBDT table. + pub fn image_data_offset(&self) -> u32 { + let range = self.shape.image_data_offset_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Array length. + pub fn num_glyphs(&self) -> u32 { + let range = self.shape.num_glyphs_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// One per glyph. + pub fn glyph_array(&self) -> &'a [GlyphIdOffsetPair] { + let range = self.shape.glyph_array_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for IndexSubtable4<'a> { + fn type_name(&self) -> &str { + "IndexSubtable4" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("index_format", self.index_format())), + 1usize => Some(Field::new("image_format", self.image_format())), + 2usize => Some(Field::new("image_data_offset", self.image_data_offset())), + 3usize => Some(Field::new("num_glyphs", self.num_glyphs())), + 4usize => Some(Field::new( + "glyph_array", + traversal::FieldType::array_of_records( + stringify!(GlyphIdOffsetPair), + self.glyph_array(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for IndexSubtable4<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +/// [GlyphIdOffsetPair](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#glyphidoffsetpair-record) record. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +#[repr(packed)] +pub struct GlyphIdOffsetPair { + /// Glyph ID of glyph present. + pub glyph_id: BigEndian, + /// Location in EBDT. + pub sbit_offset: BigEndian, +} + +impl GlyphIdOffsetPair { + /// Glyph ID of glyph present. + pub fn glyph_id(&self) -> GlyphId { + self.glyph_id.get() + } + + /// Location in EBDT. + pub fn sbit_offset(&self) -> u16 { + self.sbit_offset.get() + } +} + +impl FixedSize for GlyphIdOffsetPair { + const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + u16::RAW_BYTE_LEN; +} + +impl sealed::Sealed for GlyphIdOffsetPair {} + +/// SAFETY: see the [`FromBytes`] trait documentation. +unsafe impl FromBytes for GlyphIdOffsetPair { + fn this_trait_should_only_be_implemented_in_generated_code() {} +} + +#[cfg(feature = "traversal")] +impl<'a> SomeRecord<'a> for GlyphIdOffsetPair { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "GlyphIdOffsetPair", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("glyph_id", self.glyph_id())), + 1usize => Some(Field::new("sbit_offset", self.sbit_offset())), + _ => None, + }), + data, + } + } +} + +impl Format for IndexSubtable5Marker { + const FORMAT: u16 = 5; +} + +/// [IndexSubTable5](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable5-constant-metrics-glyphs-with-sparse-glyph-codes): constant-metrics glyphs with sparse glyph codes +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct IndexSubtable5Marker { + big_metrics_byte_len: usize, + glyph_array_byte_len: usize, +} + +impl IndexSubtable5Marker { + fn index_format_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn image_format_byte_range(&self) -> Range { + let start = self.index_format_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn image_data_offset_byte_range(&self) -> Range { + let start = self.image_format_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn image_size_byte_range(&self) -> Range { + let start = self.image_data_offset_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn big_metrics_byte_range(&self) -> Range { + let start = self.image_size_byte_range().end; + start..start + self.big_metrics_byte_len + } + fn num_glyphs_byte_range(&self) -> Range { + let start = self.big_metrics_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn glyph_array_byte_range(&self) -> Range { + let start = self.num_glyphs_byte_range().end; + start..start + self.glyph_array_byte_len + } +} + +impl<'a> FontRead<'a> for IndexSubtable5<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let big_metrics_byte_len = BigGlyphMetrics::RAW_BYTE_LEN; + cursor.advance_by(big_metrics_byte_len); + let num_glyphs: u32 = cursor.read()?; + let glyph_array_byte_len = num_glyphs as usize * GlyphId::RAW_BYTE_LEN; + cursor.advance_by(glyph_array_byte_len); + cursor.finish(IndexSubtable5Marker { + big_metrics_byte_len, + glyph_array_byte_len, + }) + } +} + +/// [IndexSubTable5](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable5-constant-metrics-glyphs-with-sparse-glyph-codes): constant-metrics glyphs with sparse glyph codes +pub type IndexSubtable5<'a> = TableRef<'a, IndexSubtable5Marker>; + +impl<'a> IndexSubtable5<'a> { + /// Format of this IndexSubTable. + pub fn index_format(&self) -> u16 { + let range = self.shape.index_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Format of EBDT image data. + pub fn image_format(&self) -> u16 { + let range = self.shape.image_format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Offset to image data in EBDT table. + pub fn image_data_offset(&self) -> u32 { + let range = self.shape.image_data_offset_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// All glyphs have the same data size. + pub fn image_size(&self) -> u32 { + let range = self.shape.image_size_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// All glyphs have the same metrics. + pub fn big_metrics(&self) -> &'a [BigGlyphMetrics] { + let range = self.shape.big_metrics_byte_range(); + self.data.read_array(range).unwrap() + } + + /// Array length. + pub fn num_glyphs(&self) -> u32 { + let range = self.shape.num_glyphs_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// One per glyph, sorted by glyhph ID. + pub fn glyph_array(&self) -> &'a [BigEndian] { + let range = self.shape.glyph_array_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for IndexSubtable5<'a> { + fn type_name(&self) -> &str { + "IndexSubtable5" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("index_format", self.index_format())), + 1usize => Some(Field::new("image_format", self.image_format())), + 2usize => Some(Field::new("image_data_offset", self.image_data_offset())), + 3usize => Some(Field::new("image_size", self.image_size())), + 4usize => Some(Field::new( + "big_metrics", + traversal::FieldType::array_of_records( + stringify!(BigGlyphMetrics), + self.big_metrics(), + self.offset_data(), + ), + )), + 5usize => Some(Field::new("num_glyphs", self.num_glyphs())), + 6usize => Some(Field::new("glyph_array", self.glyph_array())), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for IndexSubtable5<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +/// [EbdtComponent](https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#ebdtcomponent-record) record. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +#[repr(packed)] +pub struct BdtComponent { + /// Component glyph ID. + pub glyph_id: BigEndian, + /// Position of component left. + pub x_offset: BigEndian, + /// Position of component top. + pub y_offset: BigEndian, +} + +impl BdtComponent { + /// Component glyph ID. + pub fn glyph_id(&self) -> GlyphId { + self.glyph_id.get() + } + + /// Position of component left. + pub fn x_offset(&self) -> i8 { + self.x_offset.get() + } + + /// Position of component top. + pub fn y_offset(&self) -> i8 { + self.y_offset.get() + } +} + +impl FixedSize for BdtComponent { + const RAW_BYTE_LEN: usize = GlyphId::RAW_BYTE_LEN + i8::RAW_BYTE_LEN + i8::RAW_BYTE_LEN; +} + +impl sealed::Sealed for BdtComponent {} + +/// SAFETY: see the [`FromBytes`] trait documentation. +unsafe impl FromBytes for BdtComponent { + fn this_trait_should_only_be_implemented_in_generated_code() {} +} + +#[cfg(feature = "traversal")] +impl<'a> SomeRecord<'a> for BdtComponent { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "BdtComponent", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("glyph_id", self.glyph_id())), + 1usize => Some(Field::new("x_offset", self.x_offset())), + 2usize => Some(Field::new("y_offset", self.y_offset())), + _ => None, + }), + data, + } + } +} diff --git a/read-fonts/generated/generated_cbdt.rs b/read-fonts/generated/generated_cbdt.rs new file mode 100644 index 000000000..ed24ac511 --- /dev/null +++ b/read-fonts/generated/generated_cbdt.rs @@ -0,0 +1,74 @@ +// THIS FILE IS AUTOGENERATED. +// Any changes to this file will be overwritten. +// For more information about how codegen works, see font-codegen/README.md + +#[allow(unused_imports)] +use crate::codegen_prelude::*; + +/// The [Color Bitmap Data](https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt) table +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct CbdtMarker {} + +impl CbdtMarker { + fn major_version_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn minor_version_byte_range(&self) -> Range { + let start = self.major_version_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } +} + +impl TopLevelTable for Cbdt<'_> { + /// `CBDT` + const TAG: Tag = Tag::new(b"CBDT"); +} + +impl<'a> FontRead<'a> for Cbdt<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.finish(CbdtMarker {}) + } +} + +/// The [Color Bitmap Data](https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt) table +pub type Cbdt<'a> = TableRef<'a, CbdtMarker>; + +impl<'a> Cbdt<'a> { + /// Major version of the CBDT table, = 3. + pub fn major_version(&self) -> u16 { + let range = self.shape.major_version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Minor version of CBDT table, = 0. + pub fn minor_version(&self) -> u16 { + let range = self.shape.minor_version_byte_range(); + self.data.read_at(range.start).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for Cbdt<'a> { + fn type_name(&self) -> &str { + "Cbdt" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("major_version", self.major_version())), + 1usize => Some(Field::new("minor_version", self.minor_version())), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for Cbdt<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} diff --git a/read-fonts/generated/generated_cblc.rs b/read-fonts/generated/generated_cblc.rs new file mode 100644 index 000000000..2a9f1cb0a --- /dev/null +++ b/read-fonts/generated/generated_cblc.rs @@ -0,0 +1,110 @@ +// THIS FILE IS AUTOGENERATED. +// Any changes to this file will be overwritten. +// For more information about how codegen works, see font-codegen/README.md + +#[allow(unused_imports)] +use crate::codegen_prelude::*; + +/// The [Color Bitmap Location](https://learn.microsoft.com/en-us/typography/opentype/spec/cblc) table +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct CblcMarker { + bitmap_sizes_byte_len: usize, +} + +impl CblcMarker { + fn major_version_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn minor_version_byte_range(&self) -> Range { + let start = self.major_version_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn num_sizes_byte_range(&self) -> Range { + let start = self.minor_version_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn bitmap_sizes_byte_range(&self) -> Range { + let start = self.num_sizes_byte_range().end; + start..start + self.bitmap_sizes_byte_len + } +} + +impl TopLevelTable for Cblc<'_> { + /// `CBLC` + const TAG: Tag = Tag::new(b"CBLC"); +} + +impl<'a> FontRead<'a> for Cblc<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + let num_sizes: u32 = cursor.read()?; + let bitmap_sizes_byte_len = num_sizes as usize * BitmapSize::RAW_BYTE_LEN; + cursor.advance_by(bitmap_sizes_byte_len); + cursor.finish(CblcMarker { + bitmap_sizes_byte_len, + }) + } +} + +/// The [Color Bitmap Location](https://learn.microsoft.com/en-us/typography/opentype/spec/cblc) table +pub type Cblc<'a> = TableRef<'a, CblcMarker>; + +impl<'a> Cblc<'a> { + /// Major version of the CBLC table, = 3. + pub fn major_version(&self) -> u16 { + let range = self.shape.major_version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Minor version of CBLC table, = 0. + pub fn minor_version(&self) -> u16 { + let range = self.shape.minor_version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Number of BitmapSize records. + pub fn num_sizes(&self) -> u32 { + let range = self.shape.num_sizes_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// BitmapSize records array. + pub fn bitmap_sizes(&self) -> &'a [BitmapSize] { + let range = self.shape.bitmap_sizes_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for Cblc<'a> { + fn type_name(&self) -> &str { + "Cblc" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("major_version", self.major_version())), + 1usize => Some(Field::new("minor_version", self.minor_version())), + 2usize => Some(Field::new("num_sizes", self.num_sizes())), + 3usize => Some(Field::new( + "bitmap_sizes", + traversal::FieldType::array_of_records( + stringify!(BitmapSize), + self.bitmap_sizes(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for Cblc<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} diff --git a/read-fonts/generated/generated_ebdt.rs b/read-fonts/generated/generated_ebdt.rs new file mode 100644 index 000000000..3002c4532 --- /dev/null +++ b/read-fonts/generated/generated_ebdt.rs @@ -0,0 +1,74 @@ +// THIS FILE IS AUTOGENERATED. +// Any changes to this file will be overwritten. +// For more information about how codegen works, see font-codegen/README.md + +#[allow(unused_imports)] +use crate::codegen_prelude::*; + +/// The [Embedded Bitmap Data](https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt) table +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct EbdtMarker {} + +impl EbdtMarker { + fn major_version_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn minor_version_byte_range(&self) -> Range { + let start = self.major_version_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } +} + +impl TopLevelTable for Ebdt<'_> { + /// `EBDT` + const TAG: Tag = Tag::new(b"EBDT"); +} + +impl<'a> FontRead<'a> for Ebdt<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.finish(EbdtMarker {}) + } +} + +/// The [Embedded Bitmap Data](https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt) table +pub type Ebdt<'a> = TableRef<'a, EbdtMarker>; + +impl<'a> Ebdt<'a> { + /// Major version of the EBDT table, = 2. + pub fn major_version(&self) -> u16 { + let range = self.shape.major_version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Minor version of EBDT table, = 0. + pub fn minor_version(&self) -> u16 { + let range = self.shape.minor_version_byte_range(); + self.data.read_at(range.start).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for Ebdt<'a> { + fn type_name(&self) -> &str { + "Ebdt" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("major_version", self.major_version())), + 1usize => Some(Field::new("minor_version", self.minor_version())), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for Ebdt<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} diff --git a/read-fonts/generated/generated_eblc.rs b/read-fonts/generated/generated_eblc.rs new file mode 100644 index 000000000..f9ac0c8b3 --- /dev/null +++ b/read-fonts/generated/generated_eblc.rs @@ -0,0 +1,110 @@ +// THIS FILE IS AUTOGENERATED. +// Any changes to this file will be overwritten. +// For more information about how codegen works, see font-codegen/README.md + +#[allow(unused_imports)] +use crate::codegen_prelude::*; + +/// The [Embedded Bitmap Location](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc) table +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct EblcMarker { + bitmap_sizes_byte_len: usize, +} + +impl EblcMarker { + fn major_version_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn minor_version_byte_range(&self) -> Range { + let start = self.major_version_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn num_sizes_byte_range(&self) -> Range { + let start = self.minor_version_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + fn bitmap_sizes_byte_range(&self) -> Range { + let start = self.num_sizes_byte_range().end; + start..start + self.bitmap_sizes_byte_len + } +} + +impl TopLevelTable for Eblc<'_> { + /// `EBLC` + const TAG: Tag = Tag::new(b"EBLC"); +} + +impl<'a> FontRead<'a> for Eblc<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + let num_sizes: u32 = cursor.read()?; + let bitmap_sizes_byte_len = num_sizes as usize * BitmapSize::RAW_BYTE_LEN; + cursor.advance_by(bitmap_sizes_byte_len); + cursor.finish(EblcMarker { + bitmap_sizes_byte_len, + }) + } +} + +/// The [Embedded Bitmap Location](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc) table +pub type Eblc<'a> = TableRef<'a, EblcMarker>; + +impl<'a> Eblc<'a> { + /// Major version of the EBLC table, = 2. + pub fn major_version(&self) -> u16 { + let range = self.shape.major_version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Minor version of EBLC table, = 0. + pub fn minor_version(&self) -> u16 { + let range = self.shape.minor_version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Number of BitmapSize records. + pub fn num_sizes(&self) -> u32 { + let range = self.shape.num_sizes_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// BitmapSize records array. + pub fn bitmap_sizes(&self) -> &'a [BitmapSize] { + let range = self.shape.bitmap_sizes_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "traversal")] +impl<'a> SomeTable<'a> for Eblc<'a> { + fn type_name(&self) -> &str { + "Eblc" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("major_version", self.major_version())), + 1usize => Some(Field::new("minor_version", self.minor_version())), + 2usize => Some(Field::new("num_sizes", self.num_sizes())), + 3usize => Some(Field::new( + "bitmap_sizes", + traversal::FieldType::array_of_records( + stringify!(BitmapSize), + self.bitmap_sizes(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "traversal")] +impl<'a> std::fmt::Debug for Eblc<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} diff --git a/read-fonts/src/table_provider.rs b/read-fonts/src/table_provider.rs index c136b4658..2f246875b 100644 --- a/read-fonts/src/table_provider.rs +++ b/read-fonts/src/table_provider.rs @@ -138,6 +138,22 @@ pub trait TableProvider<'a> { self.expect_table() } + fn cblc(&self) -> Result, ReadError> { + self.expect_table() + } + + fn cbdt(&self) -> Result, ReadError> { + self.expect_table() + } + + fn eblc(&self) -> Result, ReadError> { + self.expect_table() + } + + fn ebdt(&self) -> Result, ReadError> { + self.expect_table() + } + fn sbix(&self) -> Result, ReadError> { // should we make the user pass this in? let num_glyphs = self.maxp().map(|maxp| maxp.num_glyphs())?; diff --git a/read-fonts/src/tables.rs b/read-fonts/src/tables.rs index ad4f53fa4..93e29bb7a 100644 --- a/read-fonts/src/tables.rs +++ b/read-fonts/src/tables.rs @@ -2,11 +2,16 @@ pub mod avar; pub mod base; +pub mod bitmap; +pub mod cbdt; +pub mod cblc; pub mod cff; pub mod cff2; pub mod cmap; pub mod colr; pub mod cpal; +pub mod ebdt; +pub mod eblc; pub mod fvar; pub mod gdef; pub mod glyf; diff --git a/read-fonts/src/tables/bitmap.rs b/read-fonts/src/tables/bitmap.rs new file mode 100644 index 000000000..aea1d7c2b --- /dev/null +++ b/read-fonts/src/tables/bitmap.rs @@ -0,0 +1,321 @@ +//! Common bitmap (EBLC/EBDT/CBLC/CBDT) types. + +include!("../../generated/generated_bitmap.rs"); + +impl BitmapSize { + /// Returns the bitmap location information for the given glyph. + /// + /// The `offset_data` parameter is provided by the `offset_data()` method + /// of the parent `Eblc` or `Cblc` table. + /// + /// The resulting [`BitmapLocation`] value is used by the `data()` method + /// in the associated `Ebdt` or `Cbdt` table to extract the bitmap data. + pub fn location( + &self, + offset_data: FontData, + glyph_id: GlyphId, + ) -> Result { + if !(self.start_glyph_index()..=self.end_glyph_index()).contains(&glyph_id) { + return Err(ReadError::OutOfBounds); + } + let mut location = BitmapLocation { + bit_depth: self.bit_depth, + ..BitmapLocation::default() + }; + for ix in 0..self.number_of_index_subtables() { + let subtable = self.subtable(offset_data, ix)?; + if !(subtable.first_glyph_index..=subtable.last_glyph_index).contains(&glyph_id) { + continue; + } + // 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; + match &subtable.kind { + IndexSubtable::Format1(st) => { + location.format = st.image_format(); + location.data_offset = st.image_data_offset() as usize + + st.sbit_offsets() + .get(glyph_ix) + .ok_or(ReadError::OutOfBounds)? + .get() as usize; + } + IndexSubtable::Format2(st) => { + location.format = st.image_format(); + let data_size = st.image_size() as usize; + location.data_size = Some(data_size); + location.data_offset = st.image_data_offset() as usize + glyph_ix * data_size; + location.metrics = Some(st.big_metrics()[0].clone()); + } + IndexSubtable::Format3(st) => { + location.format = st.image_format(); + location.data_offset = st.image_data_offset() as usize + + st.sbit_offsets() + .get(glyph_ix) + .ok_or(ReadError::OutOfBounds)? + .get() as usize; + } + 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)) { + Ok(ix) => ix, + _ => { + return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u16() as u32)) + } + }; + let offset1 = array[array_ix].sbit_offset() as usize; + let offset2 = array + .get(array_ix + 1) + .ok_or(ReadError::OutOfBounds)? + .sbit_offset() as usize; + location.data_offset = offset1; + location.data_size = Some(offset2 - offset1); + } + IndexSubtable::Format5(st) => { + location.format = st.image_format(); + let array = st.glyph_array(); + if array.binary_search_by(|x| x.get().cmp(&glyph_id)).is_err() { + return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u16() as u32)); + } + let data_size = st.image_size() as usize; + location.data_size = Some(data_size); + location.data_offset = st.image_data_offset() as usize + glyph_ix * data_size; + location.metrics = Some(st.big_metrics()[0].clone()); + } + } + return Ok(location); + } + Err(ReadError::OutOfBounds) + } + + fn subtable<'a>( + &self, + offset_data: FontData<'a>, + index: u32, + ) -> Result, ReadError> { + let base_offset = self.index_subtable_array_offset() as usize; + const SUBTABLE_HEADER_SIZE: usize = 8; + let header_offset = base_offset + index as usize * SUBTABLE_HEADER_SIZE; + let header_data = offset_data + .slice(header_offset..) + .ok_or(ReadError::OutOfBounds)?; + let header = IndexSubtableArray::read(header_data)?; + let subtable_offset = base_offset + header.additional_offset_to_index_subtable() as usize; + let subtable_data = offset_data + .slice(subtable_offset..) + .ok_or(ReadError::OutOfBounds)?; + let subtable = IndexSubtable::read(subtable_data)?; + Ok(BitmapSizeSubtable { + first_glyph_index: header.first_glyph_index(), + last_glyph_index: header.last_glyph_index(), + kind: subtable, + }) + } +} + +struct BitmapSizeSubtable<'a> { + pub first_glyph_index: GlyphId, + pub last_glyph_index: GlyphId, + pub kind: IndexSubtable<'a>, +} + +#[derive(Clone, Default)] +pub struct BitmapLocation { + /// Format of EBDT/CBDT image data. + pub format: u16, + /// Offset in bytes from the start of the EBDT/CBDT table. + pub data_offset: usize, + /// Size of the image data in bytes, if present in the EBLC/CBLC table. + pub data_size: Option, + /// Bit depth from the associated size. Required for computing image data + /// size when unspecified. + pub bit_depth: u8, + /// Full metrics, if present in the EBLC/CBLC table. + pub metrics: Option, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum BitmapDataFormat { + /// The full bitmap is tightly packed according to the bit depth. + BitAligned, + /// Each row of the data is aligned to a byte boundary. + ByteAligned, + Png, +} + +#[derive(Clone)] +pub enum BitmapMetrics { + Small(SmallGlyphMetrics), + Big(BigGlyphMetrics), +} + +#[derive(Clone)] +pub struct BitmapData<'a> { + pub metrics: BitmapMetrics, + pub content: BitmapContent<'a>, +} + +#[derive(Clone)] +pub enum BitmapContent<'a> { + Data(BitmapDataFormat, &'a [u8]), + Composite(&'a [BdtComponent]), +} + +pub(crate) fn bitmap_data<'a>( + offset_data: FontData<'a>, + location: &BitmapLocation, + is_color: bool, +) -> Result, ReadError> { + let mut image_data = offset_data + .slice(location.data_offset..) + .ok_or(ReadError::OutOfBounds)? + .cursor(); + match location.format { + // Small metrics, byte-aligned data + // + 1 => { + let metrics = read_small_metrics(&mut image_data)?; + // The data for each row is padded to a byte boundary + let pitch = (metrics.width as usize * location.bit_depth as usize + 7) / 8; + let height = metrics.height as usize; + let data = image_data.read_array::(pitch * height)?; + Ok(BitmapData { + metrics: BitmapMetrics::Small(metrics), + content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data), + }) + } + // Small metrics, bit-aligned data + // + 2 => { + let metrics = read_small_metrics(&mut image_data)?; + let width = metrics.width as usize * location.bit_depth as usize; + let height = metrics.height as usize; + // The data is tightly packed + let data = image_data.read_array::((width * height + 7) / 8)?; + Ok(BitmapData { + metrics: BitmapMetrics::Small(metrics), + content: BitmapContent::Data(BitmapDataFormat::BitAligned, data), + }) + } + // Format 3 is obsolete + // + // Format 4 is not supported + // + // --- + // Metrics in EBLC/CBLC, bit-aligned image data only + // + 5 => { + let metrics = location.metrics.clone().ok_or(ReadError::MalformedData( + "expected metrics from location table", + ))?; + let width = metrics.width as usize * location.bit_depth as usize; + let height = metrics.height as usize; + // The data is tightly packed + let data = image_data.read_array::((width * height + 7) / 8)?; + Ok(BitmapData { + metrics: BitmapMetrics::Big(metrics), + content: BitmapContent::Data(BitmapDataFormat::BitAligned, data), + }) + } + // Big metrics, byte-aligned data + // + 6 => { + let metrics = read_big_metrics(&mut image_data)?; + // The data for each row is padded to a byte boundary + let pitch = (metrics.width as usize * location.bit_depth as usize + 7) / 8; + let height = metrics.height as usize; + let data = image_data.read_array::(pitch * height)?; + Ok(BitmapData { + metrics: BitmapMetrics::Big(metrics), + content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data), + }) + } + // Big metrics, bit-aligned data + // + 7 => { + let metrics = read_big_metrics(&mut image_data)?; + let width = metrics.width as usize * location.bit_depth as usize; + let height = metrics.height as usize; + // The data is tightly packed + let data = image_data.read_array::((width * height + 7) / 8)?; + Ok(BitmapData { + metrics: BitmapMetrics::Big(metrics), + content: BitmapContent::Data(BitmapDataFormat::BitAligned, data), + }) + } + // Small metrics, component data + // + 8 => { + let metrics = read_small_metrics(&mut image_data)?; + let _pad = image_data.read::()?; + let count = image_data.read::()? as usize; + let components = image_data.read_array::(count)?; + Ok(BitmapData { + metrics: BitmapMetrics::Small(metrics), + content: BitmapContent::Composite(components), + }) + } + // Big metrics, component data + // + 9 => { + let metrics = read_big_metrics(&mut image_data)?; + let count = image_data.read::()? as usize; + let components = image_data.read_array::(count)?; + Ok(BitmapData { + metrics: BitmapMetrics::Big(metrics), + content: BitmapContent::Composite(components), + }) + } + // Small metrics, PNG image data + // + 17 if is_color => { + let metrics = read_small_metrics(&mut image_data)?; + let data_len = image_data.read::()? as usize; + let data = image_data.read_array::(data_len)?; + Ok(BitmapData { + metrics: BitmapMetrics::Small(metrics), + content: BitmapContent::Data(BitmapDataFormat::Png, data), + }) + } + // Big metrics, PNG image data + // + 18 if is_color => { + let metrics = read_big_metrics(&mut image_data)?; + let data_len = image_data.read::()? as usize; + let data = image_data.read_array::(data_len)?; + Ok(BitmapData { + metrics: BitmapMetrics::Big(metrics), + content: BitmapContent::Data(BitmapDataFormat::Png, data), + }) + } + // Metrics in CBLC table, PNG image data + // + 19 if is_color => { + let metrics = location.metrics.clone().ok_or(ReadError::MalformedData( + "expected metrics from location table", + ))?; + let data_len = image_data.read::()? as usize; + let data = image_data.read_array::(data_len)?; + Ok(BitmapData { + metrics: BitmapMetrics::Big(metrics), + content: BitmapContent::Data(BitmapDataFormat::Png, data), + }) + } + _ => Err(ReadError::MalformedData("unexpected bitmap data format")), + } +} + +fn read_small_metrics(cursor: &mut Cursor) -> Result { + Ok(cursor.read_array::(1)?[0].clone()) +} + +fn read_big_metrics(cursor: &mut Cursor) -> Result { + Ok(cursor.read_array::(1)?[0].clone()) +} + +#[cfg(feature = "traversal")] +impl SbitLineMetrics { + pub(crate) fn traversal_type<'a>(&self, data: FontData<'a>) -> FieldType<'a> { + FieldType::Record(self.traverse(data)) + } +} diff --git a/read-fonts/src/tables/cbdt.rs b/read-fonts/src/tables/cbdt.rs new file mode 100644 index 000000000..81b7d13e9 --- /dev/null +++ b/read-fonts/src/tables/cbdt.rs @@ -0,0 +1,59 @@ +//! The [CBDT (Color Bitmap Data)](https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt) table + +use super::bitmap::{BitmapData, BitmapLocation}; + +include!("../../generated/generated_cbdt.rs"); + +impl<'a> Cbdt<'a> { + pub fn data(&self, location: &BitmapLocation) -> Result, ReadError> { + super::bitmap::bitmap_data(self.offset_data(), location, true) + } +} + +#[cfg(test)] +mod tests { + use super::super::bitmap::{BitmapDataFormat, SmallGlyphMetrics}; + use crate::{types::GlyphId, FontRef, TableProvider}; + + #[test] + fn read_cblc_1_cbdt_17() { + let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap(); + let cblc = font.cblc().unwrap(); + let cbdt = font.cbdt().unwrap(); + let size = &cblc.bitmap_sizes()[0]; + // Metrics for size at index 0 + assert!( + size.hori.ascender() == 101 + && size.hori.descender() == -27 + && size.hori.width_max() == 136 + && 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.ppem_x() == 109 + && size.ppem_y() == 109 + && size.bit_depth() == 32 + ); + let expected: &[(GlyphId, &[u8], SmallGlyphMetrics)] = &[( + GlyphId::new(4), + &[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], + SmallGlyphMetrics { + height: 128, + width: 136, + bearing_x: 0.into(), + bearing_y: 101.into(), + advance: 136, + }, + )]; + for (gid, data, metrics) in expected { + let location = size.location(cblc.offset_data(), *gid).unwrap(); + assert_eq!(location.format, 17); + let bitmap_data = cbdt.data(&location).unwrap(); + let (img_fmt, img_data) = bitmap_data.content.extract_data(); + assert_eq!(img_fmt, BitmapDataFormat::Png); + assert_eq!(img_data, *data); + assert_eq!(bitmap_data.extract_small_metrics(), metrics); + } + } +} diff --git a/read-fonts/src/tables/cblc.rs b/read-fonts/src/tables/cblc.rs new file mode 100644 index 000000000..abeba74df --- /dev/null +++ b/read-fonts/src/tables/cblc.rs @@ -0,0 +1,5 @@ +//! The [CBLC (Color Bitmap Location)](https://docs.microsoft.com/en-us/typography/opentype/spec/cblc) table + +use super::bitmap::*; + +include!("../../generated/generated_cblc.rs"); diff --git a/read-fonts/src/tables/ebdt.rs b/read-fonts/src/tables/ebdt.rs new file mode 100644 index 000000000..b0f643b9c --- /dev/null +++ b/read-fonts/src/tables/ebdt.rs @@ -0,0 +1,147 @@ +//! The [EBDT (Embedded Bitmap Data)](https://docs.microsoft.com/en-us/typography/opentype/spec/ebdt) table + +use super::bitmap::{BitmapData, BitmapLocation}; + +include!("../../generated/generated_ebdt.rs"); + +impl<'a> Ebdt<'a> { + pub fn data(&self, location: &BitmapLocation) -> Result, ReadError> { + super::bitmap::bitmap_data(self.offset_data(), location, false) + } +} + +#[cfg(test)] +mod tests { + use super::super::bitmap::{ + BigGlyphMetrics, BitmapContent, BitmapData, BitmapDataFormat, BitmapMetrics, + SmallGlyphMetrics, + }; + use crate::{types::GlyphId, FontRef, TableProvider}; + + impl<'a> BitmapContent<'a> { + pub(crate) fn extract_data(&self) -> (BitmapDataFormat, &'a [u8]) { + match self { + BitmapContent::Data(fmt, data) => (*fmt, *data), + _ => panic!("expected data content"), + } + } + } + + impl<'a> BitmapData<'a> { + pub(crate) fn extract_small_metrics(&self) -> &SmallGlyphMetrics { + match &self.metrics { + BitmapMetrics::Small(small) => small, + _ => panic!("expected small glyph metrics"), + } + } + } + + #[test] + fn read_eblc_3_ebdt_2() { + let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap(); + let eblc = font.eblc().unwrap(); + let ebdt = font.ebdt().unwrap(); + let size = &eblc.bitmap_sizes()[0]; + // Metrics for size at index 0 + assert!( + size.hori.ascender() == 6 + && size.hori.descender() == 2 + && size.hori.width_max() == 4 + && size.hori.max_before_bl() == 6 + && 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.ppem_x() == 7 + && size.ppem_y() == 7 + && size.bit_depth() == 1 + ); + // Bit aligned formats in this strike: + let expected: &[(GlyphId, &[u8], SmallGlyphMetrics)] = &[ + ( + GlyphId::new(1), + &[0xee, 0xae, 0xea], + SmallGlyphMetrics { + height: 8, + width: 3, + bearing_x: 1.into(), + bearing_y: 6.into(), + advance: 4, + }, + ), + ( + GlyphId::new(2), + &[0xf0, 0xf0, 0xf0, 0xf0], + SmallGlyphMetrics { + height: 8, + width: 4, + bearing_x: 0.into(), + bearing_y: 6.into(), + advance: 4, + }, + ), + ]; + for (gid, data, metrics) in expected { + let location = size.location(eblc.offset_data(), *gid).unwrap(); + assert_eq!(location.format, 2); + let bitmap_data = ebdt.data(&location).unwrap(); + let (img_fmt, img_data) = bitmap_data.content.extract_data(); + assert_eq!(img_fmt, BitmapDataFormat::BitAligned); + assert_eq!(img_data, *data); + assert_eq!(bitmap_data.extract_small_metrics(), metrics); + } + } + + #[test] + fn read_eblc_2_ebdt_5() { + let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap(); + let eblc = font.eblc().unwrap(); + let ebdt = font.ebdt().unwrap(); + let size = &eblc.bitmap_sizes()[1]; + // Metrics for size at index 1 + assert!( + size.hori.ascender() == 12 + && size.hori.descender() == 5 + && size.hori.width_max() == 9 + && size.hori.max_before_bl() == 12 + && 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.ppem_x() == 15 + && size.ppem_y() == 15 + && size.bit_depth() == 1 + ); + let expected: &[(GlyphId, &[u8])] = &[( + GlyphId::new(3), + &[ + 0xaa, 0xbb, 0xcc, 0xdd, 0x00, 0x11, 0x22, 0x33, 0xff, 0xee, 0x12, 0x34, 0x42, 0x42, + 0x42, 0xaa, 0x88, 0x99, 0x00, 0x11, + ], + )]; + for (gid, data) in expected { + let location = size.location(eblc.offset_data(), *gid).unwrap(); + // Metrics are in EBLC, so the same for all glyphs + assert_eq!( + &location.metrics, + &Some(BigGlyphMetrics { + height: 17, + width: 9, + hori_bearing_x: 0.into(), + hori_bearing_y: 12.into(), + hori_advance: 9, + vert_bearing_x: (-4).into(), + vert_bearing_y: (-9).into(), + vert_advance: 0, + }) + ); + assert_eq!(location.format, 5); + let bitmap_data = ebdt.data(&location).unwrap(); + let (img_fmt, img_data) = bitmap_data.content.extract_data(); + assert_eq!(img_fmt, BitmapDataFormat::BitAligned); + assert_eq!(img_data, *data); + } + } +} diff --git a/read-fonts/src/tables/eblc.rs b/read-fonts/src/tables/eblc.rs new file mode 100644 index 000000000..b8618e1a7 --- /dev/null +++ b/read-fonts/src/tables/eblc.rs @@ -0,0 +1,5 @@ +//! The [EBLC (Embedded Bitmap Location)](https://docs.microsoft.com/en-us/typography/opentype/spec/eblc) table + +use super::bitmap::*; + +include!("../../generated/generated_eblc.rs"); diff --git a/resources/codegen_inputs/bitmap.rs b/resources/codegen_inputs/bitmap.rs new file mode 100644 index 000000000..83f0e4f1c --- /dev/null +++ b/resources/codegen_inputs/bitmap.rs @@ -0,0 +1,204 @@ +#![parse_module(read_fonts::tables::bitmap)] + +/// [BitmapSize](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#bitmapsize-record) record. +record BitmapSize { + /// Offset to index subtable from beginning of EBLC/CBLC. + index_subtable_array_offset: u32, + /// Number of bytes in corresponding index subtables and array. + index_tables_size: u32, + /// There is an index subtable for each range or format change. + number_of_index_subtables: u32, + /// Not used; set to 0. + color_ref: u32, + /// Line metrics for text rendered horizontally. + hori: SbitLineMetrics, + /// Line metrics for text rendered vertically. + vert: SbitLineMetrics, + /// Lowest glyph index for this size. + start_glyph_index: GlyphId, + /// Highest glyph index for this size. + end_glyph_index: GlyphId, + /// Horizontal pixels per em. + ppem_x: u8, + /// Vertical pixels per em. + ppem_y: u8, + /// The Microsoft rasterizer v.1.7 or greater supports the following + /// bitDepth values, as described below: 1, 2, 4, and 8 (and 32 for CBLC). + bit_depth: u8, + /// Vertical or horizontal. + flags: BitmapFlags, +} + +/// [SbitLineMetrics](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#sbitlinemetrics-record) record. +record SbitLineMetrics { + ascender: i8, + descender: i8, + width_max: u8, + caret_slope_numerator: i8, + caret_slope_denominator: u8, + caret_offset: i8, + min_origin_sb: i8, + min_advance_sb: i8, + max_before_bl: i8, + min_after_bl: i8, + pad1: i8, + pad2: i8, +} + +/// [Bitmap flags](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#bitmap-flags). +flags u8 BitmapFlags { + /// Horizontal + HORIZONTAL_METRICS = 0x01, + /// Vertical + VERTICAL_METRICS = 0x02, +} + +/// [BigGlyphMetrics](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#bigglyphmetrics) record. +record BigGlyphMetrics { + /// Number of rows of data. + height: u8, + /// Number of columns of data. + width: u8, + /// Distance in pixels from the horizontal origin to the left edge of the bitmap. + hori_bearing_x: i8, + /// Distance in pixels from the horizontal origin to the top edge of the bitmap. + hori_bearing_y: i8, + /// Horizontal advance width in pixels. + hori_advance: u8, + /// Distance in pixels from the vertical origin to the left edge of the bitmap. + vert_bearing_x: i8, + /// Distance in pixels from the vertical origin to the top edge of the bitmap. + vert_bearing_y: i8, + /// Vertical advance width in pixels. + vert_advance: u8, +} + +/// [SmallGlyphMetrics](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#smallglyphmetrics) record. +record SmallGlyphMetrics { + /// Number of rows of data. + height: u8, + /// Number of columns of data. + width: u8, + /// Distance in pixels from the horizontal origin to the left edge of the bitmap (for horizontal text); or distance in pixels from the vertical origin to the top edge of the bitmap (for vertical text). + bearing_x: i8, + /// Distance in pixels from the horizontal origin to the top edge of the bitmap (for horizontal text); or distance in pixels from the vertical origin to the left edge of the bitmap (for vertical text). + bearing_y: i8, + /// Horizontal or vertical advance width in pixels. + advance: u8, +} + +/// [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, + /// Last glyph ID of this range (inclusive). + last_glyph_index: GlyphId, + /// Add to indexSubTableArrayOffset to get offset from beginning of EBLC. + additional_offset_to_index_subtable: u32, +} + +/// [IndexSubtables](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtables) format type. +format u16 IndexSubtable { + Format1(IndexSubtable1), + Format2(IndexSubtable2), + Format3(IndexSubtable3), + Format4(IndexSubtable4), + Format5(IndexSubtable5), +} + +/// [IndexSubTable1](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable1-variable-metrics-glyphs-with-4-byte-offsets): variable-metrics glyphs with 4-byte offsets. +table IndexSubtable1 { + /// Format of this IndexSubTable. + #[format = 1] + index_format: u16, + /// Format of EBDT image data. + image_format: u16, + /// Offset to image data in EBDT table. + image_data_offset: u32, + #[count(..)] + sbit_offsets: [u32], +} + +/// [IndexSubTable2](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable2-all-glyphs-have-identical-metrics): all glyphs have identical metrics. +table IndexSubtable2 { + /// Format of this IndexSubTable. + #[format = 2] + index_format: u16, + /// Format of EBDT image data. + image_format: u16, + /// Offset to image data in EBDT table. + image_data_offset: u32, + /// All the glyphs are of the same size. + image_size: u32, + /// All glyphs have the same metrics; glyph data may be compressed, byte-aligned, or bit-aligned. + #[count(1)] + big_metrics: [BigGlyphMetrics], +} + +/// [IndexSubTable3](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable3-variable-metrics-glyphs-with-2-byte-offsets): variable-metrics glyphs with 2-byte offsets. +table IndexSubtable3 { + /// Format of this IndexSubTable. + #[format = 3] + index_format: u16, + /// Format of EBDT image data. + image_format: u16, + /// Offset to image data in EBDT table. + image_data_offset: u32, + #[count(..)] + sbit_offsets: [u16], +} + +/// [IndexSubTable4](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable3-variable-metrics-glyphs-with-2-byte-offsets): variable-metrics glyphs with sparse glyph codes. +table IndexSubtable4 { + /// Format of this IndexSubTable. + #[format = 4] + index_format: u16, + /// Format of EBDT image data. + image_format: u16, + /// Offset to image data in EBDT table. + image_data_offset: u32, + /// Array length. + num_glyphs: u32, + /// One per glyph. + #[count(add($num_glyphs, 1))] + glyph_array: [GlyphIdOffsetPair], +} + +/// [GlyphIdOffsetPair](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#glyphidoffsetpair-record) record. +record GlyphIdOffsetPair { + /// Glyph ID of glyph present. + glyph_id: GlyphId, + /// Location in EBDT. + sbit_offset: u16, +} + +/// [IndexSubTable5](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtable5-constant-metrics-glyphs-with-sparse-glyph-codes): constant-metrics glyphs with sparse glyph codes +table IndexSubtable5 { + /// Format of this IndexSubTable. + #[format = 5] + index_format: u16, + /// Format of EBDT image data. + image_format: u16, + /// Offset to image data in EBDT table. + image_data_offset: u32, + /// All glyphs have the same data size. + image_size: u32, + /// All glyphs have the same metrics. + #[count(1)] + big_metrics: [BigGlyphMetrics], + /// Array length. + num_glyphs: u32, + /// One per glyph, sorted by glyhph ID. + #[count($num_glyphs)] + glyph_array: [GlyphId], +} + +/// [EbdtComponent](https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#ebdtcomponent-record) record. +record BdtComponent { + /// Component glyph ID. + glyph_id: GlyphId, + /// Position of component left. + x_offset: i8, + /// Position of component top. + y_offset: i8, +} diff --git a/resources/codegen_inputs/cbdt.rs b/resources/codegen_inputs/cbdt.rs new file mode 100644 index 000000000..92752e001 --- /dev/null +++ b/resources/codegen_inputs/cbdt.rs @@ -0,0 +1,12 @@ +#![parse_module(read_fonts::tables::cbdt)] + +/// The [Color Bitmap Data](https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt) table +#[tag = "CBDT"] +table Cbdt { + /// Major version of the CBDT table, = 3. + #[compile(3)] + major_version: u16, + /// Minor version of CBDT table, = 0. + #[compile(0)] + minor_version: u16, +} diff --git a/resources/codegen_inputs/cblc.rs b/resources/codegen_inputs/cblc.rs new file mode 100644 index 000000000..b952512d0 --- /dev/null +++ b/resources/codegen_inputs/cblc.rs @@ -0,0 +1,20 @@ +#![parse_module(read_fonts::tables::cblc)] + +extern record BitmapSize; + +/// The [Color Bitmap Location](https://learn.microsoft.com/en-us/typography/opentype/spec/cblc) table +#[tag = "CBLC"] +table Cblc { + /// Major version of the CBLC table, = 3. + #[compile(3)] + major_version: u16, + /// Minor version of CBLC table, = 0. + #[compile(0)] + minor_version: u16, + /// Number of BitmapSize records. + #[compile(array_len($bitmap_sizes))] + num_sizes: u32, + /// BitmapSize records array. + #[count($num_sizes)] + bitmap_sizes: [BitmapSize], +} diff --git a/resources/codegen_inputs/ebdt.rs b/resources/codegen_inputs/ebdt.rs new file mode 100644 index 000000000..4b35bf6ed --- /dev/null +++ b/resources/codegen_inputs/ebdt.rs @@ -0,0 +1,12 @@ +#![parse_module(read_fonts::tables::ebdt)] + +/// The [Embedded Bitmap Data](https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt) table +#[tag = "EBDT"] +table Ebdt { + /// Major version of the EBDT table, = 2. + #[compile(2)] + major_version: u16, + /// Minor version of EBDT table, = 0. + #[compile(0)] + minor_version: u16, +} \ No newline at end of file diff --git a/resources/codegen_inputs/eblc.rs b/resources/codegen_inputs/eblc.rs new file mode 100644 index 000000000..2b71d8000 --- /dev/null +++ b/resources/codegen_inputs/eblc.rs @@ -0,0 +1,20 @@ +#![parse_module(read_fonts::tables::eblc)] + +extern record BitmapSize; + +/// The [Embedded Bitmap Location](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc) table +#[tag = "EBLC"] +table Eblc { + /// Major version of the EBLC table, = 2. + #[compile(2)] + major_version: u16, + /// Minor version of EBLC table, = 0. + #[compile(0)] + minor_version: u16, + /// Number of BitmapSize records. + #[compile(array_len($bitmap_sizes))] + num_sizes: u32, + /// BitmapSize records array. + #[count($num_sizes)] + bitmap_sizes: [BitmapSize], +} diff --git a/resources/codegen_plan.toml b/resources/codegen_plan.toml index a93f6d7ed..52789ad4f 100644 --- a/resources/codegen_plan.toml +++ b/resources/codegen_plan.toml @@ -298,6 +298,31 @@ mode = "compile" source = "resources/codegen_inputs/sbix.rs" target = "write-fonts/generated/generated_sbix.rs" +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/bitmap.rs" +target = "read-fonts/generated/generated_bitmap.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/eblc.rs" +target = "read-fonts/generated/generated_eblc.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/ebdt.rs" +target = "read-fonts/generated/generated_ebdt.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/cblc.rs" +target = "read-fonts/generated/generated_cblc.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/cbdt.rs" +target = "read-fonts/generated/generated_cbdt.rs" + # modules just used for testing [[generate]] mode = "parse"