From 528277e7ec6ec6fd99e33b3753b678366cd2b733 Mon Sep 17 00:00:00 2001 From: Colin Rofls Date: Fri, 18 Oct 2024 19:45:32 -0400 Subject: [PATCH 1/2] [write] Add common util for computing searchRange This is shared in a few places, so it makes sense to have a single implementation. (It is used in the kern table, which is coming up) --- write-fonts/src/font_builder.rs | 15 +++++++-------- write-fonts/src/tables/cmap.rs | 20 ++++++-------------- write-fonts/src/util.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/write-fonts/src/font_builder.rs b/write-fonts/src/font_builder.rs index 0c56c6322..ad62e14c0 100644 --- a/write-fonts/src/font_builder.rs +++ b/write-fonts/src/font_builder.rs @@ -6,6 +6,8 @@ use std::{borrow::Cow, fmt::Display}; use read_fonts::{FontRef, TableProvider}; use types::{Tag, TT_SFNT_VERSION}; +use crate::util::SearchRange; + include!("../generated/generated_font.rs"); const TABLE_RECORD_LEN: usize = 16; @@ -32,19 +34,16 @@ pub struct BuilderError { impl TableDirectory { pub fn from_table_records(table_records: Vec) -> TableDirectory { assert!(table_records.len() <= u16::MAX as usize); + const TABLE_RECORD_LEN: usize = 16; // See https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory - // Computation works at the largest allowable num tables so don't stress the as u16's - let entry_selector = (table_records.len() as f64).log2().floor() as u16; - let search_range = (2.0_f64.powi(entry_selector as i32) * 16.0) as u16; - // The result doesn't really make sense with 0 tables but ... let's at least not fail - let range_shift = (table_records.len() * 16).saturating_sub(search_range as usize) as u16; + let computed = SearchRange::compute(table_records.len(), TABLE_RECORD_LEN); TableDirectory::new( TT_SFNT_VERSION, - search_range, - entry_selector, - range_shift, + computed.search_range, + computed.entry_selector, + computed.range_shift, table_records, ) } diff --git a/write-fonts/src/tables/cmap.rs b/write-fonts/src/tables/cmap.rs index b64928700..a993efe52 100644 --- a/write-fonts/src/tables/cmap.rs +++ b/write-fonts/src/tables/cmap.rs @@ -6,6 +6,8 @@ include!("../../generated/generated_cmap.rs"); use std::collections::HashMap; +use crate::util::SearchRange; + // https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#windows-platform-platform-id--3 const WINDOWS_BMP_ENCODING: u16 = 1; const WINDOWS_FULL_REPERTOIRE_ENCODING: u16 = 10; @@ -96,26 +98,16 @@ impl CmapSubtable { ); let seg_count: u16 = start_code.len().try_into().unwrap(); - // Spec: Log2 of the maximum power of 2 less than or equal to segCount (log2(searchRange/2), - // which is equal to floor(log2(segCount))) - let entry_selector = (seg_count as f32).log2().floor(); - - // Spec: Maximum power of 2 less than or equal to segCount, times 2 - // ((2**floor(log2(segCount))) * 2, where “**” is an exponentiation operator) - let search_range = 2u16.pow(entry_selector as u32).checked_mul(2).unwrap(); - - // if 2^entry_selector*2 is a u16 then so is entry_selector - let entry_selector = entry_selector as u16; - let range_shift = seg_count * 2 - search_range; + let computed = SearchRange::compute(seg_count as _, u16::RAW_BYTE_LEN); let id_range_offsets = vec![0; id_deltas.len()]; Some(CmapSubtable::format_4( size_of_cmap4(seg_count, 0), 0, // 'lang' set to zero for all 'cmap' subtables whose platform IDs are other than Macintosh seg_count * 2, - search_range, - entry_selector, - range_shift, + computed.search_range, + computed.entry_selector, + computed.range_shift, end_code, start_code, id_deltas, diff --git a/write-fonts/src/util.rs b/write-fonts/src/util.rs index 8b7ceba18..d5b76c41d 100644 --- a/write-fonts/src/util.rs +++ b/write-fonts/src/util.rs @@ -100,3 +100,29 @@ impl Default for FloatComparator { pub fn isclose(a: f64, b: f64) -> bool { FloatComparator::default().isclose(a, b) } + +/// Search range values used in various tables +#[derive(Clone, Copy, Debug)] +pub struct SearchRange { + pub search_range: u16, + pub entry_selector: u16, + pub range_shift: u16, +} + +impl SearchRange { + //https://github.com/fonttools/fonttools/blob/729b3d2960ef/Lib/fontTools/ttLib/ttFont.py#L1147 + /// calculate searchRange, entrySelector, and rangeShift + /// + /// these values are used in various tables. + pub fn compute(n_items: usize, item_size: usize) -> Self { + let entry_selector = (n_items as f64).log2().floor() as usize; + let search_range = (2.0_f64.powi(entry_selector as i32) * item_size as f64) as usize; + // The result doesn't really make sense with 0 tables but ... let's at least not fail + let range_shift = (n_items * item_size).saturating_sub(search_range); + SearchRange { + search_range: search_range.try_into().unwrap(), + entry_selector: entry_selector.try_into().unwrap(), + range_shift: range_shift.try_into().unwrap(), + } + } +} From 2e494029d96311e588d3bd73c4045cc415147680 Mon Sep 17 00:00:00 2001 From: Colin Rofls Date: Fri, 18 Oct 2024 19:43:44 -0400 Subject: [PATCH 2/2] Add support for the 'kern' table This supports only the kern0 subtable type, and we should probably add a nice builder to make this a bit easier. --- font-test-data/src/kern.rs | 17 + font-test-data/src/lib.rs | 1 + read-fonts/generated/generated_kern.rs | 649 ++++++++++++++++++++++++ read-fonts/src/tables.rs | 1 + read-fonts/src/tables/kern.rs | 55 ++ resources/codegen_inputs/kern.rs | 94 ++++ resources/codegen_plan.toml | 10 + write-fonts/generated/generated_kern.rs | 213 ++++++++ write-fonts/src/tables.rs | 1 + write-fonts/src/tables/kern.rs | 43 ++ 10 files changed, 1084 insertions(+) create mode 100644 font-test-data/src/kern.rs create mode 100644 read-fonts/generated/generated_kern.rs create mode 100644 read-fonts/src/tables/kern.rs create mode 100644 resources/codegen_inputs/kern.rs create mode 100644 write-fonts/generated/generated_kern.rs create mode 100644 write-fonts/src/tables/kern.rs diff --git a/font-test-data/src/kern.rs b/font-test-data/src/kern.rs new file mode 100644 index 000000000..6db12cf81 --- /dev/null +++ b/font-test-data/src/kern.rs @@ -0,0 +1,17 @@ +// from https://github.com/fonttools/fonttools/blob/729b3d2960efd3/Tests/ttLib/tables/_k_e_r_n_test.py#L9 +#[rustfmt::skip] +pub static KERN_VER_0_FMT_0_DATA: &[u8] = &[ + 0x00, 0x00, // "0000 " # 0: version=0 + 0x00, 0x01, // "0001 " # 2: nTables=1 + 0x00, 0x00, // "0000 " # 4: version=0 (bogus field, unused) + 0x00, 0x20, // "0020 " # 6: length=32 + 0x00, // "00 " # 8: format=0 + 0x01, // "01 " # 9: coverage=1 + 0x00, 0x03, // "0003 " # 10: nPairs=3 + 0x00, 0x0C, // "000C " # 12: searchRange=12 + 0x00, 0x01, // "0001 " # 14: entrySelector=1 + 0x00, 0x06, // "0006 " # 16: rangeShift=6 + 0x00, 0x04, 0x00, 0x0C, 0xFF, 0xD8, // "0004 000C FFD8 " # 18: l=4, r=12, v=-40 + 0x00, 0x04, 0x00, 0x1C, 0x00, 0x28, // "0004 001C 0028 " # 24: l=4, r=28, v=40 + 0x00, 0x05, 0x00, 0x28, 0xFF, 0xCE, // "0005 0028 FFCE " # 30: l=5, r=40, v=-50 +]; diff --git a/font-test-data/src/lib.rs b/font-test-data/src/lib.rs index 0b6242363..aeca8adc8 100644 --- a/font-test-data/src/lib.rs +++ b/font-test-data/src/lib.rs @@ -5,6 +5,7 @@ pub mod gdef; pub mod gpos; pub mod gsub; pub mod ift; +pub mod kern; pub mod layout; pub static CMAP12_FONT1: &[u8] = include_bytes!("../test_data/ttf/cmap12_font1.ttf"); diff --git a/read-fonts/generated/generated_kern.rs b/read-fonts/generated/generated_kern.rs new file mode 100644 index 000000000..b71e5fb57 --- /dev/null +++ b/read-fonts/generated/generated_kern.rs @@ -0,0 +1,649 @@ +// 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 [kern (Kerning)](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) table +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct KernMarker { + subtables_byte_len: usize, +} + +impl KernMarker { + fn version_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn num_tables_byte_range(&self) -> Range { + let start = self.version_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn subtables_byte_range(&self) -> Range { + let start = self.num_tables_byte_range().end; + start..start + self.subtables_byte_len + } +} + +impl TopLevelTable for Kern<'_> { + /// `kern` + const TAG: Tag = Tag::new(b"kern"); +} + +impl<'a> FontRead<'a> for Kern<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + let num_tables: u16 = cursor.read()?; + let subtables_byte_len = { + let data = cursor.remaining().ok_or(ReadError::OutOfBounds)?; + ::total_len_for_count(data, num_tables as usize)? + }; + cursor.advance_by(subtables_byte_len); + cursor.finish(KernMarker { subtables_byte_len }) + } +} + +/// The [kern (Kerning)](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) table +pub type Kern<'a> = TableRef<'a, KernMarker>; + +impl<'a> Kern<'a> { + /// Table version number — set to 0. + pub fn version(&self) -> u16 { + let range = self.shape.version_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Number of subtables in the kerning table + pub fn num_tables(&self) -> u16 { + let range = self.shape.num_tables_byte_range(); + self.data.read_at(range.start).unwrap() + } + + pub fn subtables(&self) -> VarLenArray<'a, Kern0> { + let range = self.shape.subtables_byte_range(); + VarLenArray::read(self.data.split_off(range.start).unwrap()).unwrap() + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Kern<'a> { + fn type_name(&self) -> &str { + "Kern" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("version", self.version())), + 1usize => Some(Field::new("num_tables", self.num_tables())), + 2usize => Some(Field::new("subtables", traversal::FieldType::Unknown)), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> std::fmt::Debug for Kern<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self as &dyn SomeTable<'a>).fmt(f) + } +} + +/// The `macStyle` field for the head table. +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, bytemuck :: AnyBitPattern)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[repr(transparent)] +pub struct KernCoverage { + bits: u16, +} + +impl KernCoverage { + /// Bit 0: 1 if horizontal, 0 if vertical + pub const HORIZONTAL: Self = Self { bits: 0x0001 }; + + /// Bit 1: 1 if table contains minimum values, 0 if kern values + pub const MINIMUM: Self = Self { bits: 0x0002 }; + + /// Bit 2: If set to 1, kerning is perpendicular to the flow of the text. + /// + /// If the text is normally written horizontally, kerning will be done in + /// the up and down directions. If kerning values are positive, the text + /// will be kerned upwards; if they are negative, the text will be kerned + /// downwards. + /// + /// If the text is normally written vertically, kerning will be done in the + /// left and right directions. If kerning values are positive, the text + /// will be kerned to the right; if they are negative, the text will be + /// kerned to the left. + /// + /// The value 0x8000 in the kerning data resets the cross-stream kerning + /// back to 0. + pub const CROSS_STREAM: Self = Self { bits: 0x0004 }; + + /// Bit 3: If this bit is set to 1 the value in this table should replace + /// the value currently being accumulated. + pub const OVERRIDE: Self = Self { bits: 0x0008 }; + + /// Bit 4: Shadow (if set to 1) + pub const SHADOW: Self = Self { bits: 0x0010 }; + + /// Bit 5: Condensed (if set to 1) + pub const CONDENSED: Self = Self { bits: 0x0020 }; + + /// Bit 6: Extended (if set to 1) + pub const EXTENDED: Self = Self { bits: 0x0040 }; +} + +impl KernCoverage { + /// 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.bits + | Self::MINIMUM.bits + | Self::CROSS_STREAM.bits + | Self::OVERRIDE.bits + | Self::SHADOW.bits + | Self::CONDENSED.bits + | Self::EXTENDED.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 KernCoverage { + type Output = Self; + + /// Returns the union of the two sets of flags. + #[inline] + fn bitor(self, other: KernCoverage) -> Self { + Self { + bits: self.bits | other.bits, + } + } +} + +impl std::ops::BitOrAssign for KernCoverage { + /// Adds the set of flags. + #[inline] + fn bitor_assign(&mut self, other: Self) { + self.bits |= other.bits; + } +} + +impl std::ops::BitXor for KernCoverage { + 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 KernCoverage { + /// Toggles the set of flags. + #[inline] + fn bitxor_assign(&mut self, other: Self) { + self.bits ^= other.bits; + } +} + +impl std::ops::BitAnd for KernCoverage { + 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 KernCoverage { + /// Disables all flags disabled in the set. + #[inline] + fn bitand_assign(&mut self, other: Self) { + self.bits &= other.bits; + } +} + +impl std::ops::Sub for KernCoverage { + 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 KernCoverage { + /// Disables all flags enabled in the set. + #[inline] + fn sub_assign(&mut self, other: Self) { + self.bits &= !other.bits; + } +} + +impl std::ops::Not for KernCoverage { + 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 KernCoverage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let members: &[(&str, Self)] = &[ + ("HORIZONTAL", Self::HORIZONTAL), + ("MINIMUM", Self::MINIMUM), + ("CROSS_STREAM", Self::CROSS_STREAM), + ("OVERRIDE", Self::OVERRIDE), + ("SHADOW", Self::SHADOW), + ("CONDENSED", Self::CONDENSED), + ("EXTENDED", Self::EXTENDED), + ]; + 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 KernCoverage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Binary::fmt(&self.bits, f) + } +} + +impl std::fmt::Octal for KernCoverage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Octal::fmt(&self.bits, f) + } +} + +impl std::fmt::LowerHex for KernCoverage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::LowerHex::fmt(&self.bits, f) + } +} + +impl std::fmt::UpperHex for KernCoverage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::UpperHex::fmt(&self.bits, f) + } +} + +impl font_types::Scalar for KernCoverage { + 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: KernCoverage) -> FieldType<'a> { + src.bits().into() + } +} + +impl Format for Kern0Marker { + const FORMAT: u16 = 0; +} + +/// [kern Format 0](https://docs.microsoft.com/en-us/typography/opentype/spec/kern#format-0) +#[derive(Debug, Clone, Copy)] +#[doc(hidden)] +pub struct Kern0Marker { + kerning_pairs_byte_len: usize, +} + +impl Kern0Marker { + fn format_byte_range(&self) -> Range { + let start = 0; + start..start + u16::RAW_BYTE_LEN + } + fn length_byte_range(&self) -> Range { + let start = self.format_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn coverage_byte_range(&self) -> Range { + let start = self.length_byte_range().end; + start..start + KernCoverage::RAW_BYTE_LEN + } + fn num_pairs_byte_range(&self) -> Range { + let start = self.coverage_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn search_range_byte_range(&self) -> Range { + let start = self.num_pairs_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn entry_selector_byte_range(&self) -> Range { + let start = self.search_range_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn range_shift_byte_range(&self) -> Range { + let start = self.entry_selector_byte_range().end; + start..start + u16::RAW_BYTE_LEN + } + fn kerning_pairs_byte_range(&self) -> Range { + let start = self.range_shift_byte_range().end; + start..start + self.kerning_pairs_byte_len + } +} + +impl<'a> FontRead<'a> for Kern0<'a> { + fn read(data: FontData<'a>) -> Result { + let mut cursor = data.cursor(); + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let num_pairs: u16 = cursor.read()?; + cursor.advance::(); + cursor.advance::(); + cursor.advance::(); + let kerning_pairs_byte_len = (num_pairs as usize) + .checked_mul(KernPair::RAW_BYTE_LEN) + .ok_or(ReadError::OutOfBounds)?; + cursor.advance_by(kerning_pairs_byte_len); + cursor.finish(Kern0Marker { + kerning_pairs_byte_len, + }) + } +} + +/// [kern Format 0](https://docs.microsoft.com/en-us/typography/opentype/spec/kern#format-0) +pub type Kern0<'a> = TableRef<'a, Kern0Marker>; + +impl<'a> Kern0<'a> { + /// Format number is set to 0. + pub fn format(&self) -> u16 { + let range = self.shape.format_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// The length of the subtable, in bytes (including this header). + pub fn length(&self) -> u16 { + let range = self.shape.length_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// What type of information is contained in this table. + pub fn coverage(&self) -> KernCoverage { + let range = self.shape.coverage_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// This gives the number of kerning pairs in the table. + pub fn num_pairs(&self) -> u16 { + let range = self.shape.num_pairs_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// The largest power of two less than or equal to the value of num_pairs, multiplied by the + /// size in bytes of an entry in the table. + pub fn search_range(&self) -> u16 { + let range = self.shape.search_range_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// This is calculated as log2 of the largest power of two less than or equal to the value of num_pairs. + /// This value indicates how many iterations of the search loop will have to be made. + /// (For example, in a list of eight items, there would have to be three iterations of the loop). + pub fn entry_selector(&self) -> u16 { + let range = self.shape.entry_selector_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// The value of num_pairs minus the largest power of two less than or equal to num_pairs, + /// and then multiplied by the size in bytes of an entry in the table. + pub fn range_shift(&self) -> u16 { + let range = self.shape.range_shift_byte_range(); + self.data.read_at(range.start).unwrap() + } + + /// Kern pairs + pub fn kerning_pairs(&self) -> &'a [KernPair] { + let range = self.shape.kerning_pairs_byte_range(); + self.data.read_array(range).unwrap() + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeTable<'a> for Kern0<'a> { + fn type_name(&self) -> &str { + "Kern0" + } + fn get_field(&self, idx: usize) -> Option> { + match idx { + 0usize => Some(Field::new("format", self.format())), + 1usize => Some(Field::new("length", self.length())), + 2usize => Some(Field::new("coverage", self.coverage())), + 3usize => Some(Field::new("num_pairs", self.num_pairs())), + 4usize => Some(Field::new("search_range", self.search_range())), + 5usize => Some(Field::new("entry_selector", self.entry_selector())), + 6usize => Some(Field::new("range_shift", self.range_shift())), + 7usize => Some(Field::new( + "kerning_pairs", + traversal::FieldType::array_of_records( + stringify!(KernPair), + self.kerning_pairs(), + self.offset_data(), + ), + )), + _ => None, + } + } +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> std::fmt::Debug for Kern0<'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, Copy, bytemuck :: AnyBitPattern)] +#[repr(C)] +#[repr(packed)] +pub struct KernPair { + /// The glyph index for the left-hand glyph in the kerning pair. + pub left: BigEndian, + /// The glyph index for the right-hand glyph in the kerning pair. + pub right: BigEndian, + /// The kerning value for the above pair, in font design units. + /// If this value is greater than zero, the characters will be moved apart. + /// If this value is less than zero, the character will be moved closer together. + pub value: BigEndian, +} + +impl KernPair { + /// The glyph index for the left-hand glyph in the kerning pair. + pub fn left(&self) -> u16 { + self.left.get() + } + + /// The glyph index for the right-hand glyph in the kerning pair. + pub fn right(&self) -> u16 { + self.right.get() + } + + /// The kerning value for the above pair, in font design units. + /// If this value is greater than zero, the characters will be moved apart. + /// If this value is less than zero, the character will be moved closer together. + pub fn value(&self) -> FWord { + self.value.get() + } +} + +impl FixedSize for KernPair { + const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + FWord::RAW_BYTE_LEN; +} + +#[cfg(feature = "experimental_traverse")] +impl<'a> SomeRecord<'a> for KernPair { + fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> { + RecordResolver { + name: "KernPair", + 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("value", self.value())), + _ => None, + }), + data, + } + } +} diff --git a/read-fonts/src/tables.rs b/read-fonts/src/tables.rs index 9825cb332..e9d3d19ca 100644 --- a/read-fonts/src/tables.rs +++ b/read-fonts/src/tables.rs @@ -29,6 +29,7 @@ pub mod hhea; pub mod hmtx; pub mod hvar; pub mod ift; +pub mod kern; pub mod layout; pub mod loca; pub mod ltag; diff --git a/read-fonts/src/tables/kern.rs b/read-fonts/src/tables/kern.rs new file mode 100644 index 000000000..74ce4d5f0 --- /dev/null +++ b/read-fonts/src/tables/kern.rs @@ -0,0 +1,55 @@ +//! The [kern](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) table + +use crate::{FontRead, VarSize}; + +include!("../../generated/generated_kern.rs"); + +impl VarSize for Kern0<'_> { + type Size = u16; + + fn read_len_at(data: FontData, pos: usize) -> Option { + let length_offset = pos + std::mem::size_of::(); + data.read_at::(length_offset).ok().map(Into::into) + } +} + +#[cfg(test)] +mod tests { + use crate::FontData; + + use super::*; + fn kern_pair(left: u16, right: u16, value: i16) -> KernPair { + KernPair { + left: left.into(), + right: right.into(), + value: FWord::new(value).into(), + } + } + + #[test] + fn smoke_test() { + let data = FontData::new(font_test_data::kern::KERN_VER_0_FMT_0_DATA); + let kern = Kern::read(data).unwrap(); + assert_eq!(kern.version(), 0); + assert_eq!(kern.num_tables(), 1); + + let subtable = kern.subtables().iter().next().unwrap().unwrap(); + assert_eq!(subtable.format(), 0); + assert_eq!(subtable.length(), 32); + assert_eq!(subtable.coverage(), KernCoverage::HORIZONTAL); + assert_eq!(subtable.num_pairs(), 3); + assert_eq!(subtable.search_range(), 12); + assert_eq!(subtable.entry_selector(), 1); + assert_eq!(subtable.range_shift(), 6); + + let pairs = subtable.kerning_pairs(); + assert_eq!( + pairs, + [ + kern_pair(4, 12, -40), + kern_pair(4, 28, 40), + kern_pair(5, 40, -50), + ] + ) + } +} diff --git a/resources/codegen_inputs/kern.rs b/resources/codegen_inputs/kern.rs new file mode 100644 index 000000000..fe2baabb8 --- /dev/null +++ b/resources/codegen_inputs/kern.rs @@ -0,0 +1,94 @@ +#![parse_module(read_fonts::tables::kern)] + +/// The [kern (Kerning)](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) table +#[tag = "kern"] +table Kern { + /// Table version number — set to 0. + #[compile(0)] + version: u16, + /// Number of subtables in the kerning table + #[compile(array_len($subtables))] + num_tables: u16, + #[count($num_tables)] + #[traverse_with(skip)] + subtables: VarLenArray, +} + +///// The different kern subtable formats. +//format u16 KernSubtable { + //Format0(Kern0), + //// Nope. + //// Format2(Kern2), +//} + +/// The `macStyle` field for the head table. +flags u16 KernCoverage { + /// Bit 0: 1 if horizontal, 0 if vertical + HORIZONTAL = 0x0001, + /// Bit 1: 1 if table contains minimum values, 0 if kern values + MINIMUM = 0x0002, + /// Bit 2: If set to 1, kerning is perpendicular to the flow of the text. + /// + /// If the text is normally written horizontally, kerning will be done in + /// the up and down directions. If kerning values are positive, the text + /// will be kerned upwards; if they are negative, the text will be kerned + /// downwards. + /// + /// If the text is normally written vertically, kerning will be done in the + /// left and right directions. If kerning values are positive, the text + /// will be kerned to the right; if they are negative, the text will be + /// kerned to the left. + /// + /// The value 0x8000 in the kerning data resets the cross-stream kerning + /// back to 0. + CROSS_STREAM = 0x0004, + /// Bit 3: If this bit is set to 1 the value in this table should replace + /// the value currently being accumulated. + OVERRIDE = 0x0008, + /// Bit 4: Shadow (if set to 1) + SHADOW = 0x0010, + /// Bit 5: Condensed (if set to 1) + CONDENSED = 0x0020, + /// Bit 6: Extended (if set to 1) + EXTENDED = 0x0040, + // Bits 7-15: Reserved (set to 0) +} + +/// [kern Format 0](https://docs.microsoft.com/en-us/typography/opentype/spec/kern#format-0) +table Kern0 { + /// Format number is set to 0. + #[format = 0] + format: u16, + /// The length of the subtable, in bytes (including this header). + #[compile(self.compute_length())] + length: u16, + /// What type of information is contained in this table. + coverage: KernCoverage, + /// This gives the number of kerning pairs in the table. + #[compile(array_len($kerning_pairs))] + num_pairs: u16, + /// The largest power of two less than or equal to the value of num_pairs, multiplied by the + /// size in bytes of an entry in the table. + search_range: u16, + /// This is calculated as log2 of the largest power of two less than or equal to the value of num_pairs. + /// This value indicates how many iterations of the search loop will have to be made. + /// (For example, in a list of eight items, there would have to be three iterations of the loop). + entry_selector: u16, + /// The value of num_pairs minus the largest power of two less than or equal to num_pairs, + /// and then multiplied by the size in bytes of an entry in the table. + range_shift: u16, + /// Kern pairs + #[count($num_pairs)] + kerning_pairs: [KernPair], +} + +record KernPair { + /// The glyph index for the left-hand glyph in the kerning pair. + left: u16, + /// The glyph index for the right-hand glyph in the kerning pair. + right: u16, + /// The kerning value for the above pair, in font design units. + /// If this value is greater than zero, the characters will be moved apart. + /// If this value is less than zero, the character will be moved closer together. + value: FWord, +} diff --git a/resources/codegen_plan.toml b/resources/codegen_plan.toml index ee74d746c..e81357f17 100644 --- a/resources/codegen_plan.toml +++ b/resources/codegen_plan.toml @@ -398,6 +398,16 @@ mode = "compile" source = "resources/codegen_inputs/ift.rs" target = "write-fonts/generated/generated_ift.rs" +[[generate]] +mode = "parse" +source = "resources/codegen_inputs/kern.rs" +target = "read-fonts/generated/generated_kern.rs" + +[[generate]] +mode = "compile" +source = "resources/codegen_inputs/kern.rs" +target = "write-fonts/generated/generated_kern.rs" + # modules just used for testing [[generate]] mode = "parse" diff --git a/write-fonts/generated/generated_kern.rs b/write-fonts/generated/generated_kern.rs new file mode 100644 index 000000000..f983fefd1 --- /dev/null +++ b/write-fonts/generated/generated_kern.rs @@ -0,0 +1,213 @@ +// 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::kern::KernCoverage; + +/// The [kern (Kerning)](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) table +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Kern { + pub subtables: Vec, +} + +impl Kern { + /// Construct a new `Kern` + pub fn new(subtables: Vec) -> Self { + Self { subtables } + } +} + +impl FontWrite for Kern { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + (0 as u16).write_into(writer); + (array_len(&self.subtables).unwrap() as u16).write_into(writer); + self.subtables.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::TopLevel(Kern::TAG) + } +} + +impl Validate for Kern { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Kern", |ctx| { + ctx.in_field("subtables", |ctx| { + if self.subtables.len() > (u16::MAX as usize) { + ctx.report("array exceeds max length"); + } + self.subtables.validate_impl(ctx); + }); + }) + } +} + +impl TopLevelTable for Kern { + const TAG: Tag = Tag::new(b"kern"); +} + +impl<'a> FromObjRef> for Kern { + fn from_obj_ref(obj: &read_fonts::tables::kern::Kern<'a>, _: FontData) -> Self { + let offset_data = obj.offset_data(); + Kern { + subtables: obj + .subtables() + .iter() + .filter_map(|x| x.map(|x| FromObjRef::from_obj_ref(&x, offset_data)).ok()) + .collect(), + } + } +} + +impl<'a> FromTableRef> for Kern {} + +impl<'a> FontRead<'a> for Kern { + fn read(data: FontData<'a>) -> Result { + ::read(data).map(|x| x.to_owned_table()) + } +} + +impl FontWrite for KernCoverage { + fn write_into(&self, writer: &mut TableWriter) { + writer.write_slice(&self.bits().to_be_bytes()) + } +} + +/// [kern Format 0](https://docs.microsoft.com/en-us/typography/opentype/spec/kern#format-0) +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Kern0 { + /// What type of information is contained in this table. + pub coverage: KernCoverage, + /// The largest power of two less than or equal to the value of num_pairs, multiplied by the + /// size in bytes of an entry in the table. + pub search_range: u16, + /// This is calculated as log2 of the largest power of two less than or equal to the value of num_pairs. + /// This value indicates how many iterations of the search loop will have to be made. + /// (For example, in a list of eight items, there would have to be three iterations of the loop). + pub entry_selector: u16, + /// The value of num_pairs minus the largest power of two less than or equal to num_pairs, + /// and then multiplied by the size in bytes of an entry in the table. + pub range_shift: u16, + /// Kern pairs + pub kerning_pairs: Vec, +} + +impl Kern0 { + /// Construct a new `Kern0` + pub fn new( + coverage: KernCoverage, + search_range: u16, + entry_selector: u16, + range_shift: u16, + kerning_pairs: Vec, + ) -> Self { + Self { + coverage, + search_range, + entry_selector, + range_shift, + kerning_pairs: kerning_pairs.into_iter().map(Into::into).collect(), + } + } +} + +impl FontWrite for Kern0 { + #[allow(clippy::unnecessary_cast)] + fn write_into(&self, writer: &mut TableWriter) { + (0 as u16).write_into(writer); + (self.compute_length() as u16).write_into(writer); + self.coverage.write_into(writer); + (array_len(&self.kerning_pairs).unwrap() as u16).write_into(writer); + self.search_range.write_into(writer); + self.entry_selector.write_into(writer); + self.range_shift.write_into(writer); + self.kerning_pairs.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("Kern0") + } +} + +impl Validate for Kern0 { + fn validate_impl(&self, ctx: &mut ValidationCtx) { + ctx.in_table("Kern0", |ctx| { + ctx.in_field("kerning_pairs", |ctx| { + if self.kerning_pairs.len() > (u16::MAX as usize) { + ctx.report("array exceeds max length"); + } + self.kerning_pairs.validate_impl(ctx); + }); + }) + } +} + +impl<'a> FromObjRef> for Kern0 { + fn from_obj_ref(obj: &read_fonts::tables::kern::Kern0<'a>, _: FontData) -> Self { + let offset_data = obj.offset_data(); + Kern0 { + coverage: obj.coverage(), + search_range: obj.search_range(), + entry_selector: obj.entry_selector(), + range_shift: obj.range_shift(), + kerning_pairs: obj.kerning_pairs().to_owned_obj(offset_data), + } + } +} + +impl<'a> FromTableRef> for Kern0 {} + +impl<'a> FontRead<'a> for Kern0 { + 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 KernPair { + /// The glyph index for the left-hand glyph in the kerning pair. + pub left: u16, + /// The glyph index for the right-hand glyph in the kerning pair. + pub right: u16, + /// The kerning value for the above pair, in font design units. + /// If this value is greater than zero, the characters will be moved apart. + /// If this value is less than zero, the character will be moved closer together. + pub value: FWord, +} + +impl KernPair { + /// Construct a new `KernPair` + pub fn new(left: u16, right: u16, value: FWord) -> Self { + Self { left, right, value } + } +} + +impl FontWrite for KernPair { + fn write_into(&self, writer: &mut TableWriter) { + self.left.write_into(writer); + self.right.write_into(writer); + self.value.write_into(writer); + } + fn table_type(&self) -> TableType { + TableType::Named("KernPair") + } +} + +impl Validate for KernPair { + fn validate_impl(&self, _ctx: &mut ValidationCtx) {} +} + +impl FromObjRef for KernPair { + fn from_obj_ref(obj: &read_fonts::tables::kern::KernPair, _: FontData) -> Self { + KernPair { + left: obj.left(), + right: obj.right(), + value: obj.value(), + } + } +} diff --git a/write-fonts/src/tables.rs b/write-fonts/src/tables.rs index 1444b797d..b17fd8788 100644 --- a/write-fonts/src/tables.rs +++ b/write-fonts/src/tables.rs @@ -17,6 +17,7 @@ pub mod hhea; pub mod hmtx; pub mod hvar; pub mod ift; +pub mod kern; pub mod layout; pub mod loca; pub mod maxp; diff --git a/write-fonts/src/tables/kern.rs b/write-fonts/src/tables/kern.rs new file mode 100644 index 000000000..a7393e394 --- /dev/null +++ b/write-fonts/src/tables/kern.rs @@ -0,0 +1,43 @@ +//! The [kern](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) table + +include!("../../generated/generated_kern.rs"); + +impl Kern0 { + fn compute_length(&self) -> u16 { + const KERN_PAIR_LEN: usize = 6; + let len = u16::RAW_BYTE_LEN * 7 + // format, length, coverage, num_pairs, + // search_range, entry_selector, range_shift + self.kerning_pairs.len() * KERN_PAIR_LEN; + u16::try_from(len).unwrap() + } +} + +#[cfg(test)] +mod tests { + use crate::util::SearchRange; + + use super::*; + + #[test] + fn smoke_test() { + let pairs = vec![ + KernPair::new(4, 12, FWord::new(-40)), + KernPair::new(4, 28, FWord::new(40)), + KernPair::new(5, 40, FWord::new(-50)), + ]; + //searchRange, entrySelector, rangeShift = getSearchRange(pairs.len(), 6); + let computed = SearchRange::compute(pairs.len(), 6); + let kern0 = Kern0::new( + KernCoverage::HORIZONTAL, + computed.search_range, + computed.entry_selector, + computed.range_shift, + pairs, + ); + + let kern = Kern::new(vec![kern0]); + + let bytes = crate::dump_table(&kern).unwrap(); + assert_eq!(bytes, font_test_data::kern::KERN_VER_0_FMT_0_DATA); + } +}