diff --git a/font-codegen/README.md b/font-codegen/README.md index 21dbbc8df..90d5553ff 100644 --- a/font-codegen/README.md +++ b/font-codegen/README.md @@ -5,6 +5,7 @@ compiling various font tables. For an in-depth overview of what code we generate and how it works, see the [codegen-tour][] document. The basics: + - Inputs live in `resources/codegen_inputs`. - To run the code generator: ```sh @@ -27,7 +28,6 @@ The basics: [`include!`][] macro into a corresponding module, generally in `$crate/src/tables/$name.rs`. - ## Adding a new table - Create a new codegen input file in `resources/codegen_inputs`. The name of @@ -51,7 +51,6 @@ The basics: and ensure it is producing reasonable output. - Repeat this process for the `write-fonts` crate. - ## Modifying the codegen code It is possible that in adding a table you will need to modify the codegen code @@ -107,7 +106,7 @@ Offset16 langTagOffset Language-tag string offset from start of storage area (in ``` - all objects are separated by a newline, and begin with `@OBJECT_TYPE`. -- record & table are currently interchangeable, but this may change, and you +- record & table are currently interchangeable, but this may change, and you should follow the spec. - enum & flags require an explicit format - this does not handle lifetimes, which will need to be added manually @@ -164,18 +163,27 @@ The following annotations are supported on top-level objects: with the signature `fn(&self, &mut ValidationCtx)`. #### field attributes + - `#[nullable]`: only allowed on offsets or arrays of offsets, and indicates that this field is allowed to be null. This changes the behaviour of getters, as well as validation and compilation code. - `#[since_version(version)]`: indicates that a field only exists in a given version of the table. The `version` may be either a single integer literal (`#[since_version(1)]`), or a major.minor pair (`#[since_version(1.1)]`). +- `#[before_version(version)]`: indicates that a field only exists prior to a given version + of the table. The `version` may be either a single integer literal + (`#[before_version(2)]`) or a major.minor pair (`#[before_version(1.1)]`). - `#[if_flag($field, Flags::SOME_FLAG)]`: indicates that a given field is only present if a particular flag is set on the named field. The field is expected to be a bitset with a `contains` method. -- `#[if_cond($field, Flags::SOME_FLAG_A, Flags::SOME_FLAG_B, ...)]`: indicates that a - given field is only present if at least one of the listed flags is set on the named - field. The field is expected to be a bitset with a `contains` method. +- `#[if_cond($method(...))]`: + A function identifier, then one or more arguments. + - `#[if_cond(any_flag($field, Flags::SOME_FLAG_A, Flags::SOME_FLAG_B, ...))]`: indicates that a + given field is only present if at least one of the listed flags is set on the named + field. The field is expected to be a bitset with an `intersects` method. + - `#[if_cond(not_flag($field, Flags::SOME_FLAG_A, Flags::SOME_FLAG_B, ...))]`: indicates that a + given field is only present if none of the listed flags are set on the named + field. The field is expected to be a bitset with an `intersects` method. - `#[skip_getter]`: if present, we will not generate a getter for this field. Used on things like padding fields. - `#[offset_getter(method name)]`: only allowed on offsets or arrays of offsets. @@ -233,7 +241,6 @@ The following annotations are supported on top-level objects: - `#[to_owned(expr)]`: uncommon/hacky: provide an expression that will be used in `FromObjRef` to convert the parse type to the compile type. - ### codegen plans There is also the concept of a 'codegen plan', which is a simple toml file @@ -243,6 +250,8 @@ intended to be the general mechanism by which codegen is run. See `../resources/codegen_plan.toml` for an example. [opentype]: https://docs.microsoft.com/en-us/typography/opentype/ + [`include!`]: http://doc.rust-lang.org/1.64.0/std/macro.include.html + [codegen-tour]: ../docs/codegen-tour.md diff --git a/font-codegen/src/fields.rs b/font-codegen/src/fields.rs index e59f41226..f1eb069ae 100644 --- a/font-codegen/src/fields.rs +++ b/font-codegen/src/fields.rs @@ -67,10 +67,11 @@ impl Fields { if matches!(fld.attrs.count.as_deref(), Some(Count::All(_))) && i != self.fields.len() - 1 { - return Err(logged_syn_error( - fld.name.span(), - "#[count(..)] or VarLenArray fields can only be last field in table.", - )); + // TODO: This needs to take into account cfg-ed out fields + // return Err(logged_syn_error( + // fld.name.span(), + // "#[count(..)] or VarLenArray fields can only be last field in table.", + // )); } fld.sanity_check(phase)?; } @@ -201,6 +202,11 @@ impl Fields { ctx.report(format!("field must be present for version {version}")); } }, + Condition::BeforeVersion(_) => quote! { + if #condition && self.#name.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }, Condition::IfFlag { flag, .. } => { let flag = stringify_path(flag); let flag_missing = format!("'{name}' is present but {flag} not set",); @@ -229,6 +235,21 @@ impl Fields { } } } + IfTransform::NotFlag(_, _) => { + let condition_not_set_message = format!( + "if_cond is not satisfied but '{name}' is not present." + ); + let condition_set_message = + format!("if_cond is satisfied by '{name}' is present."); + quote! { + if !(#condition) && self.#name.is_some() { + ctx.report(#condition_not_set_message); + } + if (#condition) && self.#name.is_none() { + ctx.report(#condition_set_message); + } + } + } }, } }); @@ -311,6 +332,13 @@ fn if_expression(xform: &IfTransform, add_self: bool) -> TokenStream { quote!(#field.intersects(#(#flags)|*)) } } + IfTransform::NotFlag(field, flags) => { + if add_self { + quote!(!self.#field.intersects(#(#flags)|*)) + } else { + quote!(!#field.intersects(#(#flags)|*)) + } + } } } @@ -318,6 +346,7 @@ impl Condition { fn condition_tokens_for_read(&self) -> TokenStream { match self { Condition::SinceVersion(version) => quote!(version.compatible(#version)), + Condition::BeforeVersion(version) => quote!(!version.compatible(#version)), Condition::IfFlag { field, flag } => quote!(#field.contains(#flag)), Condition::IfCond { xform } => if_expression(xform, false), } @@ -326,6 +355,7 @@ impl Condition { fn condition_tokens_for_write(&self) -> TokenStream { match self { Condition::SinceVersion(version) => quote!(version.compatible(#version)), + Condition::BeforeVersion(version) => quote!(!version.compatible(#version)), Condition::IfFlag { field, flag } => quote!(self.#field.contains(#flag)), Condition::IfCond { xform } => if_expression(xform, true), } @@ -336,6 +366,7 @@ impl Condition { match self { // special case, we always treat a version field as input Condition::SinceVersion(_) => vec![], + Condition::BeforeVersion(_) => vec![], Condition::IfFlag { field, .. } => vec![field.clone()], Condition::IfCond { xform } => xform.input_field(), } diff --git a/font-codegen/src/parsing.rs b/font-codegen/src/parsing.rs index 986b35f80..e473370b3 100644 --- a/font-codegen/src/parsing.rs +++ b/font-codegen/src/parsing.rs @@ -246,6 +246,7 @@ pub(crate) struct FieldReadArgs { #[derive(Clone, Debug)] pub(crate) enum Condition { SinceVersion(VersionSpec), + BeforeVersion(VersionSpec), IfFlag { field: syn::Ident, flag: syn::Path }, IfCond { xform: IfTransform }, } @@ -256,6 +257,10 @@ pub(crate) enum IfTransform { /// /// Evaluates to true if field has at least one of the input flags set. AnyFlag(syn::Ident, Vec), + /// not_flag(field, flag_a, ...): + /// + /// Evaluates to true if field does *not* have any flag set + NotFlag(syn::Ident, Vec), } enum IfArg { @@ -320,6 +325,8 @@ pub(crate) enum CountTransform { SubAddTwo, /// requires exactly one arg. Get the count from the $arg1.`try_into::`(). TryInto, + /// requires exactly one arg. Count the number of 1 bits in the value + CountOnes, } /// Attributes for specifying how to compile a field @@ -1042,6 +1049,7 @@ static NULLABLE: &str = "nullable"; static SKIP_GETTER: &str = "skip_getter"; static COUNT: &str = "count"; static SINCE_VERSION: &str = "since_version"; +static BEFORE_VERSION: &str = "before_version"; static IF_COND: &str = "if_cond"; static IF_FLAG: &str = "if_flag"; static FORMAT: &str = "format"; @@ -1103,6 +1111,9 @@ impl Parse for FieldAttrs { } else if ident == SINCE_VERSION { let spec = attr.parse_args()?; this.checked_set_condition(ident, Condition::SinceVersion(spec))?; + } else if ident == BEFORE_VERSION { + let spec = attr.parse_args()?; + this.checked_set_condition(ident, Condition::BeforeVersion(spec))?; } else if ident == IF_FLAG { let condition = parse_if_flag(&attr)?; this.checked_set_condition(ident, condition)?; @@ -1471,6 +1482,7 @@ static TRANSFORM_IDENTS: &[(CountTransform, &str)] = &[ (CountTransform::MaxValueBitmapLen, "max_value_bitmap_len"), (CountTransform::SubAddTwo, "subtract_add_two"), (CountTransform::TryInto, "try_into"), + (CountTransform::CountOnes, "count_ones"), ]; impl FromStr for CountTransform { @@ -1509,6 +1521,7 @@ impl CountTransform { CountTransform::MaxValueBitmapLen => 1, CountTransform::SubAddTwo => 2, CountTransform::TryInto => 1, + CountTransform::CountOnes => 1, } } } @@ -1669,6 +1682,9 @@ impl Count { (CountTransform::TryInto, [a]) => { quote!(usize::try_from(#a).unwrap_or_default()) } + (CountTransform::CountOnes, [a]) => { + quote!(transforms::count_ones(#a)) + } _ => unreachable!("validated before now"), }, } @@ -1861,6 +1877,7 @@ impl IfTransform { fn from_args(s: &str, args: Vec) -> Result { match s { "any_flag" => Self::any_flag(args), + "not_flag" => Self::not_flag(args), _ => Err(format!("invalid if_cond transform function: {}", s)), } } @@ -1884,9 +1901,29 @@ impl IfTransform { Ok(IfTransform::AnyFlag(field.clone(), flags)) } + fn not_flag(args: Vec) -> Result { + let Some(IfArg::Field(field)) = args.first() else { + return Err("First argument to not_flag must be a field name.".to_string()); + }; + + let mut flags: Vec = vec![]; + for arg in args.iter().skip(1) { + let IfArg::Path(flag) = arg else { + return Err( + "Arguments after the first argument to not_flag must be a flag names." + .to_string(), + ); + }; + flags.push(flag.clone()); + } + + Ok(IfTransform::NotFlag(field.clone(), flags)) + } + pub(crate) fn input_field(&self) -> Vec { match self { IfTransform::AnyFlag(field, _) => vec![field.clone()], + IfTransform::NotFlag(field, _) => vec![field.clone()], } } } diff --git a/read-fonts/generated/generated_featgr.rs b/read-fonts/generated/generated_featgr.rs new file mode 100644 index 000000000..2d337e350 --- /dev/null +++ b/read-fonts/generated/generated_featgr.rs @@ -0,0 +1,547 @@ +// 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 graphite feature table - this is similar but not identical to apple's feature table. +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct FeatMarker { + features_byte_len: usize, +} + +impl FeatMarker { + pub fn version_byte_range(&self) -> Range { + let start = 0; + start..start + MajorMinor::RAW_BYTE_LEN + } + + pub fn num_features_byte_range(&self) -> Range { + let start = self.version_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + + pub fn _padding1_byte_range(&self) -> Range { + let start = self.num_features_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + + pub fn _padding2_byte_range(&self) -> Range { + let start = self._padding1_byte_range().end; + start..start + u32::RAW_BYTE_LEN + } + + pub fn features_byte_range(&self) -> Range { + let start = self._padding2_byte_range().end; + start..start + self.features_byte_len + } +} + +impl MinByteRange for FeatMarker { + fn min_byte_range(&self) -> Range { + 0..self.features_byte_range().end + } +} + +impl TopLevelTable for Feat<'_> { + /// `Feat` + const TAG: Tag = Tag::new(b"Feat"); +} + +impl<'a> FontRead<'a> for Feat<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + let version: MajorMinor = cursor.read()?; + let num_features: u16 = cursor.read()?; + cursor.advance::(); + cursor.advance::(); + let features_byte_len = (num_features as usize) + .checked_mul(Feature::RAW_BYTE_LEN) + .ok_or(ReadError::OutOfBounds)?; + cursor.advance_by(features_byte_len); + cursor.finish(FeatMarker { features_byte_len }) + } +} + +/// The graphite feature table - this is similar but not identical to apple's feature table. +pub type Feat<'a> = TableRef<'a, FeatMarker>; + +#[allow(clippy::needless_lifetimes)] +impl<'a> Feat<'a> { + /// (major, minor) Version for the Feat table + pub fn version(&self) -> MajorMinor { + let range = self.shape.version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn num_features(&self) -> u16 { + let range = self.shape.num_features_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn features(&self) -> &'a [Feature] { + let range = self.shape.features_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Feat<'a> { + fn type_name(&self) -> &str { + "Feat" + } + fn get_field(&self, idx: usize) -> Option> { + let version = self.version(); + match idx { + 0usize => Some(Field::new("version", self.version())), + 1usize => Some(Field::new("num_features", self.num_features())), + 2usize => Some(Field::new( + "features", + traversal::FieldType::array_of_records( + stringify!(Feature), + self.features(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +#[allow(clippy::needless_lifetimes)] +impl<'a> std::fmt::Debug for Feat<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +#[derive(Clone, Debug, Copy, bytemuck :: AnyBitPattern)] +#[repr(C)] +#[repr(packed)] +pub struct Feature { + pub feat_id: BigEndian, + pub feat_id: BigEndian, + pub num_settings: BigEndian, + pub _padding: BigEndian, + pub settings_offset: BigEndian, + pub flags: BigEndian, + pub name_idx: BigEndian, +} + +impl Feature { + pub fn feat_id(&self) -> u32 { + self.feat_id.get() + } + + pub fn feat_id(&self) -> u16 { + self.feat_id.get() + } + + pub fn num_settings(&self) -> u16 { + self.num_settings.get() + } + + pub fn settings_offset(&self) -> Offset32 { + self.settings_offset.get() + } + + /// + /// The `data` argument should be retrieved from the parent table + /// By calling its `offset_data` method. + pub fn settings<'a>(&self, data: FontData<'a>) -> Result<&'a [Setting], ReadError> { + let args = self.num_settings(); + self.settings_offset().resolve_with_args(data, &args) + } + + pub fn flags(&self) -> FeatureFlags { + self.flags.get() + } + + pub fn name_idx(&self) -> NameId { + self.name_idx.get() + } +} + +impl FixedSize for Feature { + const RAW_BYTE_LEN: usize = u32::RAW_BYTE_LEN + + u16::RAW_BYTE_LEN + + u16::RAW_BYTE_LEN + + u16::RAW_BYTE_LEN + + Offset32::RAW_BYTE_LEN + + FeatureFlags::RAW_BYTE_LEN + + NameId::RAW_BYTE_LEN; +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for Feature { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "Feature", + get_field: Box::new(move |idx, _data| match idx { + 0usize if version.compatible(3u16) => { + Some(Field::new("feat_id", self.feat_id().unwrap())) + } + 1usize if !version.compatible(3u16) => { + Some(Field::new("feat_id", self.feat_id().unwrap())) + } + 2usize => Some(Field::new("num_settings", self.num_settings())), + 3usize => Some(Field::new( + "settings_offset", + traversal::FieldType::offset_to_array_of_records( + self.settings_offset(), + self.settings(_data), + stringify!(Setting), + _data, + ), + )), + 4usize => Some(Field::new("flags", self.flags())), + 5usize => Some(Field::new("name_idx", self.name_idx())), + _ => None, + }), + data, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, bytemuck :: AnyBitPattern)] +#[repr(C)] +#[repr(packed)] +pub struct Setting { + pub feature_id: BigEndian, + pub value: BigEndian, + pub _padding: BigEndian, +} + +impl Setting { + pub fn feature_id(&self) -> u32 { + self.feature_id.get() + } + + pub fn value(&self) -> u16 { + self.value.get() + } +} + +impl FixedSize for Setting { + const RAW_BYTE_LEN: usize = u32::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN; +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for Setting { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "Setting", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("feature_id", self.feature_id())), + 1usize => Some(Field::new("value", self.value())), + _ => None, + }), + data, + } + } +} + +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, bytemuck :: AnyBitPattern)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[repr(transparent)] +pub struct FeatureFlags { + bits: u16, +} + +impl FeatureFlags { + pub const HIDDEN: Self = Self { bits: 0x0800 }; + + pub const EXCLUSIVE: Self = Self { bits: 0x8000 }; +} + +impl FeatureFlags { + /// 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::HIDDEN.bits | Self::EXCLUSIVE.bits, + } + } + + /// Returns the raw value of the flags currently stored. + #[inline] + pub const fn bits(&self) -> u16 { + 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: u16) -> 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: u16) -> 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 FeatureFlags { + type Output = Self; + + /// Returns the union of the two sets of flags. + #[inline] + fn bitor(self, other: FeatureFlags) -> Self { + Self { + bits: self.bits | other.bits, + } + } +} + +impl std::ops::BitOrAssign for FeatureFlags { + /// Adds the set of flags. + #[inline] + fn bitor_assign(&mut self, other: Self) { + self.bits |= other.bits; + } +} + +impl std::ops::BitXor for FeatureFlags { + 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 FeatureFlags { + /// Toggles the set of flags. + #[inline] + fn bitxor_assign(&mut self, other: Self) { + self.bits ^= other.bits; + } +} + +impl std::ops::BitAnd for FeatureFlags { + 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 FeatureFlags { + /// Disables all flags disabled in the set. + #[inline] + fn bitand_assign(&mut self, other: Self) { + self.bits &= other.bits; + } +} + +impl std::ops::Sub for FeatureFlags { + 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 FeatureFlags { + /// Disables all flags enabled in the set. + #[inline] + fn sub_assign(&mut self, other: Self) { + self.bits &= !other.bits; + } +} + +impl std::ops::Not for FeatureFlags { + 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 FeatureFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let members: &[(&str, Self)] = &[("HIDDEN", Self::HIDDEN), ("EXCLUSIVE", Self::EXCLUSIVE)]; + 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 FeatureFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Binary::fmt(&self.bits, f) + } +} + +impl std::fmt::Octal for FeatureFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Octal::fmt(&self.bits, f) + } +} + +impl std::fmt::LowerHex for FeatureFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.bits, f) + } +} + +impl std::fmt::UpperHex for FeatureFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::UpperHex::fmt(&self.bits, f) + } +} + +impl font_types::Scalar for FeatureFlags { + 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 = "experimental_traverse")] +impl<'a> From for FieldType<'a> { + fn from(src: FeatureFlags) -> FieldType<'a> { + src.bits().into() + } +} diff --git a/read-fonts/generated/generated_glat.rs b/read-fonts/generated/generated_glat.rs new file mode 100644 index 000000000..90c22f24c --- /dev/null +++ b/read-fonts/generated/generated_glat.rs @@ -0,0 +1,359 @@ +// 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::*; + +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct GlatMarker { + output_octaboxes_byte_start: Option, + octaboxes_byte_len: usize, + glyphs_byte_len: usize, +} + +impl GlatMarker { + pub fn version_byte_range(&self) -> Range { + let start = 0; + start..start + MajorMinor::RAW_BYTE_LEN + } + + pub fn output_octaboxes_byte_range(&self) -> Option> { + let start = self.output_octaboxes_byte_start?; + Some(start..start + u32::RAW_BYTE_LEN) + } + + pub fn octaboxes_byte_range(&self) -> Range { + let start = self + .output_octaboxes_byte_range() + .map(|range| range.end) + .unwrap_or_else(|| self.version_byte_range().end); + start..start + self.octaboxes_byte_len + } + + pub fn glyphs_byte_range(&self) -> Range { + let start = self.octaboxes_byte_range().end; + start..start + self.glyphs_byte_len + } +} + +impl MinByteRange for GlatMarker { + fn min_byte_range(&self) -> Range { + 0..self.glyphs_byte_range().end + } +} + +impl TopLevelTable for Glat<'_> { + /// `Glat` + const TAG: Tag = Tag::new(b"Glat"); +} + +impl ReadArgs for Glat<'_> { + type Args = u16; +} + +impl<'a> FontReadWithArgs<'a> for Glat<'a> { + fn read_with_args(data: FontData<'a>, args: &u16) -> Result { + let num_glyphs = *args; + let mut cursor = data.cursor(); + let version: MajorMinor = cursor.read()?; + let output_octaboxes_byte_start = version + .compatible((3u16, 0u16)) + .then(|| cursor.position()) + .transpose()?; + version + .compatible((3u16, 0u16)) + .then(|| cursor.advance::()); + let octaboxes_byte_len = (num_glyphs as usize) + .checked_mul(OctaBox::RAW_BYTE_LEN) + .ok_or(ReadError::OutOfBounds)?; + cursor.advance_by(octaboxes_byte_len); + let glyphs_byte_len = + cursor.remaining_bytes() / GlyphAttrRun::RAW_BYTE_LEN * GlyphAttrRun::RAW_BYTE_LEN; + cursor.advance_by(glyphs_byte_len); + cursor.finish(GlatMarker { + output_octaboxes_byte_start, + octaboxes_byte_len, + glyphs_byte_len, + }) + } +} + +impl<'a> Glat<'a> { + /// A constructor that requires additional arguments. + /// + /// This type requires some external state in order to be + /// parsed. + pub fn read(data: FontData<'a>, num_glyphs: u16) -> Result { + let args = num_glyphs; + Self::read_with_args(data, &args) + } +} + +pub type Glat<'a> = TableRef<'a, GlatMarker>; + +#[allow(clippy::needless_lifetimes)] +impl<'a> Glat<'a> { + /// (major, minor) Version for the Glat table + pub fn version(&self) -> MajorMinor { + let range = self.shape.version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn output_octaboxes(&self) -> Option { + let range = self.shape.output_octaboxes_byte_range()?; + Some(self.data.read_at(range.start).unwrap()) + } + + pub fn octaboxes(&self) -> &'a [OctaBox] { + let range = self.shape.octaboxes_byte_range(); + self.data.read_array(range).unwrap() + } + + pub fn glyphs(&self) -> &'a [GlyphAttrRun] { + let range = self.shape.glyphs_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Glat<'a> { + fn type_name(&self) -> &str { + "Glat" + } + fn get_field(&self, idx: usize) -> Option> { + let version = self.version(); + match idx { + 0usize => Some(Field::new("version", self.version())), + 1usize if version.compatible((3u16, 0u16)) => Some(Field::new( + "output_octaboxes", + self.output_octaboxes().unwrap(), + )), + 2usize => Some(Field::new( + "octaboxes", + traversal::FieldType::array_of_records( + stringify!(OctaBox), + self.octaboxes(), + self.offset_data(), + ), + )), + 3usize => Some(Field::new( + "glyphs", + traversal::FieldType::array_of_records( + stringify!(GlyphAttrRun), + self.glyphs(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +#[allow(clippy::needless_lifetimes)] +impl<'a> std::fmt::Debug for Glat<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OctaBox<'a> { + pub bitmap: BigEndian, + pub dn_min: u8, + pub dn_max: u8, + pub dp_min: u8, + pub dp_max: u8, + pub sub_box: &'a [SubBox], +} + +impl<'a> OctaBox<'a> { + pub fn bitmap(&self) -> u16 { + self.bitmap.get() + } + + pub fn dn_min(&self) -> u8 { + self.dn_min + } + + pub fn dn_max(&self) -> u8 { + self.dn_max + } + + pub fn dp_min(&self) -> u8 { + self.dp_min + } + + pub fn dp_max(&self) -> u8 { + self.dp_max + } + + pub fn sub_box(&self) -> &'a [SubBox] { + self.sub_box + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for OctaBox<'a> { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "OctaBox", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("bitmap", self.bitmap())), + 1usize => Some(Field::new("dn_min", self.dn_min())), + 2usize => Some(Field::new("dn_max", self.dn_max())), + 3usize => Some(Field::new("dp_min", self.dp_min())), + 4usize => Some(Field::new("dp_max", self.dp_max())), + 5usize => Some(Field::new( + "sub_box", + traversal::FieldType::array_of_records( + stringify!(SubBox), + self.sub_box(), + _data, + ), + )), + _ => None, + }), + data, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GlyphAttrRun<'a> { + pub start: u8, + pub length: u8, + pub start: BigEndian, + pub length: BigEndian, + pub attrs: &'a [BigEndian], +} + +impl<'a> GlyphAttrRun<'a> { + pub fn start(&self) -> u8 { + self.start + } + + pub fn length(&self) -> u8 { + self.length + } + + pub fn start(&self) -> u16 { + self.start.get() + } + + pub fn length(&self) -> u16 { + self.length.get() + } + + pub fn attrs(&self) -> &'a [BigEndian] { + self.attrs + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for GlyphAttrRun<'a> { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "GlyphAttrRun", + get_field: Box::new(move |idx, _data| match idx { + 0usize if !version.compatible(2u16) => { + Some(Field::new("start", self.start().unwrap())) + } + 1usize if !version.compatible(2u16) => { + Some(Field::new("length", self.length().unwrap())) + } + 2usize if version.compatible(2u16) => { + Some(Field::new("start", self.start().unwrap())) + } + 3usize if version.compatible(2u16) => { + Some(Field::new("length", self.length().unwrap())) + } + 4usize => Some(Field::new("attrs", self.attrs())), + _ => None, + }), + data, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, bytemuck :: AnyBitPattern)] +#[repr(C)] +#[repr(packed)] +pub struct SubBox { + pub left: u8, + pub right: u8, + pub bottom: u8, + pub top: u8, + pub dn_min: u8, + pub dn_max: u8, + pub dp_min: u8, + pub dp_max: u8, +} + +impl SubBox { + pub fn left(&self) -> u8 { + self.left + } + + pub fn right(&self) -> u8 { + self.right + } + + pub fn bottom(&self) -> u8 { + self.bottom + } + + pub fn top(&self) -> u8 { + self.top + } + + pub fn dn_min(&self) -> u8 { + self.dn_min + } + + pub fn dn_max(&self) -> u8 { + self.dn_max + } + + pub fn dp_min(&self) -> u8 { + self.dp_min + } + + pub fn dp_max(&self) -> u8 { + self.dp_max + } +} + +impl FixedSize for SubBox { + const RAW_BYTE_LEN: usize = u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN + + u8::RAW_BYTE_LEN; +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for SubBox { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "SubBox", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("left", self.left())), + 1usize => Some(Field::new("right", self.right())), + 2usize => Some(Field::new("bottom", self.bottom())), + 3usize => Some(Field::new("top", self.top())), + 4usize => Some(Field::new("dn_min", self.dn_min())), + 5usize => Some(Field::new("dn_max", self.dn_max())), + 6usize => Some(Field::new("dp_min", self.dp_min())), + 7usize => Some(Field::new("dp_max", self.dp_max())), + _ => None, + }), + data, + } + } +} diff --git a/read-fonts/generated/generated_gloc.rs b/read-fonts/generated/generated_gloc.rs new file mode 100644 index 000000000..e6394af47 --- /dev/null +++ b/read-fonts/generated/generated_gloc.rs @@ -0,0 +1,455 @@ +// 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::*; + +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct GlocMarker { + offsets_byte_start: Option, + offsets_byte_len: Option, + offsets_byte_start: Option, + offsets_byte_len: Option, +} + +impl GlocMarker { + pub fn version_byte_range(&self) -> Range { + let start = 0; + start..start + MajorMinor::RAW_BYTE_LEN + } + + pub fn flags_byte_range(&self) -> Range { + let start = self.version_byte_range().end; + start..start + GlocFlags::RAW_BYTE_LEN + } + + pub fn num_attrs_byte_range(&self) -> Range { + let start = self.flags_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + + pub fn offsets_byte_range(&self) -> Option> { + let start = self.offsets_byte_start?; + Some(start..start + self.offsets_byte_len?) + } + + pub fn offsets_byte_range(&self) -> Option> { + let start = self.offsets_byte_start?; + Some(start..start + self.offsets_byte_len?) + } +} + +impl MinByteRange for GlocMarker { + fn min_byte_range(&self) -> Range { + 0..self.num_attrs_byte_range().end + } +} + +impl TopLevelTable for Gloc<'_> { + /// `Gloc` + const TAG: Tag = Tag::new(b"Gloc"); +} + +impl<'a> FontRead<'a> for Gloc<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + let version: MajorMinor = cursor.read()?; + let flags: GlocFlags = cursor.read()?; + cursor.advance::(); + let offsets_byte_start = flags + .contains(GlocFlags::NEED_LONG_FORMAT) + .then(|| cursor.position()) + .transpose()?; + let offsets_byte_len = flags + .contains(GlocFlags::NEED_LONG_FORMAT) + .then_some(cursor.remaining_bytes() / u32::RAW_BYTE_LEN * u32::RAW_BYTE_LEN); + if let Some(value) = offsets_byte_len { + cursor.advance_by(value); + } + let offsets_byte_start = !flags + .intersects(GlocFlags::NEED_LONG_FORMAT) + .then(|| cursor.position()) + .transpose()?; + let offsets_byte_len = !flags + .intersects(GlocFlags::NEED_LONG_FORMAT) + .then_some(cursor.remaining_bytes() / u16::RAW_BYTE_LEN * u16::RAW_BYTE_LEN); + if let Some(value) = offsets_byte_len { + cursor.advance_by(value); + } + cursor.finish(GlocMarker { + offsets_byte_start, + offsets_byte_len, + offsets_byte_start, + offsets_byte_len, + }) + } +} + +pub type Gloc<'a> = TableRef<'a, GlocMarker>; + +#[allow(clippy::needless_lifetimes)] +impl<'a> Gloc<'a> { + /// (major, minor) Version for the Gloc table + pub fn version(&self) -> MajorMinor { + let range = self.shape.version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn flags(&self) -> GlocFlags { + let range = self.shape.flags_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn num_attrs(&self) -> u16 { + let range = self.shape.num_attrs_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn offsets(&self) -> Option<&'a [BigEndian]> { + let range = self.shape.offsets_byte_range()?; + Some(self.data.read_array(range).unwrap()) + } + + pub fn offsets(&self) -> Option<&'a [BigEndian]> { + let range = self.shape.offsets_byte_range()?; + Some(self.data.read_array(range).unwrap()) + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Gloc<'a> { + fn type_name(&self) -> &str { + "Gloc" + } + fn get_field(&self, idx: usize) -> Option> { + let version = self.version(); + let flags = self.flags(); + match idx { + 0usize => Some(Field::new("version", self.version())), + 1usize => Some(Field::new("flags", self.flags())), + 2usize => Some(Field::new("num_attrs", self.num_attrs())), + 3usize if flags.contains(GlocFlags::NEED_LONG_FORMAT) => { + Some(Field::new("offsets", self.offsets().unwrap())) + } + 4usize if !flags.intersects(GlocFlags::NEED_LONG_FORMAT) => { + Some(Field::new("offsets", self.offsets().unwrap())) + } + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +#[allow(clippy::needless_lifetimes)] +impl<'a> std::fmt::Debug for Gloc<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, bytemuck :: AnyBitPattern)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[repr(transparent)] +pub struct GlocFlags { + bits: u16, +} + +impl GlocFlags { + pub const NEED_LONG_FORMAT: Self = Self { bits: 0x0001 }; + + pub const ATTR_NAMES: Self = Self { bits: 0x0002 }; +} + +impl GlocFlags { + /// 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::NEED_LONG_FORMAT.bits | Self::ATTR_NAMES.bits, + } + } + + /// Returns the raw value of the flags currently stored. + #[inline] + pub const fn bits(&self) -> u16 { + 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: u16) -> 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: u16) -> 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 GlocFlags { + type Output = Self; + + /// Returns the union of the two sets of flags. + #[inline] + fn bitor(self, other: GlocFlags) -> Self { + Self { + bits: self.bits | other.bits, + } + } +} + +impl std::ops::BitOrAssign for GlocFlags { + /// Adds the set of flags. + #[inline] + fn bitor_assign(&mut self, other: Self) { + self.bits |= other.bits; + } +} + +impl std::ops::BitXor for GlocFlags { + 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 GlocFlags { + /// Toggles the set of flags. + #[inline] + fn bitxor_assign(&mut self, other: Self) { + self.bits ^= other.bits; + } +} + +impl std::ops::BitAnd for GlocFlags { + 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 GlocFlags { + /// Disables all flags disabled in the set. + #[inline] + fn bitand_assign(&mut self, other: Self) { + self.bits &= other.bits; + } +} + +impl std::ops::Sub for GlocFlags { + 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 GlocFlags { + /// Disables all flags enabled in the set. + #[inline] + fn sub_assign(&mut self, other: Self) { + self.bits &= !other.bits; + } +} + +impl std::ops::Not for GlocFlags { + 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 GlocFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let members: &[(&str, Self)] = &[ + ("NEED_LONG_FORMAT", Self::NEED_LONG_FORMAT), + ("ATTR_NAMES", Self::ATTR_NAMES), + ]; + 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 GlocFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Binary::fmt(&self.bits, f) + } +} + +impl std::fmt::Octal for GlocFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Octal::fmt(&self.bits, f) + } +} + +impl std::fmt::LowerHex for GlocFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.bits, f) + } +} + +impl std::fmt::UpperHex for GlocFlags { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::UpperHex::fmt(&self.bits, f) + } +} + +impl font_types::Scalar for GlocFlags { + 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 = "experimental_traverse")] +impl<'a> From for FieldType<'a> { + fn from(src: GlocFlags) -> FieldType<'a> { + src.bits().into() + } +} diff --git a/read-fonts/generated/generated_silf.rs b/read-fonts/generated/generated_silf.rs new file mode 100644 index 000000000..51a7d68dd --- /dev/null +++ b/read-fonts/generated/generated_silf.rs @@ -0,0 +1,186 @@ +// 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::*; + +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct SilfMarker { + compiler_version_byte_start: Option, + _padding_byte_start: Option, +} + +impl SilfMarker { + pub fn version_byte_range(&self) -> Range { + let start = 0; + start..start + MajorMinor::RAW_BYTE_LEN + } + + pub fn compiler_version_byte_range(&self) -> Option> { + let start = self.compiler_version_byte_start?; + Some(start..start + MajorMinor::RAW_BYTE_LEN) + } + + pub fn sub_tables_byte_range(&self) -> Range { + let start = self + .compiler_version_byte_range() + .map(|range| range.end) + .unwrap_or_else(|| self.version_byte_range().end); + start..start + u16::RAW_BYTE_LEN + } + + pub fn _padding_byte_range(&self) -> Option> { + let start = self._padding_byte_start?; + Some(start..start + u16::RAW_BYTE_LEN) + } + + pub fn start_offset_byte_range(&self) -> Range { + let start = self + ._padding_byte_range() + .map(|range| range.end) + .unwrap_or_else(|| self.sub_tables_byte_range().end); + start..start + Offset32::RAW_BYTE_LEN + } +} + +impl MinByteRange for SilfMarker { + fn min_byte_range(&self) -> Range { + 0..self.start_offset_byte_range().end + } +} + +impl TopLevelTable for Silf<'_> { + /// `Silf` + const TAG: Tag = Tag::new(b"Silf"); +} + +impl<'a> FontRead<'a> for Silf<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + let version: MajorMinor = cursor.read()?; + let compiler_version_byte_start = version + .compatible(3u16) + .then(|| cursor.position()) + .transpose()?; + version + .compatible(3u16) + .then(|| cursor.advance::()); + cursor.advance::(); + let _padding_byte_start = version + .compatible(2u16) + .then(|| cursor.position()) + .transpose()?; + version.compatible(2u16).then(|| cursor.advance::()); + cursor.advance::(); + cursor.finish(SilfMarker { + compiler_version_byte_start, + _padding_byte_start, + }) + } +} + +pub type Silf<'a> = TableRef<'a, SilfMarker>; + +#[allow(clippy::needless_lifetimes)] +impl<'a> Silf<'a> { + /// (major, minor) Version for the Silf table + pub fn version(&self) -> MajorMinor { + let range = self.shape.version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn compiler_version(&self) -> Option { + let range = self.shape.compiler_version_byte_range()?; + Some(self.data.read_at(range.start).unwrap()) + } + + pub fn sub_tables(&self) -> u16 { + let range = self.shape.sub_tables_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn start_offset(&self) -> Offset32 { + let range = self.shape.start_offset_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Attempt to resolve [`start_offset`][Self::start_offset]. + pub fn start(&self) -> Result, ReadError> { + let data = self.data; + self.start_offset().resolve(data) + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Silf<'a> { + fn type_name(&self) -> &str { + "Silf" + } + fn get_field(&self, idx: usize) -> Option> { + let version = self.version(); + match idx { + 0usize => Some(Field::new("version", self.version())), + 1usize if version.compatible(3u16) => Some(Field::new( + "compiler_version", + self.compiler_version().unwrap(), + )), + 2usize => Some(Field::new("sub_tables", self.sub_tables())), + 3usize => Some(Field::new( + "start_offset", + FieldType::offset(self.start_offset(), self.start()), + )), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +#[allow(clippy::needless_lifetimes)] +impl<'a> std::fmt::Debug for Silf<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct SilfSubtableMarker {} + +impl SilfSubtableMarker {} + +impl<'a> FontRead<'a> for SilfSubtable<'a> { + fn read(data: FontData<'a>) -> Result { + let cursor = data.cursor(); + cursor.finish(SilfSubtableMarker {}) + } +} + +pub type SilfSubtable<'a> = TableRef<'a, SilfSubtableMarker>; + +#[allow(clippy::needless_lifetimes)] +impl<'a> SilfSubtable<'a> {} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for SilfSubtable<'a> { + fn type_name(&self) -> &str { + "SilfSubtable" + } + + #[allow(unused_variables)] + #[allow(clippy::match_single_binding)] + fn get_field(&self, idx: usize) -> Option> { + match idx { + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +#[allow(clippy::needless_lifetimes)] +impl<'a> std::fmt::Debug for SilfSubtable<'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_sill.rs b/read-fonts/generated/generated_sill.rs new file mode 100644 index 000000000..03e4cc9c7 --- /dev/null +++ b/read-fonts/generated/generated_sill.rs @@ -0,0 +1,240 @@ +// 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::*; + +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct SillMarker { + languages_byte_len: usize, +} + +impl SillMarker { + pub fn version_byte_range(&self) -> Range { + let start = 0; + start..start + MajorMinor::RAW_BYTE_LEN + } + + pub fn num_langs_byte_range(&self) -> Range { + let start = self.version_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + + pub fn next_power_of_two_byte_range(&self) -> Range { + let start = self.num_langs_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + + pub fn log_byte_range(&self) -> Range { + let start = self.next_power_of_two_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + + pub fn power_diff_byte_range(&self) -> Range { + let start = self.log_byte_range().end; + start..start + i16::RAW_BYTE_LEN + } + + pub fn languages_byte_range(&self) -> Range { + let start = self.power_diff_byte_range().end; + start..start + self.languages_byte_len + } +} + +impl MinByteRange for SillMarker { + fn min_byte_range(&self) -> Range { + 0..self.languages_byte_range().end + } +} + +impl TopLevelTable for Sill<'_> { + /// `Sill` + const TAG: Tag = Tag::new(b"Sill"); +} + +impl<'a> FontRead<'a> for Sill<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + let version: MajorMinor = cursor.read()?; + let num_langs: u16 = cursor.read()?; + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let languages_byte_len = (num_langs as usize) + .checked_mul(Language::RAW_BYTE_LEN) + .ok_or(ReadError::OutOfBounds)?; + cursor.advance_by(languages_byte_len); + cursor.finish(SillMarker { languages_byte_len }) + } +} + +pub type Sill<'a> = TableRef<'a, SillMarker>; + +#[allow(clippy::needless_lifetimes)] +impl<'a> Sill<'a> { + /// (major, minor) Version for the Sill table + pub fn version(&self) -> MajorMinor { + let range = self.shape.version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn num_langs(&self) -> u16 { + let range = self.shape.num_langs_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// A power of two > num_langs + pub fn next_power_of_two(&self) -> u16 { + let range = self.shape.next_power_of_two_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Rounded base-2 log of num_langs + pub fn log(&self) -> u16 { + let range = self.shape.log_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Difference between next_power_of_two and num_langs + pub fn power_diff(&self) -> i16 { + let range = self.shape.power_diff_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn languages(&self) -> &'a [Language] { + let range = self.shape.languages_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Sill<'a> { + fn type_name(&self) -> &str { + "Sill" + } + fn get_field(&self, idx: usize) -> Option> { + let version = self.version(); + match idx { + 0usize => Some(Field::new("version", self.version())), + 1usize => Some(Field::new("num_langs", self.num_langs())), + 2usize => Some(Field::new("next_power_of_two", self.next_power_of_two())), + 3usize => Some(Field::new("log", self.log())), + 4usize => Some(Field::new("power_diff", self.power_diff())), + 5usize => Some(Field::new( + "languages", + traversal::FieldType::array_of_records( + stringify!(Language), + self.languages(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +#[allow(clippy::needless_lifetimes)] +impl<'a> std::fmt::Debug for Sill<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +#[derive(Clone, Debug, Copy, bytemuck :: AnyBitPattern)] +#[repr(C)] +#[repr(packed)] +pub struct Language { + pub language: BigEndian, + pub num_settings: BigEndian, + pub settings_offset: BigEndian, +} + +impl Language { + pub fn language(&self) -> Tag { + self.language.get() + } + + pub fn num_settings(&self) -> u16 { + self.num_settings.get() + } + + pub fn settings_offset(&self) -> Offset32 { + self.settings_offset.get() + } + + /// + /// The `data` argument should be retrieved from the parent table + /// By calling its `offset_data` method. + pub fn settings<'a>(&self, data: FontData<'a>) -> Result<&'a [SettingName], ReadError> { + let args = self.num_settings(); + self.settings_offset().resolve_with_args(data, &args) + } +} + +impl FixedSize for Language { + const RAW_BYTE_LEN: usize = Tag::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + Offset32::RAW_BYTE_LEN; +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for Language { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "Language", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("language", self.language())), + 1usize => Some(Field::new("num_settings", self.num_settings())), + 2usize => Some(Field::new( + "settings_offset", + traversal::FieldType::offset_to_array_of_records( + self.settings_offset(), + self.settings(_data), + stringify!(SettingName), + _data, + ), + )), + _ => None, + }), + data, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, bytemuck :: AnyBitPattern)] +#[repr(C)] +#[repr(packed)] +pub struct SettingName { + pub value: BigEndian, + pub name: BigEndian, +} + +impl SettingName { + pub fn value(&self) -> u16 { + self.value.get() + } + + pub fn name(&self) -> NameId { + self.name.get() + } +} + +impl FixedSize for SettingName { + const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + NameId::RAW_BYTE_LEN; +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for SettingName { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "SettingName", + get_field: Box::new(move |idx, _data| match idx { + 0usize => Some(Field::new("value", self.value())), + 1usize => Some(Field::new("name", self.name())), + _ => None, + }), + data, + } + } +} diff --git a/read-fonts/generated/generated_test_conditions.rs b/read-fonts/generated/generated_test_conditions.rs index c720ffd88..40efec4c8 100644 --- a/read-fonts/generated/generated_test_conditions.rs +++ b/read-fonts/generated/generated_test_conditions.rs @@ -431,6 +431,7 @@ pub struct FlagDayMarker { foo_byte_start: Option, bar_byte_start: Option, baz_byte_start: Option, + qux_byte_start: Option, } impl FlagDayMarker { @@ -458,6 +459,11 @@ impl FlagDayMarker { let start = self.baz_byte_start?; Some(start..start + u16::RAW_BYTE_LEN) } + + pub fn qux_byte_range(&self) -> Option> { + let start = self.qux_byte_start?; + Some(start..start + u16::RAW_BYTE_LEN) + } } impl MinByteRange for FlagDayMarker { @@ -492,10 +498,18 @@ impl<'a> FontRead<'a> for FlagDay<'a> { flags .intersects(GotFlags::BAZ | GotFlags::FOO) .then(|| cursor.advance::()); + let qux_byte_start = !flags + .intersects(GotFlags::FOO) + .then(|| cursor.position()) + .transpose()?; + !flags + .intersects(GotFlags::FOO) + .then(|| cursor.advance::()); cursor.finish(FlagDayMarker { foo_byte_start, bar_byte_start, baz_byte_start, + qux_byte_start, }) } } @@ -528,6 +542,11 @@ impl<'a> FlagDay<'a> { let range = self.shape.baz_byte_range()?; Some(self.data.read_at(range.start).unwrap()) } + + pub fn qux(&self) -> Option { + let range = self.shape.qux_byte_range()?; + Some(self.data.read_at(range.start).unwrap()) + } } #[cfg(feature = "experimental_traverse")] @@ -545,6 +564,9 @@ impl<'a> SomeTable<'a> for FlagDay<'a> { 4usize if flags.intersects(GotFlags::BAZ | GotFlags::FOO) => { Some(Field::new("baz", self.baz().unwrap())) } + 5usize if !flags.intersects(GotFlags::FOO) => { + Some(Field::new("qux", self.qux().unwrap())) + } _ => None, } } diff --git a/read-fonts/src/lib.rs b/read-fonts/src/lib.rs index f711cba64..f17fc5601 100644 --- a/read-fonts/src/lib.rs +++ b/read-fonts/src/lib.rs @@ -183,6 +183,10 @@ pub(crate) mod codegen_prelude { .saturating_sub(rhs.try_into().unwrap_or_default()) .saturating_add(2) } + + pub fn count_ones>(val: T) -> usize { + val.try_into().unwrap_or_default().count_ones() as usize + } } } diff --git a/read-fonts/src/tables.rs b/read-fonts/src/tables.rs index 7f2a2542f..244e5c861 100644 --- a/read-fonts/src/tables.rs +++ b/read-fonts/src/tables.rs @@ -53,8 +53,12 @@ pub mod vmtx; pub mod vorg; pub mod vvar; +mod featgr; +mod gloc; #[cfg(feature = "ift")] pub mod ift; +mod silf; +mod sill; /// Computes the table checksum for the given data. /// diff --git a/read-fonts/src/tables/featgr.rs b/read-fonts/src/tables/featgr.rs new file mode 100644 index 000000000..764a145ee --- /dev/null +++ b/read-fonts/src/tables/featgr.rs @@ -0,0 +1,3 @@ +//! The [feat](https://graphite.sil.org/graphite_techAbout#graphite-font-tables) table + +include!("../../generated/generated_featgr.rs"); diff --git a/read-fonts/src/tables/glat.rs b/read-fonts/src/tables/glat.rs new file mode 100644 index 000000000..6a78747dd --- /dev/null +++ b/read-fonts/src/tables/glat.rs @@ -0,0 +1,3 @@ +//! The [glat](https://graphite.sil.org/graphite_techAbout#graphite-font-tables) table + +include!("../../generated/generated_glat.rs"); diff --git a/read-fonts/src/tables/gloc.rs b/read-fonts/src/tables/gloc.rs new file mode 100644 index 000000000..ad0d02462 --- /dev/null +++ b/read-fonts/src/tables/gloc.rs @@ -0,0 +1,3 @@ +//! The [gloc](https://graphite.sil.org/graphite_techAbout#graphite-font-tables) table + +include!("../../generated/generated_gloc.rs"); diff --git a/read-fonts/src/tables/silf.rs b/read-fonts/src/tables/silf.rs new file mode 100644 index 000000000..24accd038 --- /dev/null +++ b/read-fonts/src/tables/silf.rs @@ -0,0 +1,3 @@ +//! The [silf](https://graphite.sil.org/graphite_techAbout#graphite-font-tables) table + +include!("../../generated/generated_silf.rs"); diff --git a/read-fonts/src/tables/sill.rs b/read-fonts/src/tables/sill.rs new file mode 100644 index 000000000..f93f8d7a5 --- /dev/null +++ b/read-fonts/src/tables/sill.rs @@ -0,0 +1,3 @@ +//! The [sill](https://graphite.sil.org/graphite_techAbout#graphite-font-tables) table + +include!("../../generated/generated_sill.rs"); diff --git a/resources/codegen_inputs/featgr.rs b/resources/codegen_inputs/featgr.rs new file mode 100644 index 000000000..8d17c89d3 --- /dev/null +++ b/resources/codegen_inputs/featgr.rs @@ -0,0 +1,60 @@ +#![parse_module(read_fonts::tables::featgr)] + +/// The graphite feature table - this is similar but not identical to apple's feature table. +#[tag = "Feat"] +table Feat { + /// (major, minor) Version for the Feat table + #[version] + #[compile(self.compute_version())] + version: MajorMinor, + + #[compile(array_len($features))] + num_features: u16, + + #[skip_getter] + #[compile(0)] + _padding1: u16, + #[skip_getter] + #[compile(0)] + _padding2: u32, + + #[count($num_features)] + features: [Feature] +} + +record Feature { + #[since_version(3)] + feat_id: u32, + #[before_version(3)] + feat_id: u16, + + #[compile(array_len($settings))] + num_settings: u16, + + #[since_version(2)] + #[skip_getter] + #[compile(0)] + _padding: u16, + + #[read_offset_with($num_settings)] + settings_offset: Offset32<[Setting]>, + + flags: FeatureFlags, + + name_idx: NameId, +} + +record Setting { + feature_id: u32, + value: u16, + #[skip_getter] + #[compile(0)] + _padding: u16, +} + +flags u16 FeatureFlags { + HIDDEN = 0x0800, + EXCLUSIVE = 0x8000, +} + + diff --git a/resources/codegen_inputs/glat.rs b/resources/codegen_inputs/glat.rs new file mode 100644 index 000000000..ced95991f --- /dev/null +++ b/resources/codegen_inputs/glat.rs @@ -0,0 +1,57 @@ +#![parse_module(read_fonts::tables::glat)] + +#[tag = "Glat"] +#[read_args(num_glyphs: u16)] +table Glat { + /// (major, minor) Version for the Glat table + #[version] + #[compile(self.compute_version())] + version: MajorMinor, + + #[since_version(3.0)] + #[compile(1)] + output_octaboxes: u32, + + #[count($num_glyphs)] + octaboxes: [OctaBox], + + #[count(..)] + glyphs: [GlyphAttrRun], +} + +record OctaBox<'a> { + bitmap: u16, + dn_min: u8, + dn_max: u8, + dp_min: u8, + dp_max: u8, + #[count(count_ones($bitmap))] + sub_box: [SubBox], +} + +record GlyphAttrRun<'a> { + #[before_version(2)] + start: u8, + #[before_version(2)] + length: u8, + + #[since_version(2)] + start: u16, + #[since_version(2)] + length: u16, + + #[count($length)] + attrs: [u16], +} + +record SubBox { + left: u8, + right: u8, + bottom: u8, + top: u8, + + dn_min: u8, + dn_max: u8, + dp_min: u8, + dp_max: u8, +} diff --git a/resources/codegen_inputs/gloc.rs b/resources/codegen_inputs/gloc.rs new file mode 100644 index 000000000..741d409a2 --- /dev/null +++ b/resources/codegen_inputs/gloc.rs @@ -0,0 +1,26 @@ +#![parse_module(read_fonts::tables::gloc)] + +#[tag = "Gloc"] +table Gloc { + /// (major, minor) Version for the Gloc table + #[version] + #[compile(self.compute_version())] + version: MajorMinor, + + flags: GlocFlags, + + num_attrs: u16, + + #[if_flag($flags, GlocFlags::NEED_LONG_FORMAT)] + #[count(..)] + offsets: [u32], + + #[if_cond(not_flag($flags, GlocFlags::NEED_LONG_FORMAT))] + #[count(..)] + offsets: [u16], +} + +flags u16 GlocFlags { + NEED_LONG_FORMAT = 0x0001, + ATTR_NAMES = 0x0002, +} diff --git a/resources/codegen_inputs/silf.rs b/resources/codegen_inputs/silf.rs new file mode 100644 index 000000000..ca69fb26f --- /dev/null +++ b/resources/codegen_inputs/silf.rs @@ -0,0 +1,26 @@ +#![parse_module(read_fonts::tables::silf)] + +#[tag = "Silf"] +table Silf { + /// (major, minor) Version for the Silf table + #[version] + #[compile(self.compute_version())] + version: MajorMinor, + + #[since_version(3)] + compiler_version: MajorMinor, + + sub_tables: u16, + + #[since_version(2)] + #[skip_getter] + #[compile(0)] + _padding: u16, + + #[compile(self.compute_header_length())] + start_offset: Offset32, +} + +table SilfSubtable { + +} diff --git a/resources/codegen_inputs/sill.rs b/resources/codegen_inputs/sill.rs new file mode 100644 index 000000000..f20d6a929 --- /dev/null +++ b/resources/codegen_inputs/sill.rs @@ -0,0 +1,34 @@ +#![parse_module(read_fonts::tables::sill)] + +#[tag = "Sill"] +table Sill { + /// (major, minor) Version for the Sill table + #[version] + #[compile(self.compute_version())] + version: MajorMinor, + + #[compile(array_len($languages))] + num_langs: u16, + /// A power of two > num_langs + next_power_of_two: u16, + /// Rounded base-2 log of num_langs + log: u16, + /// Difference between next_power_of_two and num_langs + power_diff: i16, + + #[count($num_langs)] + languages: [Language], +} + +record Language { + language: Tag, + #[compile(array_len($settings))] + num_settings: u16, + #[read_offset_with($num_settings)] + settings_offset: Offset32<[SettingName]>, +} + +record SettingName { + value: u16, + name: NameId, +} diff --git a/resources/codegen_inputs/test_conditions.rs b/resources/codegen_inputs/test_conditions.rs index 19d60041d..cd3c60c7b 100644 --- a/resources/codegen_inputs/test_conditions.rs +++ b/resources/codegen_inputs/test_conditions.rs @@ -28,6 +28,8 @@ table FlagDay { bar: u16, #[if_cond(any_flag($flags, GotFlags::BAZ, GotFlags::FOO))] baz: u16, + #[if_cond(not_flag($flags, GotFlags::FOO))] + qux: u16, } table FieldsAfterConditionals { diff --git a/resources/codegen_plan.toml b/resources/codegen_plan.toml index 7bf42a930..49cf53028 100644 --- a/resources/codegen_plan.toml +++ b/resources/codegen_plan.toml @@ -453,6 +453,56 @@ mode = "compile" source = "resources/codegen_inputs/ift.rs" target = "write-fonts/generated/generated_ift.rs" +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/silf.rs" +target = "read-fonts/generated/generated_silf.rs" + +[[generate]] +mode = "compile" +source = "resources/codegen_inputs/silf.rs" +target = "write-fonts/generated/generated_silf.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/glat.rs" +target = "read-fonts/generated/generated_glat.rs" + +[[generate]] +mode = "compile" +source = "resources/codegen_inputs/glat.rs" +target = "write-fonts/generated/generated_glat.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/gloc.rs" +target = "read-fonts/generated/generated_gloc.rs" + +[[generate]] +mode = "compile" +source = "resources/codegen_inputs/gloc.rs" +target = "write-fonts/generated/generated_gloc.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/featgr.rs" +target = "read-fonts/generated/generated_featgr.rs" + +[[generate]] +mode = "compile" +source = "resources/codegen_inputs/featgr.rs" +target = "write-fonts/generated/generated_featgr.rs" + +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/sill.rs" +target = "read-fonts/generated/generated_sill.rs" + +[[generate]] +mode = "compile" +source = "resources/codegen_inputs/sill.rs" +target = "write-fonts/generated/generated_sill.rs" + # modules just used for testing [[generate]] mode = "parse" diff --git a/write-fonts/generated/generated_featgr.rs b/write-fonts/generated/generated_featgr.rs new file mode 100644 index 000000000..73249c636 --- /dev/null +++ b/write-fonts/generated/generated_featgr.rs @@ -0,0 +1,199 @@ +// 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::*; + +pub use read_fonts::tables::featgr::FeatureFlags; + +/// The graphite feature table - this is similar but not identical to apple's feature table. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Feat { + pub features: Vec, +} + +impl Feat { + /// Construct a new `Feat` + pub fn new(features: Vec) -> Self { + Self { features } + } +} + +impl FontWrite for Feat { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + let version = self.compute_version() as MajorMinor; + version.write_into(writer); + (u16::try_from(array_len(&self.features)).unwrap()).write_into(writer); + (0 as u16).write_into(writer); + (0 as u32).write_into(writer); + self.features.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::TopLevel(Feat::TAG) + } +} + +impl Validate for Feat { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Feat", |ctx| { + ctx.in_field("features", |ctx| { + if self.features.len() > (u16::MAX as usize) { + ctx.report("array exceeds max length"); + } + self.features.validate_impl(ctx); + }); + }) + } +} + +impl TopLevelTable for Feat { + const TAG: Tag = Tag::new(b"Feat"); +} + +impl<'a> FromObjRef> for Feat { + fn from_obj_ref(obj: &read_fonts::tables::featgr::Feat<'a>, _: FontData) -> Self { + let offset_data = obj.offset_data(); + Feat { + features: obj.features().to_owned_obj(offset_data), + } + } +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> FromTableRef> for Feat {} + +impl<'a> FontRead<'a> for Feat { + fn read(data: FontData<'a>) -> Result { + ::read(data).map(|x| x.to_owned_table()) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Feature { + pub feat_id: Option, + pub feat_id: Option, + pub settings: OffsetMarker, WIDTH_32>, + pub flags: FeatureFlags, + pub name_idx: NameId, +} + +impl Feature { + /// Construct a new `Feature` + pub fn new(settings: Vec, flags: FeatureFlags, name_idx: NameId) -> Self { + Self { + settings: settings.into(), + flags, + name_idx, + ..Default::default() + } + } +} + +impl FontWrite for Feature { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + version.compatible(3u16).then(|| { + self.feat_id + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + !version.compatible(3u16).then(|| { + self.feat_id + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + (u16::try_from(array_len(&self.settings)).unwrap()).write_into(writer); + version + .compatible(2u16) + .then(|| (0 as u16).write_into(writer)); + self.settings.write_into(writer); + self.flags.write_into(writer); + self.name_idx.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("Feature") + } +} + +impl Validate for Feature { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Feature", |ctx| { + ctx.in_field("feat_id", |ctx| { + if version.compatible(3u16) && self.feat_id.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }); + ctx.in_field("feat_id", |ctx| { + if !version.compatible(3u16) && self.feat_id.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }); + ctx.in_field("settings", |ctx| { + self.settings.validate_impl(ctx); + }); + }) + } +} + +impl FromObjRef for Feature { + fn from_obj_ref(obj: &read_fonts::tables::featgr::Feature, offset_data: FontData) -> Self { + Feature { + feat_id: obj.feat_id(), + feat_id: obj.feat_id(), + settings: obj.settings(offset_data).to_owned_obj(offset_data), + flags: obj.flags(), + name_idx: obj.name_idx(), + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Setting { + pub feature_id: u32, + pub value: u16, +} + +impl Setting { + /// Construct a new `Setting` + pub fn new(feature_id: u32, value: u16) -> Self { + Self { feature_id, value } + } +} + +impl FontWrite for Setting { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + self.feature_id.write_into(writer); + self.value.write_into(writer); + (0 as u16).write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("Setting") + } +} + +impl Validate for Setting { + fn validate_impl(&self, _ctx: &mut ValidationCtx) {} +} + +impl FromObjRef for Setting { + fn from_obj_ref(obj: &read_fonts::tables::featgr::Setting, _: FontData) -> Self { + Setting { + feature_id: obj.feature_id(), + value: obj.value(), + } + } +} + +impl FontWrite for FeatureFlags { + fn write_into(&self, writer: &mut TableWriter) { + writer.write_slice(&self.bits().to_be_bytes()) + } +} diff --git a/write-fonts/generated/generated_glat.rs b/write-fonts/generated/generated_glat.rs new file mode 100644 index 000000000..789a9af2c --- /dev/null +++ b/write-fonts/generated/generated_glat.rs @@ -0,0 +1,314 @@ +// 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::*; + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Glat { + pub octaboxes: Vec, + pub glyphs: Vec, +} + +impl Glat { + /// Construct a new `Glat` + pub fn new(octaboxes: Vec, glyphs: Vec) -> Self { + Self { + octaboxes, + glyphs, + ..Default::default() + } + } +} + +impl FontWrite for Glat { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + let version = self.compute_version() as MajorMinor; + version.write_into(writer); + version + .compatible((3u16, 0u16)) + .then(|| (1 as u32).write_into(writer)); + self.octaboxes.write_into(writer); + self.glyphs.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::TopLevel(Glat::TAG) + } +} + +impl Validate for Glat { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Glat", |ctx| { + let version: MajorMinor = self.compute_version(); + ctx.in_field("octaboxes", |ctx| { + if self.octaboxes.len() > (u16::MAX as usize) { + ctx.report("array exceeds max length"); + } + self.octaboxes.validate_impl(ctx); + }); + ctx.in_field("glyphs", |ctx| { + self.glyphs.validate_impl(ctx); + }); + }) + } +} + +impl TopLevelTable for Glat { + const TAG: Tag = Tag::new(b"Glat"); +} + +impl<'a> FromObjRef> for Glat { + fn from_obj_ref(obj: &read_fonts::tables::glat::Glat<'a>, _: FontData) -> Self { + let offset_data = obj.offset_data(); + Glat { + octaboxes: obj.octaboxes().to_owned_obj(offset_data), + glyphs: obj.glyphs().to_owned_obj(offset_data), + } + } +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> FromTableRef> for Glat {} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct OctaBox { + pub bitmap: u16, + pub dn_min: u8, + pub dn_max: u8, + pub dp_min: u8, + pub dp_max: u8, + pub sub_box: Vec, +} + +impl OctaBox { + /// Construct a new `OctaBox` + pub fn new( + bitmap: u16, + dn_min: u8, + dn_max: u8, + dp_min: u8, + dp_max: u8, + sub_box: Vec, + ) -> Self { + Self { + bitmap, + dn_min, + dn_max, + dp_min, + dp_max, + sub_box, + } + } +} + +impl FontWrite for OctaBox { + fn write_into(&self, writer: &mut TableWriter) { + self.bitmap.write_into(writer); + self.dn_min.write_into(writer); + self.dn_max.write_into(writer); + self.dp_min.write_into(writer); + self.dp_max.write_into(writer); + self.sub_box.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("OctaBox") + } +} + +impl Validate for OctaBox { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("OctaBox", |ctx| { + ctx.in_field("sub_box", |ctx| { + self.sub_box.validate_impl(ctx); + }); + }) + } +} + +impl FromObjRef> for OctaBox { + fn from_obj_ref(obj: &read_fonts::tables::glat::OctaBox, offset_data: FontData) -> Self { + OctaBox { + bitmap: obj.bitmap(), + dn_min: obj.dn_min(), + dn_max: obj.dn_max(), + dp_min: obj.dp_min(), + dp_max: obj.dp_max(), + sub_box: obj.sub_box().to_owned_obj(offset_data), + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GlyphAttrRun { + pub start: Option, + pub length: Option, + pub start: Option, + pub length: Option, + pub attrs: Vec, +} + +impl GlyphAttrRun { + /// Construct a new `GlyphAttrRun` + pub fn new(attrs: Vec) -> Self { + Self { + attrs, + ..Default::default() + } + } +} + +impl FontWrite for GlyphAttrRun { + fn write_into(&self, writer: &mut TableWriter) { + !version.compatible(2u16).then(|| { + self.start + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + !version.compatible(2u16).then(|| { + self.length + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + version.compatible(2u16).then(|| { + self.start + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + version.compatible(2u16).then(|| { + self.length + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + self.attrs.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("GlyphAttrRun") + } +} + +impl Validate for GlyphAttrRun { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("GlyphAttrRun", |ctx| { + ctx.in_field("start", |ctx| { + if !version.compatible(2u16) && self.start.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }); + ctx.in_field("length", |ctx| { + if !version.compatible(2u16) && self.length.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }); + ctx.in_field("start", |ctx| { + if version.compatible(2u16) && self.start.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }); + ctx.in_field("length", |ctx| { + if version.compatible(2u16) && self.length.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }); + ctx.in_field("attrs", |ctx| { + if self.attrs.len() > (u8::MAX as usize) { + ctx.report("array exceeds max length"); + } + }); + }) + } +} + +impl FromObjRef> for GlyphAttrRun { + fn from_obj_ref(obj: &read_fonts::tables::glat::GlyphAttrRun, offset_data: FontData) -> Self { + GlyphAttrRun { + start: obj.start(), + length: obj.length(), + start: obj.start(), + length: obj.length(), + attrs: obj.attrs().to_owned_obj(offset_data), + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SubBox { + pub left: u8, + pub right: u8, + pub bottom: u8, + pub top: u8, + pub dn_min: u8, + pub dn_max: u8, + pub dp_min: u8, + pub dp_max: u8, +} + +impl SubBox { + /// Construct a new `SubBox` + #[allow(clippy::too_many_arguments)] + pub fn new( + left: u8, + right: u8, + bottom: u8, + top: u8, + dn_min: u8, + dn_max: u8, + dp_min: u8, + dp_max: u8, + ) -> Self { + Self { + left, + right, + bottom, + top, + dn_min, + dn_max, + dp_min, + dp_max, + } + } +} + +impl FontWrite for SubBox { + fn write_into(&self, writer: &mut TableWriter) { + self.left.write_into(writer); + self.right.write_into(writer); + self.bottom.write_into(writer); + self.top.write_into(writer); + self.dn_min.write_into(writer); + self.dn_max.write_into(writer); + self.dp_min.write_into(writer); + self.dp_max.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("SubBox") + } +} + +impl Validate for SubBox { + fn validate_impl(&self, _ctx: &mut ValidationCtx) {} +} + +impl FromObjRef for SubBox { + fn from_obj_ref(obj: &read_fonts::tables::glat::SubBox, _: FontData) -> Self { + SubBox { + left: obj.left(), + right: obj.right(), + bottom: obj.bottom(), + top: obj.top(), + dn_min: obj.dn_min(), + dn_max: obj.dn_max(), + dp_min: obj.dp_min(), + dp_max: obj.dp_max(), + } + } +} diff --git a/write-fonts/generated/generated_gloc.rs b/write-fonts/generated/generated_gloc.rs new file mode 100644 index 000000000..342501571 --- /dev/null +++ b/write-fonts/generated/generated_gloc.rs @@ -0,0 +1,109 @@ +// 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::*; + +pub use read_fonts::tables::gloc::GlocFlags; + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Gloc { + pub flags: GlocFlags, + pub num_attrs: u16, + pub offsets: Option>, + pub offsets: Option>, +} + +impl Gloc { + /// Construct a new `Gloc` + pub fn new(flags: GlocFlags, num_attrs: u16) -> Self { + Self { + flags, + num_attrs, + ..Default::default() + } + } +} + +impl FontWrite for Gloc { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + let version = self.compute_version() as MajorMinor; + version.write_into(writer); + self.flags.write_into(writer); + self.num_attrs.write_into(writer); + self.flags.contains(GlocFlags::NEED_LONG_FORMAT).then(|| { + self.offsets + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + !self.flags.intersects(GlocFlags::NEED_LONG_FORMAT).then(|| { + self.offsets + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + } + fn table_type(&self) -> TableType { + TableType::TopLevel(Gloc::TAG) + } +} + +impl Validate for Gloc { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Gloc", |ctx| { + let version: MajorMinor = self.compute_version(); + let flags = self.flags; + ctx.in_field("offsets", |ctx| { + if !(flags.contains(GlocFlags::NEED_LONG_FORMAT)) && self.offsets.is_some() { + ctx.report("'offsets' is present but NEED_LONG_FORMAT not set") + } + if (flags.contains(GlocFlags::NEED_LONG_FORMAT)) && self.offsets.is_none() { + ctx.report("NEED_LONG_FORMAT is set but 'offsets' is None") + } + }); + ctx.in_field("offsets", |ctx| { + if !(!flags.intersects(GlocFlags::NEED_LONG_FORMAT)) && self.offsets.is_some() { + ctx.report("if_cond is not satisfied but 'offsets' is not present."); + } + if (!flags.intersects(GlocFlags::NEED_LONG_FORMAT)) && self.offsets.is_none() { + ctx.report("if_cond is satisfied by 'offsets' is present."); + } + }); + }) + } +} + +impl TopLevelTable for Gloc { + const TAG: Tag = Tag::new(b"Gloc"); +} + +impl<'a> FromObjRef> for Gloc { + fn from_obj_ref(obj: &read_fonts::tables::gloc::Gloc<'a>, _: FontData) -> Self { + let offset_data = obj.offset_data(); + Gloc { + flags: obj.flags(), + num_attrs: obj.num_attrs(), + offsets: obj.offsets().to_owned_obj(offset_data), + offsets: obj.offsets().to_owned_obj(offset_data), + } + } +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> FromTableRef> for Gloc {} + +impl<'a> FontRead<'a> for Gloc { + fn read(data: FontData<'a>) -> Result { + ::read(data).map(|x| x.to_owned_table()) + } +} + +impl FontWrite for GlocFlags { + fn write_into(&self, writer: &mut TableWriter) { + writer.write_slice(&self.bits().to_be_bytes()) + } +} diff --git a/write-fonts/generated/generated_silf.rs b/write-fonts/generated/generated_silf.rs new file mode 100644 index 000000000..faf3928a4 --- /dev/null +++ b/write-fonts/generated/generated_silf.rs @@ -0,0 +1,117 @@ +// 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::*; + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Silf { + pub compiler_version: Option, + pub sub_tables: u16, +} + +impl Silf { + /// Construct a new `Silf` + pub fn new(sub_tables: u16) -> Self { + Self { + sub_tables, + ..Default::default() + } + } +} + +impl FontWrite for Silf { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + let version = self.compute_version() as MajorMinor; + version.write_into(writer); + version.compatible(3u16).then(|| { + self.compiler_version + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); + self.sub_tables.write_into(writer); + version + .compatible(2u16) + .then(|| (0 as u16).write_into(writer)); + (self.compute_header_length() as Offset32).write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::TopLevel(Silf::TAG) + } +} + +impl Validate for Silf { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Silf", |ctx| { + let version: MajorMinor = self.compute_version(); + ctx.in_field("compiler_version", |ctx| { + if version.compatible(3u16) && self.compiler_version.is_none() { + ctx.report(format!("field must be present for version {version}")); + } + }); + }) + } +} + +impl TopLevelTable for Silf { + const TAG: Tag = Tag::new(b"Silf"); +} + +impl<'a> FromObjRef> for Silf { + fn from_obj_ref(obj: &read_fonts::tables::silf::Silf<'a>, _: FontData) -> Self { + Silf { + compiler_version: obj.compiler_version(), + sub_tables: obj.sub_tables(), + } + } +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> FromTableRef> for Silf {} + +impl<'a> FontRead<'a> for Silf { + fn read(data: FontData<'a>) -> Result { + ::read(data).map(|x| x.to_owned_table()) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SilfSubtable {} + +impl SilfSubtable { + /// Construct a new `SilfSubtable` + pub fn new() -> Self { + Self {} + } +} + +impl FontWrite for SilfSubtable { + fn write_into(&self, writer: &mut TableWriter) {} + fn table_type(&self) -> TableType { + TableType::Named("SilfSubtable") + } +} + +impl Validate for SilfSubtable { + fn validate_impl(&self, _ctx: &mut ValidationCtx) {} +} + +impl<'a> FromObjRef> for SilfSubtable { + fn from_obj_ref(obj: &read_fonts::tables::silf::SilfSubtable<'a>, _: FontData) -> Self { + SilfSubtable {} + } +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> FromTableRef> for SilfSubtable {} + +impl<'a> FontRead<'a> for SilfSubtable { + fn read(data: FontData<'a>) -> Result { + ::read(data).map(|x| x.to_owned_table()) + } +} diff --git a/write-fonts/generated/generated_sill.rs b/write-fonts/generated/generated_sill.rs new file mode 100644 index 000000000..5462572a3 --- /dev/null +++ b/write-fonts/generated/generated_sill.rs @@ -0,0 +1,174 @@ +// 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::*; + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Sill { + /// A power of two > num_langs + pub next_power_of_two: u16, + /// Rounded base-2 log of num_langs + pub log: u16, + /// Difference between next_power_of_two and num_langs + pub power_diff: i16, + pub languages: Vec, +} + +impl Sill { + /// Construct a new `Sill` + pub fn new( + next_power_of_two: u16, + log: u16, + power_diff: i16, + languages: Vec, + ) -> Self { + Self { + next_power_of_two, + log, + power_diff, + languages, + } + } +} + +impl FontWrite for Sill { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + let version = self.compute_version() as MajorMinor; + version.write_into(writer); + (u16::try_from(array_len(&self.languages)).unwrap()).write_into(writer); + self.next_power_of_two.write_into(writer); + self.log.write_into(writer); + self.power_diff.write_into(writer); + self.languages.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::TopLevel(Sill::TAG) + } +} + +impl Validate for Sill { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Sill", |ctx| { + ctx.in_field("languages", |ctx| { + if self.languages.len() > (u16::MAX as usize) { + ctx.report("array exceeds max length"); + } + self.languages.validate_impl(ctx); + }); + }) + } +} + +impl TopLevelTable for Sill { + const TAG: Tag = Tag::new(b"Sill"); +} + +impl<'a> FromObjRef> for Sill { + fn from_obj_ref(obj: &read_fonts::tables::sill::Sill<'a>, _: FontData) -> Self { + let offset_data = obj.offset_data(); + Sill { + next_power_of_two: obj.next_power_of_two(), + log: obj.log(), + power_diff: obj.power_diff(), + languages: obj.languages().to_owned_obj(offset_data), + } + } +} + +#[allow(clippy::needless_lifetimes)] +impl<'a> FromTableRef> for Sill {} + +impl<'a> FontRead<'a> for Sill { + fn read(data: FontData<'a>) -> Result { + ::read(data).map(|x| x.to_owned_table()) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Language { + pub language: Tag, + pub settings: OffsetMarker, WIDTH_32>, +} + +impl Language { + /// Construct a new `Language` + pub fn new(language: Tag, settings: Vec) -> Self { + Self { + language, + settings: settings.into(), + } + } +} + +impl FontWrite for Language { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + self.language.write_into(writer); + (u16::try_from(array_len(&self.settings)).unwrap()).write_into(writer); + self.settings.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("Language") + } +} + +impl Validate for Language { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Language", |ctx| { + ctx.in_field("settings", |ctx| { + self.settings.validate_impl(ctx); + }); + }) + } +} + +impl FromObjRef for Language { + fn from_obj_ref(obj: &read_fonts::tables::sill::Language, offset_data: FontData) -> Self { + Language { + language: obj.language(), + settings: obj.settings(offset_data).to_owned_obj(offset_data), + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SettingName { + pub value: u16, + pub name: NameId, +} + +impl SettingName { + /// Construct a new `SettingName` + pub fn new(value: u16, name: NameId) -> Self { + Self { value, name } + } +} + +impl FontWrite for SettingName { + fn write_into(&self, writer: &mut TableWriter) { + self.value.write_into(writer); + self.name.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("SettingName") + } +} + +impl Validate for SettingName { + fn validate_impl(&self, _ctx: &mut ValidationCtx) {} +} + +impl FromObjRef for SettingName { + fn from_obj_ref(obj: &read_fonts::tables::sill::SettingName, _: FontData) -> Self { + SettingName { + value: obj.value(), + name: obj.name(), + } + } +} diff --git a/write-fonts/generated/generated_test_conditions.rs b/write-fonts/generated/generated_test_conditions.rs index 225e95a91..aedc7b41a 100644 --- a/write-fonts/generated/generated_test_conditions.rs +++ b/write-fonts/generated/generated_test_conditions.rs @@ -111,6 +111,7 @@ pub struct FlagDay { pub foo: Option, pub bar: Option, pub baz: Option, + pub qux: Option, } impl FlagDay { @@ -148,6 +149,12 @@ impl FontWrite for FlagDay { .expect("missing conditional field should have failed validation") .write_into(writer) }); + !self.flags.intersects(GotFlags::FOO).then(|| { + self.qux + .as_ref() + .expect("missing conditional field should have failed validation") + .write_into(writer) + }); } fn table_type(&self) -> TableType { TableType::Named("FlagDay") @@ -182,6 +189,14 @@ impl Validate for FlagDay { ctx.report("if_cond is satisfied by 'baz' is not present."); } }); + ctx.in_field("qux", |ctx| { + if !(!flags.intersects(GotFlags::FOO)) && self.qux.is_some() { + ctx.report("if_cond is not satisfied but 'qux' is not present."); + } + if (!flags.intersects(GotFlags::FOO)) && self.qux.is_none() { + ctx.report("if_cond is satisfied by 'qux' is present."); + } + }); }) } } @@ -194,6 +209,7 @@ impl<'a> FromObjRef> for FlagD foo: obj.foo(), bar: obj.bar(), baz: obj.baz(), + qux: obj.qux(), } } }