Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

213
vendor/read-fonts/src/array.rs vendored Normal file
View File

@@ -0,0 +1,213 @@
//! Custom array types
#![deny(clippy::arithmetic_side_effects)]
use bytemuck::AnyBitPattern;
use font_types::FixedSize;
use crate::read::{ComputeSize, FontReadWithArgs, ReadArgs, VarSize};
use crate::{FontData, FontRead, ReadError};
/// An array whose items size is not known at compile time.
///
/// This requires the inner type to implement [`FontReadWithArgs`] as well as
/// [`ComputeSize`].
///
/// At runtime, `Args` are provided which will be used to compute the size
/// of each item; this size is then used to compute the positions of the items
/// within the underlying data, from which they will be read lazily.
#[derive(Clone)]
pub struct ComputedArray<'a, T: ReadArgs> {
// the length of each item
item_len: usize,
len: usize,
data: FontData<'a>,
args: T::Args,
}
impl<'a, T: ComputeSize> ComputedArray<'a, T> {
pub fn new(data: FontData<'a>, args: T::Args) -> Result<Self, ReadError> {
let item_len = T::compute_size(&args)?;
let len = data.len().checked_div(item_len).unwrap_or(0);
Ok(ComputedArray {
item_len,
len,
data,
args,
})
}
/// The number of items in the array
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<T: ReadArgs> ReadArgs for ComputedArray<'_, T> {
type Args = T::Args;
}
impl<'a, T> FontReadWithArgs<'a> for ComputedArray<'a, T>
where
T: ComputeSize + FontReadWithArgs<'a>,
T::Args: Copy,
{
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
Self::new(data, *args)
}
}
impl<'a, T> ComputedArray<'a, T>
where
T: FontReadWithArgs<'a>,
T::Args: Copy + 'static,
{
pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
let mut i = 0;
let data = self.data;
let args = self.args;
let item_len = self.item_len;
let len = self.len;
std::iter::from_fn(move || {
if i == len {
return None;
}
let item_start = item_len.checked_mul(i)?;
i = i.checked_add(1)?;
let data = data.split_off(item_start)?;
Some(T::read_with_args(data, &args))
})
}
pub fn get(&self, idx: usize) -> Result<T, ReadError> {
let item_start = idx
.checked_mul(self.item_len)
.ok_or(ReadError::OutOfBounds)?;
self.data
.split_off(item_start)
.ok_or(ReadError::OutOfBounds)
.and_then(|data| T::read_with_args(data, &self.args))
}
}
impl<T: ReadArgs> std::fmt::Debug for ComputedArray<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("DynSizedArray")
.field("bytes", &self.data)
.finish()
}
}
/// An array of items of non-uniform length.
///
/// Random access into this array cannot be especially efficient, since it requires
/// a linear scan.
pub struct VarLenArray<'a, T> {
data: FontData<'a>,
phantom: std::marker::PhantomData<*const T>,
}
impl<'a, T: FontRead<'a> + VarSize> VarLenArray<'a, T> {
/// Return the item at the provided index.
///
/// # Performance
///
/// Determining the position of an item in this collection requires looking
/// at all the preceding items; that is, it is `O(n)` instead of `O(1)` as
/// it would be for a `Vec`.
///
/// As a consequence, calling this method in a loop could potentially be
/// very slow. If this is something you need to do, it will probably be
/// much faster to first collect all the items into a `Vec` beforehand,
/// and then fetch them from there.
pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
let mut pos = 0usize;
for _ in 0..idx {
pos = pos.checked_add(T::read_len_at(self.data, pos)?)?;
}
self.data.split_off(pos).map(T::read)
}
/// Return an iterator over this array's items.
pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
let mut data = self.data;
std::iter::from_fn(move || {
if data.is_empty() {
return None;
}
let item_len = T::read_len_at(data, 0)?;
// If the length is 0 then then it's not useful to continue
// iteration. The subsequent read will probably fail but if
// the user is skipping malformed elements (which is common)
// this this iterator will continue forever.
if item_len == 0 {
return None;
}
let item_data = data.slice(..item_len)?;
let next = T::read(item_data);
data = data.split_off(item_len)?;
Some(next)
})
}
}
impl<'a, T> FontRead<'a> for VarLenArray<'a, T> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
Ok(VarLenArray {
data,
phantom: core::marker::PhantomData,
})
}
}
impl<T: AnyBitPattern> ReadArgs for &[T] {
type Args = u16;
}
impl<'a, T: AnyBitPattern + FixedSize> FontReadWithArgs<'a> for &'a [T] {
fn read_with_args(data: FontData<'a>, args: &u16) -> Result<Self, ReadError> {
let len = (*args as usize)
.checked_mul(T::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
data.read_array(0..len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen_test::records::VarLenItem;
use font_test_data::bebuffer::BeBuffer;
impl VarSize for VarLenItem<'_> {
type Size = u32;
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
data.read_at::<u32>(pos).ok().map(|len| len as usize)
}
}
/// HB/HarfRuzz test "shlana_9_006" has a morx table containing a chain
/// with a length of 0. This caused the VarLenArray iterator to loop
/// indefinitely.
#[test]
fn var_len_iter_with_zero_length_item() {
// Create a buffer containing three elements where the last
// has zero length
let mut buf = BeBuffer::new();
buf = buf.push(8u32).extend([0u8; 4]);
buf = buf.push(18u32).extend([0u8; 14]);
buf = buf.push(0u32);
let arr: VarLenArray<VarLenItem> = VarLenArray::read(FontData::new(buf.data())).unwrap();
// Ensure we don't iterate forever and only read two elements (the
// take() exists so that the test fails rather than hanging if the
// code regresses in the future)
assert_eq!(arr.iter().take(10).count(), 2);
}
}

338
vendor/read-fonts/src/codegen_test.rs vendored Normal file
View File

@@ -0,0 +1,338 @@
//! A module used to test codegen.
//!
//! This imports a single codegen output; while modifying the codegen crate,
//! this file can be regenerated to check that changes compile, without needing
//! to rebuild everything.
//!
//! To rebuild this input and test it, run:
//!
//! $ cargo run --bin=codegen resources/test_plan.toml && cargo test
pub mod records {
include!("../generated/generated_test_records.rs");
}
pub mod formats {
include!("../generated/generated_test_formats.rs");
}
pub mod offsets_arrays {
include!("../generated/generated_test_offsets_arrays.rs");
#[cfg(test)]
use font_test_data::bebuffer::BeBuffer;
pub struct VarSizeDummy<'a> {
#[allow(dead_code)]
count: u16,
pub bytes: &'a [u8],
}
impl VarSize for VarSizeDummy<'_> {
type Size = u16;
}
impl<'a> FontRead<'a> for VarSizeDummy<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let count: u16 = data.read_at(0)?;
let bytes = data
.as_bytes()
.get(2..2 + (count as usize))
.ok_or(ReadError::OutOfBounds)?;
Ok(Self { count, bytes })
}
}
#[test]
fn array_offsets() {
let builder = BeBuffer::new()
.push(MajorMinor::VERSION_1_0)
.push(12_u16) // offset to 0xdead
.push(0u16) // nullable
.push(2u16) // array len
.push(12u16) // array offset
.extend([0xdead_u16, 0xbeef]);
let table = KindsOfOffsets::read(builder.data().into()).unwrap();
assert_eq!(table.nonnullable().unwrap().value(), 0xdead);
let array = table.array().unwrap();
assert_eq!(array, &[0xdead, 0xbeef]);
}
#[test]
fn var_len_array_empty() {
let builder = BeBuffer::new().push(0u16).push(0xdeadbeef_u32);
let table = VarLenHaver::read(builder.data().into()).unwrap();
assert_eq!(table.other_field(), 0xdeadbeef);
}
#[test]
fn var_len_array_some() {
let builder = BeBuffer::new()
.push(3u16)
.push(0u16) // first item in array is empty
.push(2u16)
.extend([1u8, 1])
.push(5u16)
.extend([7u8, 7, 7, 7, 7])
.push(0xdeadbeef_u32);
let table = VarLenHaver::read(builder.data().into()).unwrap();
let kids = table
.var_len()
.iter()
.map(|x| x.unwrap())
.collect::<Vec<_>>();
assert!(kids[0].bytes.is_empty());
assert_eq!(kids[1].bytes, &[1, 1]);
assert_eq!(kids[2].bytes, &[7, 7, 7, 7, 7]);
assert_eq!(table.other_field(), 0xdeadbeef)
}
#[test]
#[cfg(feature = "experimental_traverse")]
fn array_offsets_traverse() {
let mut builder = BeBuffer::new()
.push(MajorMinor::VERSION_1_1)
.push(22_u16) // offset to [0xf00, 0xba4]
.push(0u16) // nullable
.push(2u16) // array len
.push(26u16) // offset to [69, 70]
.push(30u16) // record_array_offset
.push(0u16) // versioned_nullable_record_array_offset
.push(42u16) // versioned nonnullable offset
.push(0u32); // versioned nullable offset
//
let data_start = builder.len();
assert_eq!(data_start, 22);
builder = builder
.extend([0xf00u16, 0xba4])
.extend([69u16, 70])
.push(3u16) // shmecord[0]
.push(9u32)
.push(5u16) // shmecord[1]
.push(0xdead_beefu32)
.extend([0xb01du16, 0xface]); // versioned nonnullable offset;
let table = KindsOfOffsets::read(builder.data().into()).unwrap();
// traversal should not crash
let _ = format!("{table:?}");
assert_eq!(
table.versioned_nonnullable().unwrap().unwrap().value(),
0xb01d
);
}
}
pub mod flags {
include!("../generated/generated_test_flags.rs");
#[test]
fn basics() {
let all = ValueFormat::all();
let none = ValueFormat::empty();
assert!(all.contains(ValueFormat::X_PLACEMENT));
assert!(all.contains(ValueFormat::Y_PLACEMENT));
assert!(!none.contains(ValueFormat::X_PLACEMENT));
assert!(!none.contains(ValueFormat::Y_PLACEMENT));
assert_eq!(none, ValueFormat::default());
}
#[test]
fn formatting() {
let all = ValueFormat::all();
assert_eq!(format!("{all:?}"), "X_PLACEMENT | Y_PLACEMENT");
let none = ValueFormat::empty();
assert_eq!(format!("{none:?}"), "(empty)");
let xplace = ValueFormat::X_PLACEMENT;
assert_eq!(format!("{xplace:?}"), "X_PLACEMENT");
}
// not exactly a test, but this will fail to compile if these are missing
#[test]
fn impl_traits() {
fn impl_check<T: Copy + std::hash::Hash + Eq + Ord>() {}
impl_check::<ValueFormat>();
}
}
pub mod enums {
include!("../generated/generated_test_enum.rs");
}
pub mod count_all {
use crate::FontData;
include!("../generated/generated_test_count_all.rs");
/// Test for count(..) with element sizes > 1
#[test]
fn element_size_greater_than_one_with_padding() {
// Size of 13 ensures we have an extra padding byte
let bytes = [0u8; 13];
// Generated table has a 2 byte field above the array
let remainder_len = bytes.len() - 2;
let data = FontData::new(&bytes);
// Trailing array with 16-bit elements
assert!(remainder_len % 2 != 0);
let count16 = CountAll16::read(data).unwrap();
assert_eq!(count16.remainder().len(), remainder_len / 2);
// Trailing array with 32-bit elements
assert!(remainder_len % 4 != 0);
let count32 = CountAll32::read(data).unwrap();
assert_eq!(count32.remainder().len(), remainder_len / 4);
}
}
pub mod conditions {
#[cfg(test)]
use font_test_data::bebuffer::BeBuffer;
use font_types::MajorMinor;
include!("../generated/generated_test_conditions.rs");
#[test]
fn majorminor_1() {
let bytes = BeBuffer::new().push(MajorMinor::VERSION_1_0).push(0u16);
let table = MajorMinorVersion::read(bytes.data().into()).unwrap();
assert_eq!(table.always_present(), 0);
}
#[test]
fn majorminor_1_1() {
let bytes = BeBuffer::new().push(MajorMinor::VERSION_1_1).push(0u16);
// shouldn't parse, we're missing a field
assert!(MajorMinorVersion::read(bytes.data().into()).is_err());
let bytes = BeBuffer::new()
.push(MajorMinor::VERSION_1_1)
.push(0u16)
.push(1u16);
let table = MajorMinorVersion::read(bytes.data().into()).unwrap();
assert_eq!(table.if_11(), Some(1));
}
#[test]
fn major_minor_2() {
let bytes = BeBuffer::new().push(MajorMinor::VERSION_2_0).push(0u16);
// shouldn't parse, we're missing a field
assert!(MajorMinorVersion::read(bytes.data().into()).is_err());
let bytes = BeBuffer::new()
.push(MajorMinor::VERSION_2_0)
.push(0u16)
.push(2u32);
let table = MajorMinorVersion::read(bytes.data().into()).unwrap();
assert_eq!(table.if_11(), None);
assert_eq!(table.if_20(), Some(2));
}
#[cfg(test)]
fn make_flag_data(flags: GotFlags) -> BeBuffer {
let mut buf = BeBuffer::new().push(42u16).push(flags);
if flags.contains(GotFlags::FOO) {
buf = buf.push(0xf00_u16);
}
if flags.contains(GotFlags::BAR) {
buf = buf.push(0xba4_u16);
}
if flags.contains(GotFlags::FOO) || flags.contains(GotFlags::BAZ) {
buf = buf.push(0xba2_u16);
}
buf
}
#[test]
fn flags_none() {
let data = make_flag_data(GotFlags::empty());
let table = FlagDay::read(data.data().into()).unwrap();
assert!(table.foo().is_none());
assert!(table.bar().is_none());
}
#[test]
fn flags_foo() {
let data = make_flag_data(GotFlags::FOO);
let table = FlagDay::read(data.data().into()).unwrap();
assert_eq!(table.foo(), Some(0xf00));
assert!(table.bar().is_none());
assert_eq!(table.baz(), Some(0xba2));
}
#[test]
fn flags_bar() {
let data = make_flag_data(GotFlags::BAR);
let table = FlagDay::read(data.data().into()).unwrap();
assert!(table.foo().is_none());
assert_eq!(table.bar(), Some(0xba4));
assert!(table.baz().is_none());
}
#[test]
fn flags_baz() {
let data = make_flag_data(GotFlags::BAZ);
let table = FlagDay::read(data.data().into()).unwrap();
assert!(table.foo().is_none());
assert!(table.bar().is_none());
assert_eq!(table.baz(), Some(0xba2));
}
#[test]
fn flags_foobar() {
let data = make_flag_data(GotFlags::BAR | GotFlags::FOO);
let table = FlagDay::read(data.data().into()).unwrap();
assert_eq!(table.foo(), Some(0xf00));
assert_eq!(table.bar(), Some(0xba4));
assert_eq!(table.baz(), Some(0xba2));
}
#[test]
fn fields_after_conditions_all_none() {
let data = BeBuffer::new().push(GotFlags::empty()).extend([1u16, 2, 3]);
let table = FieldsAfterConditionals::read(data.data().into()).unwrap();
assert_eq!(table.always_here(), 1);
assert_eq!(table.also_always_here(), 2);
assert_eq!(table.and_me_too(), 3);
}
#[test]
#[should_panic(expected = "OutOfBounds")]
fn fields_after_conditions_wrong_len() {
let data = BeBuffer::new().push(GotFlags::FOO).extend([1u16, 2, 3]);
let _table = FieldsAfterConditionals::read(data.data().into()).unwrap();
}
#[test]
fn fields_after_conditionals_one_present() {
let data = BeBuffer::new()
.push(GotFlags::BAR)
.extend([1u16, 0xba4, 2, 3]);
let table = FieldsAfterConditionals::read(data.data().into()).unwrap();
assert_eq!(table.always_here(), 1);
assert_eq!(table.bar(), Some(0xba4));
assert_eq!(table.also_always_here(), 2);
assert!(table.foo().is_none() && table.baz().is_none());
assert_eq!(table.and_me_too(), 3);
}
#[test]
fn fields_after_conditions_all_present() {
let data = BeBuffer::new()
.push(GotFlags::FOO | GotFlags::BAR | GotFlags::BAZ)
.extend([0xf00u16, 1, 0xba4, 0xba2, 2, 3]);
let table = FieldsAfterConditionals::read(data.data().into()).unwrap();
assert_eq!(table.foo(), Some(0xf00));
assert_eq!(table.always_here(), 1);
assert_eq!(table.bar(), Some(0xba4));
assert_eq!(table.baz(), Some(0xba2));
assert_eq!(table.also_always_here(), 2);
assert_eq!(table.and_me_too(), 3);
}
}

7
vendor/read-fonts/src/collections.rs vendored Normal file
View File

@@ -0,0 +1,7 @@
//! Data structures useful for font work.
pub mod int_set;
pub use int_set::IntSet;
mod range_set;
pub use range_set::RangeSet;

View File

@@ -0,0 +1,831 @@
//! Stores a page of bits, used inside of bitset's.
use std::{hash::Hash, ops::RangeInclusive};
// the integer type underlying our bit set
type Element = u64;
// the number of elements in a page
const PAGE_SIZE: u32 = 8;
// the length of an element in bytes
const ELEM_SIZE: u32 = std::mem::size_of::<Element>() as u32;
// the length of an element in bits
const ELEM_BITS: u32 = ELEM_SIZE * 8;
// mask out bits of a value not used to index into an element
const ELEM_MASK: u32 = ELEM_BITS - 1;
// the number of bits in a page
pub(crate) const PAGE_BITS: u32 = ELEM_BITS * PAGE_SIZE;
// mask out the bits of a value not used to index into a page
const PAGE_MASK: u32 = PAGE_BITS - 1;
/// A fixed size (512 bits wide) page of bits that records integer set membership from `[0, 511]`.
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub(crate) struct BitPage {
storage: [Element; PAGE_SIZE as usize],
length: u32,
}
impl BitPage {
/// Create a new page with no bits set.
pub(crate) fn new_zeroes() -> Self {
Self {
storage: [0; PAGE_SIZE as usize],
length: 0,
}
}
pub(crate) fn recompute_length(&mut self) {
self.length = self.storage.iter().copied().map(u64::count_ones).sum();
}
/// Returns the number of members in this page.
pub(crate) fn len(&self) -> u32 {
self.length
}
/// Returns true if this page has no members.
pub(crate) fn is_empty(&self) -> bool {
self.len() == 0
}
// TODO(garretrieger): iterator that starts after some value (similar to next in hb).
// TODO(garretrieger): reverse iterator.
/// Iterator over the members of this page.
pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = u32> + '_ {
self.storage
.iter()
.enumerate()
.filter(|(_, elem)| **elem != 0)
.flat_map(|(i, elem)| {
let base = i as u32 * ELEM_BITS;
Iter::new(*elem).map(move |idx| base + idx)
})
}
/// Iterator over the members of this page starting from value.
///
/// So value is included in the iterator if it's in the page.
pub(crate) fn iter_from(&self, value: u32) -> impl DoubleEndedIterator<Item = u32> + '_ {
let start_index = Self::element_index(value);
self.storage[start_index..]
.iter()
.enumerate()
.filter(|(_, elem)| **elem != 0)
.flat_map(move |(i, elem)| {
let i = i + start_index;
let base = i as u32 * ELEM_BITS;
let it = if start_index == i {
let index_in_elem = value & ELEM_MASK;
Iter::from(*elem, index_in_elem)
} else {
Iter::new(*elem)
};
it.map(move |idx| base + idx)
})
}
/// Iterator over the ranges in this page.
pub(crate) fn iter_ranges(&self) -> RangeIter<'_> {
RangeIter {
page: self,
next_value_to_check: 0,
}
}
/// Marks `(val % page width)` a member of this set and returns `true` if it is newly added.
pub(crate) fn insert(&mut self, val: u32) -> bool {
let el_mut = self.element_mut(val);
let mask = elem_index_bit_mask(val);
let is_new = (*el_mut & mask) == 0;
*el_mut |= mask;
self.length += is_new as u32;
is_new
}
/// Marks all values `[first, last]` as members of this set.
pub(crate) fn insert_range(&mut self, first: u32, last: u32) {
let first = first & PAGE_MASK;
let last = last & PAGE_MASK;
let first_elem_idx = first / ELEM_BITS;
let last_elem_idx = last / ELEM_BITS;
for elem_idx in first_elem_idx..=last_elem_idx {
let elem_start = first.max(elem_idx * ELEM_BITS) & ELEM_MASK;
let elem_last = last.min(((elem_idx + 1) * ELEM_BITS) - 1) & ELEM_MASK;
let end_shift = ELEM_BITS - elem_last - 1;
let mask = u64::MAX << (elem_start + end_shift);
let mask = mask >> end_shift;
self.storage[elem_idx as usize] |= mask;
}
self.recompute_length();
}
/// Marks all values `[first, last]` as not members of this set.
pub(crate) fn remove_range(&mut self, first: u32, last: u32) {
let first = first & PAGE_MASK;
let last = last & PAGE_MASK;
let first_elem_idx = first / ELEM_BITS;
let last_elem_idx = last / ELEM_BITS;
for elem_idx in first_elem_idx..=last_elem_idx {
let elem_start = first.max(elem_idx * ELEM_BITS) & ELEM_MASK;
let elem_last = last.min(((elem_idx + 1) * ELEM_BITS) - 1) & ELEM_MASK;
let end_shift = ELEM_BITS - elem_last - 1;
let mask = u64::MAX << (elem_start + end_shift);
let mask = !(mask >> end_shift);
self.storage[elem_idx as usize] &= mask;
}
self.recompute_length();
}
pub(crate) fn clear(&mut self) {
for elem in self.storage.iter_mut() {
*elem = 0;
}
self.length = 0;
}
/// Removes `(val % page width)` from this set.
pub(crate) fn remove(&mut self, val: u32) -> bool {
let ret = self.contains(val);
*self.element_mut(val) &= !elem_index_bit_mask(val);
self.length -= ret as u32;
ret
}
/// Return true if `(val % page width)` is a member of this set.
pub(crate) fn contains(&self, val: u32) -> bool {
(*self.element(val) & elem_index_bit_mask(val)) != 0
}
pub(crate) fn union(a: &BitPage, b: &BitPage) -> BitPage {
a.process(b, |a, b| a | b)
}
pub(crate) fn intersect(a: &BitPage, b: &BitPage) -> BitPage {
a.process(b, |a, b| a & b)
}
pub(crate) fn subtract(a: &BitPage, b: &BitPage) -> BitPage {
a.process(b, |a, b| a & !b)
}
fn process<Op>(&self, other: &BitPage, op: Op) -> BitPage
where
Op: Fn(Element, Element) -> Element,
{
let mut out = BitPage::new_zeroes();
for i in 0usize..(PAGE_SIZE as usize) {
out.storage[i] = op(self.storage[i], other.storage[i]);
}
out.recompute_length();
out
}
fn element(&self, value: u32) -> &Element {
&self.storage[Self::element_index(value)]
}
fn element_mut(&mut self, value: u32) -> &mut Element {
&mut self.storage[Self::element_index(value)]
}
const fn element_index(value: u32) -> usize {
(value as usize & PAGE_MASK as usize) / (ELEM_BITS as usize)
}
}
/// returns the bit to set in an element for this value
const fn elem_index_bit_mask(value: u32) -> Element {
1 << (value & ELEM_MASK)
}
struct Iter {
val: Element,
forward_index: i32,
backward_index: i32,
}
impl Iter {
fn new(elem: Element) -> Iter {
Iter {
val: elem,
forward_index: 0,
backward_index: ELEM_BITS as i32 - 1,
}
}
/// Construct an iterator that starts at `index`
///
/// Specifically if `index` bit is set it will be returned on the first call to `next()`.
fn from(elem: Element, index: u32) -> Iter {
Iter {
val: elem,
forward_index: index as i32, // index is at most 63
backward_index: ELEM_BITS as i32 - 1,
}
}
}
impl Iterator for Iter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.forward_index > self.backward_index {
return None;
}
let mask = (1u64 << self.forward_index) - 1;
let masked = self.val & !mask;
let next_index = masked.trailing_zeros() as i32;
if next_index > self.backward_index {
return None;
}
self.forward_index = next_index + 1;
Some(next_index as u32)
}
}
impl DoubleEndedIterator for Iter {
fn next_back(&mut self) -> Option<Self::Item> {
if self.backward_index < self.forward_index {
return None;
}
let mask = 1u64
.checked_shl(self.backward_index as u32 + 1)
.map(|v| v - 1)
.unwrap_or(Element::MAX);
let masked = self.val & mask;
let next_index = (ELEM_BITS as i32) - (masked.leading_zeros() as i32) - 1;
if next_index < self.forward_index {
return None;
}
self.backward_index = next_index - 1;
Some(next_index as u32)
}
}
pub(crate) struct RangeIter<'a> {
page: &'a BitPage,
next_value_to_check: u32,
}
impl RangeIter<'_> {
fn next_range_in_element(&self) -> Option<RangeInclusive<u32>> {
if self.next_value_to_check >= PAGE_BITS {
return None;
}
let element = self.page.element(self.next_value_to_check);
let element_bit = (self.next_value_to_check & ELEM_MASK) as u64;
let major = self.next_value_to_check & !ELEM_MASK;
let mask = !((1 << element_bit) - 1);
let range_start = (element & mask).trailing_zeros();
if range_start == ELEM_BITS {
// There's no remaining values in this element.
return None;
}
let mask = (1 << range_start) - 1;
let range_end = (element | mask).trailing_ones() - 1;
Some((major + range_start)..=(major + range_end))
}
}
impl Iterator for RangeIter<'_> {
type Item = RangeInclusive<u32>;
fn next(&mut self) -> Option<Self::Item> {
let mut current_range = self.next_range_in_element();
loop {
let element_end = (self.next_value_to_check & !ELEM_MASK) + ELEM_BITS - 1;
let Some(range) = current_range.clone() else {
// No more ranges in the current element, move to the next one.
self.next_value_to_check = element_end + 1;
if self.next_value_to_check < PAGE_BITS {
current_range = self.next_range_in_element();
continue;
} else {
return None;
}
};
self.next_value_to_check = range.end() + 1;
if *range.end() == element_end {
let continuation = self.next_range_in_element();
if let Some(continuation) = continuation {
if *continuation.start() == element_end + 1 {
current_range = Some(*range.start()..=*continuation.end());
continue;
}
}
}
break;
}
current_range
}
}
impl Default for BitPage {
fn default() -> Self {
Self::new_zeroes()
}
}
impl std::fmt::Debug for BitPage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let values: Vec<_> = self.iter().collect();
std::fmt::Debug::fmt(&values, f)
}
}
impl Hash for BitPage {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.storage.hash(state);
}
}
impl std::cmp::PartialEq for BitPage {
fn eq(&self, other: &Self) -> bool {
self.storage == other.storage
}
}
impl std::cmp::Eq for BitPage {}
#[cfg(test)]
mod test {
use std::collections::HashSet;
use super::*;
impl BitPage {
/// Create a new page with all bits set.
fn new_ones() -> Self {
Self {
storage: [Element::MAX; PAGE_SIZE as usize],
length: PAGE_SIZE * ELEM_BITS,
}
}
}
impl FromIterator<u32> for BitPage {
fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self {
let mut out = BitPage::new_zeroes();
for v in iter {
out.insert(v);
}
out
}
}
#[test]
fn test_iter_bit_indices() {
let items: Vec<_> = Iter::new(0).collect();
assert_eq!(items.len(), 0);
let items: Vec<_> = Iter::new(1).collect();
assert_eq!(items, vec![0]);
let items: Vec<_> = Iter::new(0b1100).collect();
assert_eq!(items, vec![2, 3]);
let items: Vec<_> = Iter::new(1 << 63).collect();
assert_eq!(items, vec![63]);
let items: Vec<_> = Iter::new((1 << 47) | (1 << 63)).collect();
assert_eq!(items, vec![47, 63]);
assert_eq!(Iter::new(Element::MAX).max(), Some(ELEM_BITS - 1));
assert_eq!(Iter::new(Element::MAX).min(), Some(0));
}
#[test]
fn test_iter_bit_indices_backwards() {
let mut it = Iter::new(0);
assert_eq!(None, it.next());
assert_eq!(None, it.next_back());
let mut it = Iter::new((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6));
assert_eq!(Some(1), it.next());
assert_eq!(Some(6), it.next_back());
assert_eq!(Some(5), it.next_back());
assert_eq!(Some(2), it.next());
assert_eq!(Some(3), it.next());
assert_eq!(Some(4), it.next());
assert_eq!(None, it.next());
assert_eq!(None, it.next_back());
let mut it = Iter::new(1);
assert_eq!(Some(0), it.next_back());
assert_eq!(None, it.next_back());
let mut it = Iter::new(1 << 63);
assert_eq!(Some(63), it.next_back());
assert_eq!(None, it.next_back());
let mut it = Iter::new((1 << 63) | (1 << 62));
assert_eq!(Some(63), it.next_back());
assert_eq!(Some(62), it.next_back());
assert_eq!(None, it.next_back());
let mut it = Iter::new((1 << 63) | (1 << 32));
assert_eq!(Some(63), it.next_back());
assert_eq!(Some(32), it.next_back());
assert_eq!(None, it.next_back());
}
#[test]
fn page_init() {
let page = BitPage::new_zeroes();
assert_eq!(page.len(), 0);
assert!(page.is_empty());
}
#[test]
fn page_init_ones() {
let page = BitPage::new_ones();
assert_eq!(page.len(), 512);
assert!(!page.is_empty());
}
#[test]
fn page_contains_empty() {
let page = BitPage::new_zeroes();
assert!(!page.contains(0));
assert!(!page.contains(1));
assert!(!page.contains(75475));
}
#[test]
fn page_contains_all() {
let page = BitPage::new_ones();
assert!(page.contains(0));
assert!(page.contains(1));
assert!(page.contains(75475));
}
#[test]
fn page_insert() {
for val in 0..=1025 {
let mut page = BitPage::new_zeroes();
assert!(!page.contains(val), "unexpected {val} (1)");
page.insert(val);
assert!(page.contains(val), "missing {val}");
assert!(!page.contains(val.wrapping_sub(1)), "unexpected {val} (2)");
}
}
#[test]
fn page_insert_range() {
fn page_for_range(first: u32, last: u32) -> BitPage {
let mut page = BitPage::new_zeroes();
for i in first..=last {
page.insert(i);
}
page
}
for range in [
(0, 0),
(0, 1),
(1, 15),
(5, 63),
(64, 67),
(69, 72),
(69, 127),
(32, 345),
(512 + 32, 512 + 345),
(0, 511),
] {
let mut page = BitPage::new_zeroes();
page.insert_range(range.0, range.1);
assert_eq!(page, page_for_range(range.0, range.1), "{range:?}");
}
}
#[test]
fn page_insert_return() {
let mut page = BitPage::new_zeroes();
assert!(page.insert(123));
assert!(!page.insert(123));
}
#[test]
fn page_remove() {
for val in 0..=1025 {
let mut page = BitPage::new_ones();
assert!(page.contains(val), "missing {val} (1)");
assert!(page.remove(val));
assert!(!page.remove(val));
assert!(!page.contains(val), "unexpected {val}");
assert!(page.contains(val.wrapping_sub(1)), "missing {val} (2)");
}
}
#[test]
fn page_remove_range() {
fn page_for_range(first: u32, last: u32) -> BitPage {
let mut page = BitPage::new_ones();
for i in first..=last {
page.remove(i);
}
page
}
for exclude_range in [
(0, 0),
(0, 1),
(1, 15),
(5, 63),
(64, 67),
(69, 72),
(69, 127),
(32, 345),
(0, 511),
(512 + 32, 512 + 345),
] {
let mut page = BitPage::new_ones();
page.remove_range(exclude_range.0, exclude_range.1);
assert_eq!(
page,
page_for_range(exclude_range.0, exclude_range.1),
"{exclude_range:?}"
);
}
}
#[test]
fn clear() {
let mut zeroes = BitPage::new_zeroes();
let mut ones = BitPage::new_ones();
zeroes.clear();
assert_eq!(zeroes.len(), 0);
assert_eq!(zeroes.iter().next(), None);
zeroes.insert_range(10, 300);
zeroes.clear();
assert_eq!(zeroes.len(), 0);
assert_eq!(zeroes.iter().next(), None);
ones.clear();
assert_eq!(ones.len(), 0);
assert_eq!(ones.iter().next(), None);
}
#[test]
fn remove_to_empty_page() {
let mut page = BitPage::new_zeroes();
page.insert(13);
assert!(!page.is_empty());
page.remove(13);
assert!(page.is_empty());
}
#[test]
fn page_iter() {
let mut page = BitPage::new_zeroes();
page.insert(0);
page.insert(12);
page.insert(13);
page.insert(63);
page.insert(64);
page.insert(511);
page.insert(23);
page.insert(400);
page.insert(78);
let items: Vec<_> = page.iter().collect();
assert_eq!(items, vec![0, 12, 13, 23, 63, 64, 78, 400, 511,])
}
#[test]
fn page_iter_overflow() {
let mut page = BitPage::new_zeroes();
page.insert(0);
let mut it = page.iter();
assert_eq!(Some(0), it.next_back());
assert_eq!(None, it.next());
}
#[test]
fn page_iter_from() {
let mut page = BitPage::new_zeroes();
let items: Vec<_> = page.iter_from(0).collect();
assert!(items.is_empty());
let items: Vec<_> = page.iter_from(256).collect();
assert!(items.is_empty());
page.insert(1);
page.insert(12);
page.insert(13);
page.insert(63);
page.insert(64);
page.insert(511);
page.insert(23);
page.insert(400);
page.insert(78);
let items: Vec<_> = page.iter_from(0).collect();
assert_eq!(items, vec![1, 12, 13, 23, 63, 64, 78, 400, 511,]);
page.insert(0);
let items: Vec<_> = page.iter_from(0).collect();
assert_eq!(items, vec![0, 1, 12, 13, 23, 63, 64, 78, 400, 511,]);
let items: Vec<_> = page.iter_from(1).collect();
assert_eq!(items, vec![1, 12, 13, 23, 63, 64, 78, 400, 511,]);
let items: Vec<_> = page.iter_from(2).collect();
assert_eq!(items, vec![12, 13, 23, 63, 64, 78, 400, 511,]);
let items: Vec<_> = page.iter_from(63).collect();
assert_eq!(items, vec![63, 64, 78, 400, 511,]);
let items: Vec<_> = page.iter_from(256).collect();
assert_eq!(items, vec![400, 511]);
let items: Vec<_> = page.iter_from(511).collect();
assert_eq!(items, vec![511]);
let items: Vec<_> = page.iter_from(512).collect(); // page has 511 values, so 512 wraps around and acts like '0'
assert_eq!(items, vec![0, 1, 12, 13, 23, 63, 64, 78, 400, 511,]);
let items: Vec<_> = page.iter_from(515).collect(); // page has 511 values, so 515 wraps around and acts like '3'
assert_eq!(items, vec![12, 13, 23, 63, 64, 78, 400, 511,]);
let items: Vec<_> = page.iter_from(390).collect();
assert_eq!(items, vec![400, 511]);
let items: Vec<_> = page.iter_from(400).collect();
assert_eq!(items, vec![400, 511]);
let items: Vec<_> = page.iter_from(401).collect();
assert_eq!(items, vec![511]);
}
#[test]
fn page_iter_after_rev() {
let mut page = BitPage::new_zeroes();
let items: Vec<_> = page.iter_from(0).collect();
assert!(items.is_empty());
let items: Vec<_> = page.iter_from(256).collect();
assert!(items.is_empty());
page.insert(1);
page.insert(12);
page.insert(13);
page.insert(63);
page.insert(64);
page.insert(511);
page.insert(23);
page.insert(400);
page.insert(78);
let items: Vec<_> = page.iter_from(0).rev().collect();
assert_eq!(items, vec![511, 400, 78, 64, 63, 23, 13, 12, 1]);
page.insert(0);
let items: Vec<_> = page.iter_from(0).rev().collect();
assert_eq!(items, vec![511, 400, 78, 64, 63, 23, 13, 12, 1, 0]);
let items: Vec<_> = page.iter_from(1).rev().collect();
assert_eq!(items, vec![511, 400, 78, 64, 63, 23, 13, 12, 1]);
let items: Vec<_> = page.iter_from(63).rev().collect();
assert_eq!(items, vec![511, 400, 78, 64, 63]);
let items: Vec<_> = page.iter_from(256).rev().collect();
assert_eq!(items, vec![511, 400]);
let items: Vec<_> = page.iter_from(512).rev().collect();
assert_eq!(items, vec![511, 400, 78, 64, 63, 23, 13, 12, 1, 0]);
let items: Vec<_> = page.iter_from(390).rev().collect();
assert_eq!(items, vec![511, 400]);
let items: Vec<_> = page.iter_from(400).rev().collect();
assert_eq!(items, vec![511, 400]);
let items: Vec<_> = page.iter_from(401).rev().collect();
assert_eq!(items, vec![511]);
}
fn check_iter_ranges(ranges: Vec<RangeInclusive<u32>>) {
let mut page = BitPage::new_zeroes();
for range in ranges.iter() {
page.insert_range(*range.start(), *range.end());
}
let items: Vec<_> = page.iter_ranges().collect();
assert_eq!(items, ranges);
}
#[test]
fn iter_ranges() {
// basic
check_iter_ranges(vec![]);
check_iter_ranges(vec![0..=5]);
check_iter_ranges(vec![0..=0, 5..=5, 10..=10]);
check_iter_ranges(vec![0..=5, 12..=31]);
check_iter_ranges(vec![12..=31]);
check_iter_ranges(vec![71..=84]);
check_iter_ranges(vec![273..=284]);
check_iter_ranges(vec![0..=511]);
// end of boundary
check_iter_ranges(vec![511..=511]);
check_iter_ranges(vec![500..=511]);
check_iter_ranges(vec![400..=511]);
check_iter_ranges(vec![0..=511]);
// continuation ranges
check_iter_ranges(vec![64..=127]);
check_iter_ranges(vec![64..=127, 129..=135]);
check_iter_ranges(vec![64..=135]);
check_iter_ranges(vec![71..=135]);
check_iter_ranges(vec![71..=435]);
}
#[test]
fn union() {
let a = BitPage::new_zeroes();
let b = BitPage::from_iter([32, 400]);
let c = BitPage::from_iter([32, 200]);
let d = BitPage::from_iter([32, 200, 400]);
assert_eq!(BitPage::union(&a, &b), b);
assert_eq!(BitPage::union(&b, &a), b);
assert_eq!(BitPage::union(&b, &c), d);
assert_eq!(BitPage::union(&c, &b), d);
}
#[test]
fn intersect() {
let a = BitPage::new_zeroes();
let b = BitPage::from_iter([32, 400]);
let c = BitPage::from_iter([32, 200]);
let d = BitPage::from_iter([32]);
assert_eq!(BitPage::intersect(&a, &b), a);
assert_eq!(BitPage::intersect(&b, &a), a);
assert_eq!(BitPage::intersect(&b, &c), d);
assert_eq!(BitPage::intersect(&c, &b), d);
}
#[test]
fn subtract() {
let a = BitPage::new_zeroes();
let b = BitPage::from_iter([32, 400]);
let c = BitPage::from_iter([32, 200]);
let d = BitPage::from_iter([400]);
let e = BitPage::from_iter([200]);
assert_eq!(BitPage::subtract(&a, &b), a);
assert_eq!(BitPage::subtract(&b, &a), b);
assert_eq!(BitPage::subtract(&b, &c), d);
assert_eq!(BitPage::subtract(&c, &b), e);
}
#[test]
#[allow(clippy::mutable_key_type)]
fn hash_and_eq() {
let mut page1 = BitPage::new_zeroes();
let mut page2 = BitPage::new_zeroes();
let mut page3 = BitPage::new_zeroes();
page1.insert(12);
page1.insert(300);
page2.insert(300);
page2.insert(12);
page2.len();
page3.insert(300);
page3.insert(12);
page3.insert(23);
assert_eq!(page1, page2);
assert_ne!(page1, page3);
assert_ne!(page2, page3);
let set = HashSet::from([page1]);
assert!(set.contains(&page2));
assert!(!set.contains(&page3));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,290 @@
//! Reads individual bits from a array of bytes.
use super::sparse_bit_set::BranchFactor;
pub(crate) struct InputBitStream<'a, const BF: u8> {
data: &'a [u8],
byte_index: usize,
sub_index: u32,
}
impl<const BF: u8> Iterator for InputBitStream<'_, BF> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
match BF {
2 | 4 => {
let mask = (1 << BF) - 1;
let byte = self.data.get(self.byte_index)?;
let val = (*byte as u32 & (mask << self.sub_index)) >> self.sub_index;
self.sub_index = (self.sub_index + BF as u32) % 8;
if self.sub_index == 0 {
self.byte_index += 1;
}
Some(val)
}
8 => {
let r = self.data.get(self.byte_index).map(|v| *v as u32)?;
self.byte_index += 1;
Some(r)
}
32 => {
let b1 = self.data.get(self.byte_index).map(|v| *v as u32)?;
let b2 = self.data.get(self.byte_index + 1).map(|v| *v as u32)?;
let b3 = self.data.get(self.byte_index + 2).map(|v| *v as u32)?;
let b4 = self.data.get(self.byte_index + 3).map(|v| *v as u32)?;
self.byte_index += 4;
Some(b1 | (b2 << 8) | (b3 << 16) | (b4 << 24))
}
_ => panic!("Unsupported branch factor."),
}
}
}
impl<'a, const BF: u8> InputBitStream<'a, BF> {
/// Decodes and returns the branch factor and height encoded in the header byte.
///
/// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
/// Returns None if the stream does not have enough remaining bits.
#[allow(clippy::unusual_byte_groupings)] // Used to separate bit values into units used in the set encoding.
pub(crate) fn decode_header(data: &'a [u8]) -> Option<(BranchFactor, u8)> {
let first_byte = data.first()?;
let bf_bits = 0b0_00000_11 & first_byte;
let depth_bits = (0b0_11111_00 & first_byte) >> 2;
let branch_factor = match bf_bits {
0b00 => BranchFactor::Two,
0b01 => BranchFactor::Four,
0b10 => BranchFactor::Eight,
0b11 => BranchFactor::ThirtyTwo,
_ => panic!("Invalid branch factor encoding."),
};
Some((branch_factor, depth_bits))
}
pub(crate) fn from(data: &'a [u8]) -> Self {
Self {
data,
byte_index: 1,
sub_index: 0,
}
}
/// Skips the given number of nodes, returns true if this did not overrun the data buffer.
pub(crate) fn skip_nodes(&mut self, n: u32) -> bool {
match BF {
2 | 4 => {
let bit_index = self.sub_index + n * (BF as u32);
self.byte_index += (bit_index / 8) as usize;
self.sub_index = bit_index % 8;
}
8 => {
self.byte_index += n as usize;
}
32 => {
self.byte_index += 4 * (n as usize);
}
_ => panic!("Unsupported branch factor."),
};
self.bytes_consumed() <= self.data.len()
}
/// Returns the number of bytes consumed so far (including partially consumed).
pub(crate) fn bytes_consumed(&self) -> usize {
self.byte_index + if self.sub_index > 0 { 1 } else { 0 }
}
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod test {
use super::*;
#[test]
fn read_header() {
assert_eq!(
Some((BranchFactor::Two, 25u8)),
InputBitStream::<2>::decode_header(&[0b1_11001_00u8])
);
assert_eq!(
Some((BranchFactor::Four, 0u8)),
InputBitStream::<2>::decode_header(&[0b1_00000_01u8])
);
assert_eq!(
Some((BranchFactor::Eight, 31u8)),
InputBitStream::<2>::decode_header(&[0b1_11111_10u8])
);
assert_eq!(
Some((BranchFactor::ThirtyTwo, 9u8)),
InputBitStream::<2>::decode_header(&[0b1_01001_11u8])
);
}
#[test]
fn read_2() {
let mut stream = InputBitStream::<2>::from(&[0b00000000, 0b11_10_01_00, 0b00_01_10_11]);
assert_eq!(stream.bytes_consumed(), 1); // Initially one byte consumed for the header.
assert_eq!(stream.next(), Some(0b00));
assert_eq!(stream.bytes_consumed(), 2);
assert_eq!(stream.next(), Some(0b01));
assert_eq!(stream.next(), Some(0b10));
assert_eq!(stream.next(), Some(0b11));
assert_eq!(stream.bytes_consumed(), 2);
assert_eq!(stream.next(), Some(0b11));
assert_eq!(stream.bytes_consumed(), 3);
assert_eq!(stream.next(), Some(0b10));
assert_eq!(stream.next(), Some(0b01));
assert_eq!(stream.next(), Some(0b00));
assert_eq!(stream.bytes_consumed(), 3);
assert_eq!(stream.next(), None);
let mut stream = InputBitStream::<2>::from(&[]);
assert_eq!(stream.next(), None);
}
#[test]
fn skip_2() {
let mut stream = InputBitStream::<2>::from(&[0b00000000, 0b11_10_01_00, 0b00_01_10_11]);
assert_eq!(stream.bytes_consumed(), 1); // Initially one byte consumed for the header.
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 2);
assert!(stream.skip_nodes(2));
assert_eq!(stream.bytes_consumed(), 2);
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 2);
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 3);
assert!(stream.skip_nodes(3));
assert_eq!(stream.bytes_consumed(), 3);
assert!(!stream.skip_nodes(1));
}
#[test]
fn skip_2_unaligned() {
let mut stream = InputBitStream::<2>::from(&[0b00000000, 0b11_10_01_00, 0b00_01_10_11]);
assert_eq!(stream.bytes_consumed(), 1); // Initially one byte consumed for the header.
assert!(stream.skip_nodes(3));
assert_eq!(stream.bytes_consumed(), 2);
assert!(stream.skip_nodes(3));
assert_eq!(stream.bytes_consumed(), 3);
assert!(!stream.skip_nodes(3));
}
#[test]
fn read_4() {
let mut stream = InputBitStream::<4>::from(&[0b00000000, 0b1110_0100, 0b0001_1011]);
assert_eq!(stream.bytes_consumed(), 1);
assert_eq!(stream.next(), Some(0b0100));
assert_eq!(stream.bytes_consumed(), 2);
assert_eq!(stream.next(), Some(0b1110));
assert_eq!(stream.bytes_consumed(), 2);
assert_eq!(stream.next(), Some(0b1011));
assert_eq!(stream.bytes_consumed(), 3);
assert_eq!(stream.next(), Some(0b0001));
assert_eq!(stream.bytes_consumed(), 3);
assert_eq!(stream.next(), None);
let mut stream = InputBitStream::<4>::from(&[]);
assert_eq!(stream.next(), None);
}
#[test]
fn skip_4() {
let mut stream = InputBitStream::<4>::from(&[0b00000000, 0b1110_0100, 0b0001_1011]);
assert_eq!(stream.bytes_consumed(), 1); // Initially one byte consumed for the header.
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 2);
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 2);
assert!(stream.skip_nodes(2));
assert_eq!(stream.bytes_consumed(), 3);
assert!(!stream.skip_nodes(1));
}
#[test]
fn read_8() {
let mut stream = InputBitStream::<8>::from(&[0b00000000, 0b11100100, 0b00011011]);
assert_eq!(stream.bytes_consumed(), 1);
assert_eq!(stream.next(), Some(0b11100100));
assert_eq!(stream.bytes_consumed(), 2);
assert_eq!(stream.next(), Some(0b00011011));
assert_eq!(stream.bytes_consumed(), 3);
assert_eq!(stream.next(), None);
let mut stream = InputBitStream::<8>::from(&[]);
assert_eq!(stream.next(), None);
}
#[test]
fn skip_8() {
let mut stream = InputBitStream::<8>::from(&[0b00000000, 0b11100100, 0b00011011]);
assert_eq!(stream.bytes_consumed(), 1);
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 2);
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 3);
assert!(!stream.skip_nodes(1));
let mut stream = InputBitStream::<8>::from(&[0b00000000, 0b11100100, 0b00011011]);
assert_eq!(stream.bytes_consumed(), 1);
assert!(stream.skip_nodes(2));
assert_eq!(stream.bytes_consumed(), 3);
assert!(!stream.skip_nodes(1));
}
#[test]
fn read_32() {
let mut stream = InputBitStream::<32>::from(&[
0b00000000, 0b00000000, 0b11111111, 0b11100100, 0b00011011,
]);
assert_eq!(stream.bytes_consumed(), 1);
assert_eq!(stream.next(), Some(0b00011011_11100100_11111111_00000000));
assert_eq!(stream.bytes_consumed(), 5);
assert_eq!(stream.next(), None);
let mut stream = InputBitStream::<32>::from(&[
0b00000000, 0b00000000, 0b11111111, 0b11100100, 0b00011011, 0b00000001,
]);
assert_eq!(stream.next(), Some(0b00011011_11100100_11111111_00000000));
assert_eq!(stream.next(), None);
let mut stream = InputBitStream::<32>::from(&[]);
assert_eq!(stream.next(), None);
}
#[test]
fn skip_32() {
let mut stream = InputBitStream::<32>::from(&[
0b00000000, 0b00000000, 0b11111111, 0b11100100, 0b00011011,
]);
assert_eq!(stream.bytes_consumed(), 1);
assert!(stream.skip_nodes(1));
assert_eq!(stream.bytes_consumed(), 5);
assert!(!stream.skip_nodes(1));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,172 @@
//! Writes individual bits to a vector of bytes.
use super::sparse_bit_set::BranchFactor;
pub(crate) struct OutputBitStream {
data: Vec<u8>,
sub_index: u32,
branch_factor: BranchFactor,
}
impl OutputBitStream {
pub(crate) const MAX_HEIGHT: u8 = 31;
pub(crate) fn new(branch_factor: BranchFactor, height: u8) -> OutputBitStream {
let mut out = OutputBitStream {
data: vec![],
sub_index: 0,
branch_factor,
};
if height > Self::MAX_HEIGHT {
panic!("Height value exceeds maximum for the branch factor.");
}
out.write_header(height);
out
}
pub fn into_bytes(self) -> Vec<u8> {
self.data
}
/// Writes a single node worth of bits to the stream.
///
/// `branch_factor` controls the node size.
pub fn write_node(&mut self, bits: u32) {
for byte_index in 0..self.branch_factor.bytes_per_node() {
if self.branch_factor.nodes_per_byte() == 1 || self.sub_index == 0 {
self.data.push(0);
}
let bits = (bits >> (byte_index * 8)) & self.branch_factor.byte_mask();
let bits = (bits << (self.sub_index * self.branch_factor.value())) as u8;
*self.data.last_mut().unwrap() |= bits;
if self.branch_factor.nodes_per_byte() > 1 {
self.sub_index = (self.sub_index + 1) % self.branch_factor.nodes_per_byte();
}
}
}
/// Writes the header byte for a sparse bit set.
///
/// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
fn write_header(&mut self, height: u8) {
let byte = (height & 0b00011111) << 2;
let byte = byte | self.branch_factor.bit_id();
self.data.push(byte);
}
}
impl BranchFactor {
fn nodes_per_byte(&self) -> u32 {
match self {
BranchFactor::Two => 4,
BranchFactor::Four => 2,
BranchFactor::Eight => 1,
BranchFactor::ThirtyTwo => 1,
}
}
fn bytes_per_node(&self) -> u32 {
match self {
BranchFactor::Two => 1,
BranchFactor::Four => 1,
BranchFactor::Eight => 1,
BranchFactor::ThirtyTwo => 4,
}
}
fn bit_id(&self) -> u8 {
match self {
BranchFactor::Two => 0b00,
BranchFactor::Four => 0b01,
BranchFactor::Eight => 0b10,
BranchFactor::ThirtyTwo => 0b11,
}
}
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod test {
use super::*;
#[test]
fn init() {
let os = OutputBitStream::new(BranchFactor::Two, 13);
assert_eq!(os.into_bytes(), vec![0b0_01101_00]);
let os = OutputBitStream::new(BranchFactor::Four, 23);
assert_eq!(os.into_bytes(), vec![0b0_10111_01]);
let os = OutputBitStream::new(BranchFactor::Eight, 1);
assert_eq!(os.into_bytes(), vec![0b0_00001_10]);
let os = OutputBitStream::new(BranchFactor::ThirtyTwo, 31);
assert_eq!(os.into_bytes(), vec![0b0_11111_11]);
}
#[test]
fn bf2() {
let mut os = OutputBitStream::new(BranchFactor::Two, 13);
os.write_node(0b10);
os.write_node(0b00);
os.write_node(0b11);
os.write_node(0b01);
os.write_node(0b01);
os.write_node(0b11);
assert_eq!(
os.into_bytes(),
vec![0b0_01101_00, 0b01_11_00_10, 0b00_00_11_01,]
);
}
#[test]
fn bf4() {
let mut os = OutputBitStream::new(BranchFactor::Four, 23);
os.write_node(0b0010);
os.write_node(0b0111);
os.write_node(0b1101);
assert_eq!(
os.into_bytes(),
vec![0b0_10111_01, 0b0111_0010, 0b0000_1101,]
);
}
#[test]
fn bf8() {
let mut os = OutputBitStream::new(BranchFactor::Eight, 1);
os.write_node(0b01110010);
os.write_node(0b00001101);
assert_eq!(os.into_bytes(), vec![0b0_00001_10, 0b01110010, 0b00001101,]);
}
#[test]
fn bf32() {
let mut os = OutputBitStream::new(BranchFactor::ThirtyTwo, 31);
os.write_node(0b10000000_00000000_00001101_01110010);
assert_eq!(
os.into_bytes(),
vec![0b0_11111_11, 0b01110010, 0b00001101, 0b00000000, 0b10000000]
);
}
#[test]
fn truncating() {
let mut os = OutputBitStream::new(BranchFactor::Four, 23);
os.write_node(0b11110010);
assert_eq!(os.into_bytes(), vec![0b0_10111_01, 0b0000_0010]);
}
}

View File

@@ -0,0 +1,920 @@
//! Provides serialization of [`IntSet`]'s to a highly compact bitset format as defined in the
//! IFT specification:
//!
//! <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
use std::collections::VecDeque;
use std::error::Error;
use std::fmt;
use super::bitset::BitSetBuilder;
use super::input_bit_stream::InputBitStream;
use super::output_bit_stream::OutputBitStream;
use super::BitSet;
use super::IntSet;
#[derive(Debug, PartialEq)]
pub struct DecodingError;
impl Error for DecodingError {}
impl fmt::Display for DecodingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"The input data stream was too short to be a valid sparse bit set."
)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum BranchFactor {
Two,
Four,
Eight,
ThirtyTwo,
}
impl IntSet<u32> {
/// Populate this set with the values obtained from decoding the provided sparse bit set bytes.
///
/// Sparse bit sets are a specialized, compact encoding of bit sets defined in the IFT specification:
/// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
pub fn from_sparse_bit_set(data: &[u8]) -> Result<IntSet<u32>, DecodingError> {
Self::from_sparse_bit_set_bounded(data, 0, u32::MAX).map(|(set, _)| set)
}
/// Populate this set with the values obtained from decoding the provided sparse bit set bytes.
///
/// During decoding bias will be added to each decoded set members value. The final set will not contain
/// any values larger than max_value: any encoded values larger than max_value after the bias is applied
/// are ignored.
///
/// Sparse bit sets are a specialized, compact encoding of bit sets defined in the IFT specification:
/// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
pub fn from_sparse_bit_set_bounded(
data: &[u8],
bias: u32,
max_value: u32,
) -> Result<(IntSet<u32>, &[u8]), DecodingError> {
// This is a direct port of the decoding algorithm from:
// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
let Some((branch_factor, height)) = InputBitStream::<0>::decode_header(data) else {
return Err(DecodingError);
};
if height > branch_factor.max_height() {
// TODO(garretrieger): the spec says nothing about this depth limit, we need to update the spec
// to match.
return Err(DecodingError);
}
let result = match branch_factor {
BranchFactor::Two => {
Self::decode_sparse_bit_set_nodes::<2>(data, height, bias, max_value)
}
BranchFactor::Four => {
Self::decode_sparse_bit_set_nodes::<4>(data, height, bias, max_value)
}
BranchFactor::Eight => {
Self::decode_sparse_bit_set_nodes::<8>(data, height, bias, max_value)
}
BranchFactor::ThirtyTwo => {
Self::decode_sparse_bit_set_nodes::<32>(data, height, bias, max_value)
}
};
result.map(|(bitset, data)| (IntSet::<u32>::from_bitset(bitset), data))
}
fn decode_sparse_bit_set_nodes<const BF: u8>(
data: &[u8],
height: u8,
bias: u32,
max_value: u32,
) -> Result<(BitSet, &[u8]), DecodingError> {
let mut out = BitSet::empty();
if height == 0 {
// 1 byte was used for the header.
return Ok((out, &data[1..]));
}
let mut builder = BitSetBuilder::start(&mut out);
let mut bits = InputBitStream::<BF>::from(data);
// TODO(garretrieger): estimate initial capacity (maximum is a function of the number of nodes in the bit stream).
let mut queue = VecDeque::<NextNode>::new();
queue.push_back(NextNode { start: 0, depth: 1 });
'outer: while let Some(next) = queue.pop_front() {
let mut bits = bits.next().ok_or(DecodingError)?;
if bits == 0 {
// all bits were zeroes which is a special command to completely fill in
// all integers covered by this node.
let exp = (height as u32) - next.depth + 1;
let node_size = (BF as u64).pow(exp);
let Some(start) = u32::try_from(next.start)
.ok()
.and_then(|start| start.checked_add(bias))
.filter(|start| *start <= max_value)
else {
// start is outside the valid range of the set, so skip this range.
continue;
};
let end = u32::try_from(next.start + node_size - 1)
.unwrap_or(u32::MAX)
.saturating_add(bias)
.min(max_value);
// TODO(garretrieger): implement special insert_range on the builder as well.
builder.set.insert_range(start..=end);
continue;
}
let height = height as u32;
let exp = height - next.depth;
let next_node_size = (BF as u64).pow(exp);
loop {
let bit_index = bits.trailing_zeros();
if bit_index == 32 {
break;
}
// TODO(garretrieger): possible optimization by having two versions of this loop
// as next.depth == height has the same value for each of the outer iterations.
if next.depth == height {
// TODO(garretrieger): this has a few branches, is it faster to do all the math in u64
// then check only once for > max_value? Will need to check with a benchmark.
let Some(start) = u32::try_from(next.start)
.ok()
.and_then(|start| start.checked_add(bit_index))
.and_then(|start| start.checked_add(bias))
.filter(|start| *start <= max_value)
else {
// At the lowest depth values are encountered in order, so if this is out of range so will be
// all future values. We can break early.
break 'outer;
};
// TODO(garretrieger): further optimize by inserting entire nodes at once (as a bit field).
builder.insert(start);
} else {
let start_delta = bit_index as u64 * next_node_size;
queue.push_back(NextNode {
start: next.start + start_delta,
depth: next.depth + 1,
});
}
bits &= !(1 << bit_index); // clear the bit that was just read.
}
}
builder.finish();
// If the max value was reached the loop above may have terminated early leaving some unprocessed nodes
// in the queue. The loop can only break once we are at the lowest depth which means that each remaining queue node
// will consume only one node from the bit stream. Advance the bit stream by the remaining number of nodes to
// correctly count the number of bytes consumed.
if !bits.skip_nodes(queue.len() as u32) {
// We ran out of bits to consume before decoding would have been finished.
return Err(DecodingError);
}
Ok((out, &data[bits.bytes_consumed()..]))
}
/// Encode this set as a sparse bit set byte encoding.
///
/// Sparse bit sets are a specialized, compact encoding of bit sets defined in the IFT specification:
/// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
pub fn to_sparse_bit_set(&self) -> Vec<u8> {
// TODO(garretrieger): use the heuristic approach from the incxfer
// implementation to guess the optimal size. Building the set 4 times
// is costly.
let mut candidates: Vec<Vec<u8>> = vec![];
let Some(max_value) = self.last() else {
return OutputBitStream::new(BranchFactor::Two, 0).into_bytes();
};
if BranchFactor::Two.tree_height_for(max_value) <= BranchFactor::Two.max_height() {
candidates.push(to_sparse_bit_set_with_bf::<2>(self));
}
if BranchFactor::Four.tree_height_for(max_value) <= BranchFactor::Four.max_height() {
candidates.push(to_sparse_bit_set_with_bf::<4>(self));
}
if BranchFactor::Eight.tree_height_for(max_value) <= BranchFactor::Eight.max_height() {
candidates.push(to_sparse_bit_set_with_bf::<8>(self));
}
if BranchFactor::ThirtyTwo.tree_height_for(max_value)
<= BranchFactor::ThirtyTwo.max_height()
{
candidates.push(to_sparse_bit_set_with_bf::<32>(self));
}
candidates.into_iter().min_by_key(|f| f.len()).unwrap()
}
}
/// Encode this set as a sparse bit set byte encoding with a specified branch factor.
///
/// Branch factor can be 2, 4, 8 or 32. It's a compile time constant so that optimized decoding implementations
/// can be generated by the compiler.
///
/// Sparse bit sets are a specialized, compact encoding of bit sets defined in the IFT specification:
/// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
pub fn to_sparse_bit_set_with_bf<const BF: u8>(set: &IntSet<u32>) -> Vec<u8> {
let branch_factor = BranchFactor::from_val(BF);
let Some(max_value) = set.last() else {
return OutputBitStream::new(branch_factor, 0).into_bytes();
};
let mut height = branch_factor.tree_height_for(max_value);
if height > branch_factor.max_height() {
if BF == 2 {
// Branch factor 2 cannot encode all possible u32 values, so upgrade to a BF4 set in that case.
return to_sparse_bit_set_with_bf::<4>(set);
}
// This shouldn't be reachable for any possible u32 values.
panic!("Height value exceeds the maximum for this branch factor.");
}
let mut os = OutputBitStream::new(branch_factor, height);
let mut nodes: Vec<Node> = vec![];
// We build the nodes that will comprise the bit stream in reverse order
// from the last value in the last layer up to the first layer. Then
// when generating the final stream the order is reversed.
// The reverse order construction is needed since nodes at the lower layer
// affect the values in the parent layers.
let mut indices = set.clone();
let mut filled_indices = IntSet::<u32>::all();
while height > 0 {
(indices, filled_indices) =
create_layer(branch_factor, indices, filled_indices, &mut nodes);
height -= 1;
}
for node in nodes.iter().rev() {
match node.node_type {
NodeType::Standard => os.write_node(node.bits),
NodeType::Filled => os.write_node(0),
NodeType::Skip => {}
};
}
os.into_bytes()
}
struct CreateLayerState<'a> {
// This is the set of indices which are to be set in the layer above this one
upper_indices: IntSet<u32>,
// Similarly, this is the set of indices in the layer above this one which are fully filled.
upper_filled_indices: IntSet<u32>,
current_node: Option<Node>,
current_node_filled_bits: u32,
nodes: &'a mut Vec<Node>,
child_count: u64,
nodes_init_length: u64,
branch_factor: BranchFactor,
}
impl CreateLayerState<'_> {
fn commit_current_node(&mut self) {
let Some(mut node) = self.current_node.take() else {
// noop if there isn't a node to commit.
return;
};
self.upper_indices.insert(node.parent_index);
if self.current_node_filled_bits == self.branch_factor.u32_mask() {
// This node is filled and can thus be represented by a node that is '0'.
// It's index is recorded so that the parent node can also check if they are filled.
self.upper_filled_indices.insert(node.parent_index);
node.node_type = NodeType::Filled;
if self.nodes_init_length >= self.child_count {
// Since this node is filled, find all nodes which are children and set them to be skipped in
// the encoding.
let children_start_index = self.nodes_init_length.saturating_sub(self.child_count);
let children_end_index = self.nodes_init_length;
// TODO(garretrieger): this scans all nodes of the previous layer to find those which are children,
// but we can likely limit it to just the children of this node with some extra book keeping.
for child in
&mut self.nodes[children_start_index as usize..children_end_index as usize]
{
if child.parent_index >= node.parent_index * self.branch_factor.value()
&& child.parent_index < (node.parent_index + 1) * self.branch_factor.value()
{
child.node_type = NodeType::Skip;
}
}
}
}
self.nodes.push(node);
self.current_node_filled_bits = 0;
}
}
/// Compute the nodes for a layer of the sparse bit set.
///
/// Computes the nodes needed for the layer which contains the indices in
/// 'iter'. The new nodes are appended to 'nodes'. 'iter' must be sorted
/// in ascending order.
///
/// Returns the set of indices for the layer above.
fn create_layer(
branch_factor: BranchFactor,
values: IntSet<u32>,
filled_values: IntSet<u32>,
nodes: &mut Vec<Node>,
) -> (IntSet<u32>, IntSet<u32>) {
let mut state = CreateLayerState {
upper_indices: IntSet::<u32>::empty(),
upper_filled_indices: IntSet::<u32>::empty(),
current_node: None,
current_node_filled_bits: 0,
child_count: values.len(),
nodes_init_length: nodes.len() as u64,
nodes,
branch_factor,
};
// The nodes array is produced in reverse order and then reversed before final output.
for v in values.iter().rev() {
let parent_index = v / branch_factor.value();
let prev_parent_index = state
.current_node
.as_ref()
.map_or(parent_index, |node| node.parent_index);
if prev_parent_index != parent_index {
state.commit_current_node();
}
let current_node = state.current_node.get_or_insert(Node {
bits: 0,
parent_index,
node_type: NodeType::Standard,
});
let mask = 0b1 << (v % branch_factor.value());
current_node.bits |= mask;
if filled_values.contains(v) {
state.current_node_filled_bits |= mask;
}
}
state.commit_current_node();
(state.upper_indices, state.upper_filled_indices)
}
enum NodeType {
Standard,
Filled,
Skip,
}
struct Node {
bits: u32,
parent_index: u32,
node_type: NodeType,
}
impl BranchFactor {
pub(crate) fn value(&self) -> u32 {
match self {
BranchFactor::Two => 2,
BranchFactor::Four => 4,
BranchFactor::Eight => 8,
BranchFactor::ThirtyTwo => 32,
}
}
/// The maximum height that can be used for a given branch factor without the risk of encountering overflows
pub(crate) fn max_height(&self) -> u8 {
match self {
BranchFactor::Two => 31,
BranchFactor::Four => 16,
BranchFactor::Eight => 11,
BranchFactor::ThirtyTwo => 7,
}
}
fn tree_height_for(&self, max_value: u32) -> u8 {
// height H, can represent up to (BF^height) - 1
let mut height: u32 = 0;
let mut max_value = max_value;
loop {
height += 1;
max_value >>= self.node_size_log2();
if max_value == 0 {
break height as u8;
}
}
}
fn from_val(val: u8) -> BranchFactor {
match val {
2 => BranchFactor::Two,
4 => BranchFactor::Four,
8 => BranchFactor::Eight,
32 => BranchFactor::ThirtyTwo,
// This should never happen as this is only used internally.
_ => panic!("Invalid branch factor."),
}
}
fn node_size_log2(&self) -> u32 {
match self {
BranchFactor::Two => 1,
BranchFactor::Four => 2,
BranchFactor::Eight => 3,
BranchFactor::ThirtyTwo => 5,
}
}
pub(crate) fn byte_mask(&self) -> u32 {
match self {
BranchFactor::Two => 0b00000011,
BranchFactor::Four => 0b00001111,
BranchFactor::Eight => 0b11111111,
BranchFactor::ThirtyTwo => 0b11111111,
}
}
fn u32_mask(&self) -> u32 {
match self {
BranchFactor::Two => 0b00000000_00000000_00000000_00000011,
BranchFactor::Four => 0b00000000_00000000_00000000_00001111,
BranchFactor::Eight => 0b00000000_00000000_00000000_11111111,
BranchFactor::ThirtyTwo => 0b11111111_11111111_11111111_11111111,
}
}
}
struct NextNode {
start: u64,
depth: u32,
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod test {
use super::*;
#[test]
fn spec_example_2() {
// Test of decoding the example 2 given in the specification.
// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
let bytes = [
0b00001110, 0b00100001, 0b00010001, 0b00000001, 0b00000100, 0b00000010, 0b00001000,
];
let set = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
let expected: IntSet<u32> = [2, 33, 323].iter().copied().collect();
assert_eq!(set, expected);
}
#[test]
fn spec_example_3() {
// Test of decoding the example 3 given in the specification.
// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
let bytes = [0b00000000];
let set = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
let expected: IntSet<u32> = [].iter().copied().collect();
assert_eq!(set, expected);
}
#[test]
fn spec_example_4() {
// Test of decoding the example 4 given in the specification.
// See: <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
let bytes = [0b00001101, 0b00000011, 0b00110001];
let set = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(0..=17);
assert_eq!(set, expected);
}
#[test]
fn invalid() {
// Spec example 2 with one byte missing.
let bytes = [
0b00001110, 0b00100001, 0b00010001, 0b00000001, 0b00000100, 0b00000010,
];
assert!(IntSet::<u32>::from_sparse_bit_set(&bytes).is_err());
// Max height exceeded.
let bytes = [
0b0_01000_11, // BF 32, Depth 8
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L1
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L2
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L3
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L4
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L5
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L6
0b00000000,
0b00000000,
0b00000000,
0b00000001, // L7
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L8
];
assert!(IntSet::<u32>::from_sparse_bit_set(&bytes).is_err());
}
#[test]
fn invalid_biased_and_bounded() {
let bytes = [0b0_00011_01, 0b0000_0011, 0b1111_0011];
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, u32::MAX).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 20).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 19).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 18).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 15).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 14).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 1, 20).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 2, 20).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 3, 20).is_err());
assert!(IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 6, 20).is_err());
}
#[test]
fn larger_than_u32() {
// Set with values beyond u32
let bytes = [
0b0_00111_11, // BF 32, Depth 7
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L1
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L2
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L3
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L4
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L5
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L6
0b00000000,
0b00000000,
0b00000000,
0b00000001, // L7
];
assert_eq!(
IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap(),
IntSet::<u32>::empty()
);
// Set with filled node values beyond u32
let bytes = [
0b0_00111_11, // BF 32, Depth 7
0b00000000,
0b00000000,
0b00000000,
0b10000000, // L1
0b00000000,
0b00000000,
0b00000000,
0b00000000, // L2
];
assert_eq!(
IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap(),
IntSet::<u32>::empty()
);
}
#[test]
fn from_sparse_bit_set_bounded_with_remaining_data() {
let bytes = [0b00001101, 0b00000011, 0b00110001, 0b10101010];
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(0..=17);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 19).unwrap(),
(expected.clone(), &bytes[3..]),
);
}
#[test]
fn from_sparse_bit_set_biased_and_bounded() {
let bytes = [0b0_00011_01, 0b0000_0011, 0b1111_0011, 0b0000_0001];
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(0..=20);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 20).unwrap(),
(expected.clone(), &bytes[4..])
);
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(0..=19);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 19).unwrap(),
(expected.clone(), &bytes[4..])
);
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(1..=20);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 1, 20).unwrap(),
(expected.clone(), &bytes[4..])
);
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(1..=18);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 1, 18).unwrap(),
(expected.clone(), &bytes[4..])
);
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(0..=14);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 14).unwrap(),
(expected.clone(), &bytes[4..])
);
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert_range(6..=20);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 6, 20).unwrap(),
(expected.clone(), &bytes[4..])
);
let mut expected: IntSet<u32> = IntSet::<u32>::empty();
expected.insert(0);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 0, 0).unwrap(),
(expected.clone(), &bytes[4..])
);
assert_eq!(
IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 1, 0).unwrap(),
(IntSet::<u32>::empty().clone(), &bytes[4..])
);
let bytes = [0b00000000];
let set = IntSet::<u32>::from_sparse_bit_set_bounded(&bytes, 5, 0)
.unwrap()
.0;
assert_eq!(set, IntSet::<u32>::empty());
}
#[test]
fn test_tree_height_for() {
assert_eq!(BranchFactor::Two.tree_height_for(0), 1);
assert_eq!(BranchFactor::Two.tree_height_for(1), 1);
assert_eq!(BranchFactor::Two.tree_height_for(2), 2);
assert_eq!(BranchFactor::Two.tree_height_for(117), 7);
assert_eq!(BranchFactor::Four.tree_height_for(0), 1);
assert_eq!(BranchFactor::Four.tree_height_for(3), 1);
assert_eq!(BranchFactor::Four.tree_height_for(4), 2);
assert_eq!(BranchFactor::Four.tree_height_for(63), 3);
assert_eq!(BranchFactor::Four.tree_height_for(64), 4);
assert_eq!(BranchFactor::Eight.tree_height_for(0), 1);
assert_eq!(BranchFactor::Eight.tree_height_for(7), 1);
assert_eq!(BranchFactor::Eight.tree_height_for(8), 2);
assert_eq!(BranchFactor::Eight.tree_height_for(32767), 5);
assert_eq!(BranchFactor::Eight.tree_height_for(32768), 6);
assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(0), 1);
assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(31), 1);
assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(32), 2);
assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(1_048_575), 4);
assert_eq!(BranchFactor::ThirtyTwo.tree_height_for(1_048_576), 5);
}
#[test]
fn generate_spec_example_2() {
// Test of reproducing the encoding of example 2 given
// in the specification. See:
// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
let actual_bytes = to_sparse_bit_set_with_bf::<8>(&[2, 33, 323].iter().copied().collect());
let expected_bytes = [
0b00001110, 0b00100001, 0b00010001, 0b00000001, 0b00000100, 0b00000010, 0b00001000,
];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn generate_spec_example_3() {
// Test of reproducing the encoding of example 3 given
// in the specification. See:
// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
let actual_bytes = to_sparse_bit_set_with_bf::<2>(&IntSet::<u32>::empty());
let expected_bytes = [0b00000000];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn generate_spec_example_4() {
// Test of reproducing the encoding of example 3 given
// in the specification. See:
// <https://w3c.github.io/IFT/Overview.html#sparse-bit-set-decoding>
let actual_bytes = to_sparse_bit_set_with_bf::<4>(&(0..=17).collect());
let expected_bytes = [0b00001101, 0b0000_0011, 0b0011_0001];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn encode_one_level() {
let actual_bytes = to_sparse_bit_set_with_bf::<8>(&[2, 6].iter().copied().collect());
let expected_bytes = [0b0_00001_10, 0b01000100];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn encode_one_level_filled() {
let actual_bytes = to_sparse_bit_set_with_bf::<8>(&(0..=7).collect());
let expected_bytes = [0b0_00001_10, 0b00000000];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn encode_two_level_filled() {
let actual_bytes = to_sparse_bit_set_with_bf::<8>(&(3..=21).collect());
let expected_bytes = [0b0_00010_10, 0b00000111, 0b11111000, 0b00000000, 0b00111111];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn encode_two_level_not_filled() {
let actual_bytes = to_sparse_bit_set_with_bf::<4>(&[0, 4, 8, 12].iter().copied().collect());
let expected_bytes = [0b0_00010_01, 0b0001_1111, 0b0001_0001, 0b0000_0001];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn encode_four_level_filled() {
let mut s = IntSet::<u32>::empty();
s.insert_range(64..=127); // Filled node on level 3
s.insert_range(512..=1023); // Filled node on level 2
s.insert(4000);
let actual_bytes = to_sparse_bit_set_with_bf::<8>(&s);
let expected_bytes = [
// Header
0b0_00100_10,
// L1
0b10000011,
// L2
0b00000010,
0b00000000,
0b01000000,
// L3,
0b00000000,
0b00010000,
// L4
0b00000001,
];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn encode_bf32() {
let actual_bytes = to_sparse_bit_set_with_bf::<32>(&[2, 31, 323].iter().copied().collect());
let expected_bytes = [
0b0_00010_11,
// node 0
0b00000001,
0b00000100,
0b00000000,
0b00000000,
// node 1
0b00000100,
0b00000000,
0b00000000,
0b10000000,
// node 2
0b00001000,
0b00000000,
0b00000000,
0b00000000,
];
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn round_trip() {
let s1: IntSet<u32> = [11, 74, 9358].iter().copied().collect();
let mut s2: IntSet<u32> = s1.clone();
s2.insert_range(67..=412);
check_round_trip::<2>(&s1);
check_round_trip::<4>(&s1);
check_round_trip::<8>(&s1);
check_round_trip::<32>(&s1);
check_round_trip::<2>(&s2);
check_round_trip::<4>(&s2);
check_round_trip::<8>(&s2);
check_round_trip::<32>(&s2);
}
fn check_round_trip<const BF: u8>(s: &IntSet<u32>) {
let bytes = to_sparse_bit_set_with_bf::<BF>(s);
let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes).unwrap();
assert_eq!(*s, s_prime);
}
#[test]
fn find_smallest_bf() {
let s: IntSet<u32> = [11, 74, 9358].iter().copied().collect();
let bytes = s.to_sparse_bit_set();
// BF4
assert_eq!(vec![0b0_00111_01], bytes[0..1]);
let s: IntSet<u32> = [
16, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
]
.iter()
.copied()
.collect();
let bytes = s.to_sparse_bit_set();
// BF32
assert_eq!(vec![0b0_00001_11], bytes[0..1]);
}
#[test]
fn encode_maxu32() {
let s: IntSet<u32> = [1, u32::MAX].iter().copied().collect();
let bytes = s.to_sparse_bit_set();
let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes);
assert_eq!(s, s_prime.unwrap());
let s: IntSet<u32> = [1, u32::MAX].iter().copied().collect();
let bytes = to_sparse_bit_set_with_bf::<2>(&s);
let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes);
assert_eq!(s, s_prime.unwrap());
let s: IntSet<u32> = [1, u32::MAX].iter().copied().collect();
let bytes = to_sparse_bit_set_with_bf::<4>(&s);
let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes);
assert_eq!(s, s_prime.unwrap());
let s: IntSet<u32> = [1, u32::MAX].iter().copied().collect();
let bytes = to_sparse_bit_set_with_bf::<8>(&s);
let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes);
assert_eq!(s, s_prime.unwrap());
let s: IntSet<u32> = [1, u32::MAX].iter().copied().collect();
let bytes = to_sparse_bit_set_with_bf::<32>(&s);
let s_prime = IntSet::<u32>::from_sparse_bit_set(&bytes);
assert_eq!(s, s_prime.unwrap());
}
}

View File

@@ -0,0 +1,400 @@
//! Stores a disjoint collection of ranges over numeric types.
//!
//! Overlapping and adjacent ranges are automatically merged together.
use core::{
cmp::{max, min},
fmt::{Debug, Formatter},
iter::Peekable,
ops::RangeInclusive,
};
use std::collections::BTreeMap;
use types::Fixed;
#[derive(Default, Clone, PartialEq, Eq)]
/// A set of disjoint ranges over numeric types.
///
/// Overlapping and adjacent ranges are automatically merged together.
pub struct RangeSet<T> {
// an entry in the map ranges[a] = b implies there is an range [a, b] (inclusive) in this set.
ranges: BTreeMap<T, T>,
}
/// Allows a two values to be tested for adjacency.
pub trait OrdAdjacency {
/// Returns true if self is adjacent on either side of rhs.
fn are_adjacent(self, rhs: Self) -> bool;
}
impl<T> RangeSet<T>
where
T: Ord + Copy + OrdAdjacency,
{
// Returns true if there are no members in this set currently.
pub fn is_empty(&self) -> bool {
self.ranges.is_empty()
}
/// Insert a range into this set, automatically merging with existing ranges as needed.
pub fn insert(&mut self, range: RangeInclusive<T>) {
if range.end() < range.start() {
// ignore or malformed ranges.
return;
}
let mut start = *range.start();
let mut end = *range.end();
// There may be up to one intersecting range prior to this new range, check for it and merge if needed.
if let Some((prev_start, prev_end)) = self.prev_range(start) {
if range_is_subset(start, end, prev_start, prev_end) {
return;
}
if ranges_overlap_or_adjacent(start, end, prev_start, prev_end) {
start = min(start, prev_start);
end = max(end, prev_end);
self.ranges.remove(&prev_start);
}
};
// There may be one or more ranges proceeding this new range that intersect, find and merge them as needed.
loop {
let Some((next_start, next_end)) = self.next_range(start) else {
// No existing ranges which might overlap, can now insert the current range
self.ranges.insert(start, end);
return;
};
if range_is_subset(start, end, next_start, next_end) {
return;
}
if ranges_overlap_or_adjacent(start, end, next_start, next_end) {
start = min(start, next_start);
end = max(end, next_end);
self.ranges.remove(&next_start);
} else {
self.ranges.insert(start, end);
return;
}
}
}
/// Returns an iterator over the contained ranges.
pub fn iter(&'_ self) -> impl Iterator<Item = RangeInclusive<T>> + '_ {
self.ranges.iter().map(|(a, b)| *a..=*b)
}
/// Returns an iterator over the intersection of this and other.
pub fn intersection<'a>(
&'a self,
other: &'a Self,
) -> impl Iterator<Item = RangeInclusive<T>> + 'a {
IntersectionIter {
it_a: self.iter().peekable(),
it_b: other.iter().peekable(),
}
}
/// Finds a range in this set with a start greater than or equal to the provided start value.
fn next_range(&self, start: T) -> Option<(T, T)> {
let (next_start, next_end) = self.ranges.range(start..).next()?;
Some((*next_start, *next_end))
}
/// Finds a range in this set with a start less than the provided start value.
fn prev_range(&self, start: T) -> Option<(T, T)> {
let (next_start, next_end) = self.ranges.range(..start).next_back()?;
Some((*next_start, *next_end))
}
}
impl<T> Extend<RangeInclusive<T>> for RangeSet<T>
where
T: Copy + Ord + OrdAdjacency,
{
fn extend<I: IntoIterator<Item = RangeInclusive<T>>>(&mut self, iter: I) {
iter.into_iter().for_each(|r| self.insert(r));
}
}
impl<T> FromIterator<RangeInclusive<T>> for RangeSet<T>
where
T: Default + Copy + Ord + OrdAdjacency,
{
fn from_iter<I: IntoIterator<Item = RangeInclusive<T>>>(iter: I) -> Self {
let mut result: Self = Default::default();
result.extend(iter);
result
}
}
impl<T> Debug for RangeSet<T>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "RangeSet {{")?;
for (start, end) in self.ranges.iter() {
write!(f, "[{:?}, {:?}], ", start, end)?;
}
write!(f, "}}")
}
}
struct IntersectionIter<A, B, T>
where
A: Iterator<Item = RangeInclusive<T>>,
B: Iterator<Item = RangeInclusive<T>>,
{
it_a: Peekable<A>,
it_b: Peekable<B>,
}
impl<A, B, T> Iterator for IntersectionIter<A, B, T>
where
A: Iterator<Item = RangeInclusive<T>>,
B: Iterator<Item = RangeInclusive<T>>,
T: Ord + Copy,
{
type Item = RangeInclusive<T>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let (Some(a), Some(b)) = (self.it_a.peek(), self.it_b.peek()) else {
return None;
};
let a = a.clone();
let b = b.clone();
match range_intersection(&a, &b) {
Some(intersection) => {
self.step_iterators(&a, &b);
return Some(intersection);
}
None => self.step_iterators(&a, &b),
}
}
}
}
impl<A, B, T> IntersectionIter<A, B, T>
where
A: Iterator<Item = RangeInclusive<T>>,
B: Iterator<Item = RangeInclusive<T>>,
T: Ord,
{
fn step_iterators(&mut self, a: &RangeInclusive<T>, b: &RangeInclusive<T>) {
if a.end() <= b.end() {
self.it_a.next();
}
if a.end() >= b.end() {
self.it_b.next();
}
}
}
impl OrdAdjacency for u32 {
fn are_adjacent(self, rhs: u32) -> bool {
matches!(self.checked_add(1).map(|r| r == rhs), Some(true))
|| matches!(rhs.checked_add(1).map(|r| r == self), Some(true))
}
}
impl OrdAdjacency for u16 {
fn are_adjacent(self, rhs: u16) -> bool {
matches!(self.checked_add(1).map(|r| r == rhs), Some(true))
|| matches!(rhs.checked_add(1).map(|r| r == self), Some(true))
}
}
impl OrdAdjacency for Fixed {
fn are_adjacent(self, rhs: Fixed) -> bool {
matches!(
self.checked_add(Fixed::EPSILON).map(|r| r == rhs),
Some(true)
) || matches!(
rhs.checked_add(Fixed::EPSILON).map(|r| r == self),
Some(true)
)
}
}
/// If a and b intersect return a range representing the intersection.
fn range_intersection<T: Ord + Copy>(
a: &RangeInclusive<T>,
b: &RangeInclusive<T>,
) -> Option<RangeInclusive<T>> {
if a.start() <= b.end() && b.start() <= a.end() {
Some(*max(a.start(), b.start())..=*min(a.end(), b.end()))
} else {
None
}
}
/// Returns true if the ranges [a_start, a_end] and [b_start, b_end] overlap or are adjacent to each other.
///
/// All bounds are inclusive.
fn ranges_overlap_or_adjacent<T>(a_start: T, a_end: T, b_start: T, b_end: T) -> bool
where
T: Ord + OrdAdjacency,
{
(a_start <= b_end && b_start <= a_end)
|| (a_end.are_adjacent(b_start))
|| (b_end.are_adjacent(a_start))
}
/// Returns true if the range [a_start, a_end] is a subset of [b_start, b_end].
///
/// All bounds are inclusive.
fn range_is_subset<T>(a_start: T, a_end: T, b_start: T, b_end: T) -> bool
where
T: Ord,
{
a_start >= b_start && a_end <= b_end
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn insert_invalid() {
let mut map: RangeSet<u32> = Default::default();
map.insert(12..=11);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![],);
}
#[test]
fn insert_non_overlapping() {
let mut map: RangeSet<u32> = Default::default();
map.insert(11..=11);
map.insert(2..=3);
map.insert(6..=9);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![2..=3, 6..=9, 11..=11],);
}
#[test]
fn insert_subset_before() {
let mut map: RangeSet<u32> = Default::default();
map.insert(2..=8);
map.insert(3..=7);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![2..=8],);
}
#[test]
fn insert_subset_after() {
let mut map: RangeSet<u32> = Default::default();
map.insert(2..=8);
map.insert(2..=7);
map.insert(2..=8);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![2..=8],);
}
#[test]
fn insert_overlapping_before() {
let mut map: RangeSet<u32> = Default::default();
map.insert(2..=8);
map.insert(7..=11);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![2..=11],);
}
#[test]
fn insert_overlapping_after() {
let mut map: RangeSet<u32> = Default::default();
map.insert(10..=14);
map.insert(7..=11);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![7..=14],);
let mut map: RangeSet<u32> = Default::default();
map.insert(10..=14);
map.insert(10..=17);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![10..=17],);
}
#[test]
fn insert_overlapping_multiple_after() {
let mut map: RangeSet<u32> = Default::default();
map.insert(10..=14);
map.insert(16..=17);
map.insert(7..=16);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![7..=17],);
let mut map: RangeSet<u32> = Default::default();
map.insert(10..=14);
map.insert(16..=17);
map.insert(10..=16);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![10..=17],);
let mut map: RangeSet<u32> = Default::default();
map.insert(10..=14);
map.insert(16..=17);
map.insert(10..=17);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![10..=17],);
}
#[test]
fn insert_overlapping_before_and_after() {
let mut map: RangeSet<u32> = Default::default();
map.insert(6..=8);
map.insert(10..=14);
map.insert(16..=20);
map.insert(7..=19);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![6..=20],);
}
#[test]
fn insert_joins_adjacent() {
let mut map: RangeSet<u32> = Default::default();
map.insert(6..=8);
map.insert(9..=10);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![6..=10],);
let mut map: RangeSet<u32> = Default::default();
map.insert(9..=10);
map.insert(6..=8);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![6..=10],);
let mut map: RangeSet<u32> = Default::default();
map.insert(6..=8);
map.insert(10..=10);
map.insert(9..=9);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![6..=10],);
}
#[test]
fn from_iter_and_extend() {
let mut map: RangeSet<u32> = [2..=5, 13..=64, 7..=9].into_iter().collect();
assert_eq!(map.iter().collect::<Vec<_>>(), vec![2..=5, 7..=9, 13..=64],);
map.extend([6..=17, 100..=101]);
assert_eq!(map.iter().collect::<Vec<_>>(), vec![2..=64, 100..=101],);
}
#[test]
fn intersection() {
let a: RangeSet<u32> = [2..=5, 7..=9, 13..=64].into_iter().collect();
let b: RangeSet<u32> = [1..=3, 5..=8, 13..=64, 67..=69].into_iter().collect();
let expected = vec![2..=3, 5..=5, 7..=8, 13..=64];
assert_eq!(a.intersection(&b).collect::<Vec<_>>(), expected);
assert_eq!(b.intersection(&a).collect::<Vec<_>>(), expected);
}
}

325
vendor/read-fonts/src/font_data.rs vendored Normal file
View File

@@ -0,0 +1,325 @@
//! raw font bytes
#![deny(clippy::arithmetic_side_effects)]
use std::ops::{Range, RangeBounds};
use bytemuck::AnyBitPattern;
use types::{BigEndian, FixedSize, Scalar};
use crate::array::ComputedArray;
use crate::read::{ComputeSize, FontReadWithArgs, ReadError};
use crate::table_ref::TableRef;
use crate::FontRead;
/// A reference to raw binary font data.
///
/// This is a wrapper around a byte slice, that provides convenience methods
/// for parsing and validating that data.
#[derive(Debug, Default, Clone, Copy)]
pub struct FontData<'a> {
bytes: &'a [u8],
}
/// A cursor for validating bytes during parsing.
///
/// This type improves the ergonomics of validation blah blah
///
/// # Note
///
/// call `finish` when you're done to ensure you're in bounds
#[derive(Debug, Default, Clone, Copy)]
pub struct Cursor<'a> {
pos: usize,
data: FontData<'a>,
}
impl<'a> FontData<'a> {
/// Empty data, useful for some tests and examples
pub const EMPTY: FontData<'static> = FontData { bytes: &[] };
/// Create a new `FontData` with these bytes.
///
/// You generally don't need to do this? It is handled for you when loading
/// data from disk, but may be useful in tests.
pub const fn new(bytes: &'a [u8]) -> Self {
FontData { bytes }
}
/// The length of the data, in bytes
pub fn len(&self) -> usize {
self.bytes.len()
}
/// `true` if the data has a length of zero bytes.
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
/// Returns self[pos..]
pub fn split_off(&self, pos: usize) -> Option<FontData<'a>> {
self.bytes.get(pos..).map(|bytes| FontData { bytes })
}
/// returns self[..pos], and updates self to = self[pos..];
pub fn take_up_to(&mut self, pos: usize) -> Option<FontData<'a>> {
if pos > self.len() {
return None;
}
let (head, tail) = self.bytes.split_at(pos);
self.bytes = tail;
Some(FontData { bytes: head })
}
pub fn slice(&self, range: impl RangeBounds<usize>) -> Option<FontData<'a>> {
let bounds = (range.start_bound().cloned(), range.end_bound().cloned());
self.bytes.get(bounds).map(|bytes| FontData { bytes })
}
/// Read a scalar at the provided location in the data.
pub fn read_at<T: Scalar>(&self, offset: usize) -> Result<T, ReadError> {
let end = offset
.checked_add(T::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
self.bytes
.get(offset..end)
.and_then(T::read)
.ok_or(ReadError::OutOfBounds)
}
/// Read a big-endian value at the provided location in the data.
pub fn read_be_at<T: Scalar>(&self, offset: usize) -> Result<BigEndian<T>, ReadError> {
let end = offset
.checked_add(T::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
self.bytes
.get(offset..end)
.and_then(BigEndian::from_slice)
.ok_or(ReadError::OutOfBounds)
}
pub fn read_with_args<T>(&self, range: Range<usize>, args: &T::Args) -> Result<T, ReadError>
where
T: FontReadWithArgs<'a>,
{
self.slice(range)
.ok_or(ReadError::OutOfBounds)
.and_then(|data| T::read_with_args(data, args))
}
fn check_in_bounds(&self, offset: usize) -> Result<(), ReadError> {
self.bytes
.get(..offset)
.ok_or(ReadError::OutOfBounds)
.map(|_| ())
}
/// Interpret the bytes at the provided offset as a reference to `T`.
///
/// Returns an error if the slice `offset..` is shorter than `T::RAW_BYTE_LEN`.
///
/// This is a wrapper around [`read_ref_unchecked`][], which panics if
/// the type does not uphold the required invariants.
///
/// # Panics
///
/// This function will panic if `T` is zero-sized, has an alignment
/// other than one, or has any internal padding.
///
/// [`read_ref_unchecked`]: [Self::read_ref_unchecked]
pub fn read_ref_at<T: AnyBitPattern + FixedSize>(
&self,
offset: usize,
) -> Result<&'a T, ReadError> {
let end = offset
.checked_add(T::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
self.bytes
.get(offset..end)
.ok_or(ReadError::OutOfBounds)
.map(bytemuck::from_bytes)
}
/// Interpret the bytes at the provided offset as a slice of `T`.
///
/// Returns an error if `range` is out of bounds for the underlying data,
/// or if the length of the range is not a multiple of `T::RAW_BYTE_LEN`.
///
/// This is a wrapper around [`read_array_unchecked`][], which panics if
/// the type does not uphold the required invariants.
///
/// # Panics
///
/// This function will panic if `T` is zero-sized, has an alignment
/// other than one, or has any internal padding.
///
/// [`read_array_unchecked`]: [Self::read_array_unchecked]
pub fn read_array<T: AnyBitPattern + FixedSize>(
&self,
range: Range<usize>,
) -> Result<&'a [T], ReadError> {
let bytes = self
.bytes
.get(range.clone())
.ok_or(ReadError::OutOfBounds)?;
if bytes
.len()
.checked_rem(std::mem::size_of::<T>())
.unwrap_or(1) // definitely != 0
!= 0
{
return Err(ReadError::InvalidArrayLen);
};
Ok(bytemuck::cast_slice(bytes))
}
pub(crate) fn cursor(&self) -> Cursor<'a> {
Cursor {
pos: 0,
data: *self,
}
}
/// Return the data as a byte slice
pub fn as_bytes(&self) -> &'a [u8] {
self.bytes
}
}
impl<'a> Cursor<'a> {
pub(crate) fn advance<T: Scalar>(&mut self) {
self.pos = self.pos.saturating_add(T::RAW_BYTE_LEN);
}
pub(crate) fn advance_by(&mut self, n_bytes: usize) {
self.pos = self.pos.saturating_add(n_bytes);
}
/// Read a variable length u32 and advance the cursor
pub(crate) fn read_u32_var(&mut self) -> Result<u32, ReadError> {
let mut next = || self.read::<u8>().map(|v| v as u32);
let b0 = next()?;
// TODO this feels possible to simplify, e.g. compute length, loop taking one and shifting and or'ing
#[allow(clippy::arithmetic_side_effects)] // these are all checked
let result = match b0 {
_ if b0 < 0x80 => b0,
_ if b0 < 0xC0 => ((b0 - 0x80) << 8) | next()?,
_ if b0 < 0xE0 => ((b0 - 0xC0) << 16) | (next()? << 8) | next()?,
_ if b0 < 0xF0 => ((b0 - 0xE0) << 24) | (next()? << 16) | (next()? << 8) | next()?,
_ => {
// TODO: << 32 doesn't make sense. (b0 - 0xF0) << 32
(next()? << 24) | (next()? << 16) | (next()? << 8) | next()?
}
};
Ok(result)
}
/// Read a scalar and advance the cursor.
pub(crate) fn read<T: Scalar>(&mut self) -> Result<T, ReadError> {
let temp = self.data.read_at(self.pos);
self.advance::<T>();
temp
}
/// Read a big-endian value and advance the cursor.
pub(crate) fn read_be<T: Scalar>(&mut self) -> Result<BigEndian<T>, ReadError> {
let temp = self.data.read_be_at(self.pos);
self.advance::<T>();
temp
}
pub(crate) fn read_with_args<T>(&mut self, args: &T::Args) -> Result<T, ReadError>
where
T: FontReadWithArgs<'a> + ComputeSize,
{
let len = T::compute_size(args)?;
let range_end = self.pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
let temp = self.data.read_with_args(self.pos..range_end, args);
self.advance_by(len);
temp
}
// only used in records that contain arrays :/
pub(crate) fn read_computed_array<T>(
&mut self,
len: usize,
args: &T::Args,
) -> Result<ComputedArray<'a, T>, ReadError>
where
T: FontReadWithArgs<'a> + ComputeSize,
{
let len = len
.checked_mul(T::compute_size(args)?)
.ok_or(ReadError::OutOfBounds)?;
let range_end = self.pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
let temp = self.data.read_with_args(self.pos..range_end, args);
self.advance_by(len);
temp
}
pub(crate) fn read_array<T: AnyBitPattern + FixedSize>(
&mut self,
n_elem: usize,
) -> Result<&'a [T], ReadError> {
let len = n_elem
.checked_mul(T::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
let end = self.pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
let temp = self.data.read_array(self.pos..end);
self.advance_by(len);
temp
}
/// return the current position, or an error if we are out of bounds
pub(crate) fn position(&self) -> Result<usize, ReadError> {
self.data.check_in_bounds(self.pos).map(|_| self.pos)
}
// used when handling fields with an implicit length, which must be at the
// end of a table.
pub(crate) fn remaining_bytes(&self) -> usize {
self.data.len().saturating_sub(self.pos)
}
pub(crate) fn remaining(self) -> Option<FontData<'a>> {
self.data.split_off(self.pos)
}
pub fn is_empty(&self) -> bool {
self.pos >= self.data.len()
}
pub(crate) fn finish<T>(self, shape: T) -> Result<TableRef<'a, T>, ReadError> {
let data = self.data;
data.check_in_bounds(self.pos)?;
Ok(TableRef { data, shape })
}
}
// useful so we can have offsets that are just to data
impl<'a> FontRead<'a> for FontData<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
Ok(data)
}
}
impl AsRef<[u8]> for FontData<'_> {
fn as_ref(&self) -> &[u8] {
self.bytes
}
}
impl<'a> From<&'a [u8]> for FontData<'a> {
fn from(src: &'a [u8]) -> FontData<'a> {
FontData::new(src)
}
}
//kind of ugly, but makes FontData work with FontBuilder. If FontBuilder stops using
//Cow in its API, we can probably get rid of this?
#[cfg(feature = "std")]
impl<'a> From<FontData<'a>> for std::borrow::Cow<'a, [u8]> {
fn from(src: FontData<'a>) -> Self {
src.bytes.into()
}
}

373
vendor/read-fonts/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,373 @@
//! Reading OpenType tables
//!
//! This crate provides memory safe zero-allocation parsing of font files.
//! It is unopinionated, and attempts to provide raw access to the underlying
//! font data as it is described in the [OpenType specification][spec].
//!
//! This crate is intended for use by other parts of a font stack, such as a
//! shaping engine or a glyph rasterizer.
//!
//! In addition to raw data access, this crate may also provide reference
//! implementations of algorithms for interpreting that data, where such an
//! implementation is required for the data to be useful. For instance, we
//! provide functions for [mapping codepoints to glyph identifiers][cmap-impl]
//! using the `cmap` table, or for [decoding entries in the `name` table][NameString].
//!
//! For higher level/more ergonomic access to font data, you may want to look
//! into using [`skrifa`] instead.
//!
//! ## Structure & codegen
//!
//! The root [`tables`] module contains a submodule for each supported
//! [table][table-directory], and that submodule contains items for each table,
//! record, flagset or enum described in the relevant portion of the spec.
//!
//! The majority of the code in the tables module is auto-generated. For more
//! information on our use of codegen, see the [codegen tour].
//!
//! # Related projects
//!
//! - [`write-fonts`] is a companion crate for creating/modifying font files
//! - [`skrifa`] provides access to glyph outlines and metadata (in the same vein
//! as [freetype])
//!
//! # Example
//!
//! ```no_run
//! # let path_to_my_font_file = std::path::Path::new("");
//! use read_fonts::{FontRef, TableProvider};
//! let font_bytes = std::fs::read(path_to_my_font_file).unwrap();
//! // Single fonts only. for font collections (.ttc) use FontRef::from_index
//! let font = FontRef::new(&font_bytes).expect("failed to read font data");
//! let head = font.head().expect("missing 'head' table");
//! let maxp = font.maxp().expect("missing 'maxp' table");
//!
//! println!("font version {} containing {} glyphs", head.font_revision(), maxp.num_glyphs());
//! ```
//!
//!
//! [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/
//! [codegen-tour]: https://github.com/googlefonts/fontations/blob/main/docs/codegen-tour.md
//! [cmap-impl]: tables::cmap::Cmap::map_codepoint
//! [`write-fonts`]: https://docs.rs/write-fonts/
//! [`skrifa`]: https://docs.rs/skrifa/
//! [freetype]: http://freetype.org
//! [codegen tour]: https://github.com/googlefonts/fontations/blob/main/docs/codegen-tour.md
//! [NameString]: tables::name::NameString
//! [table-directory]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![deny(rustdoc::broken_intra_doc_links)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(any(feature = "std", test))]
#[macro_use]
extern crate std;
#[cfg(all(not(feature = "std"), not(test)))]
#[macro_use]
extern crate core as std;
pub mod array;
#[cfg(feature = "std")]
pub mod collections;
mod font_data;
mod offset;
mod offset_array;
mod read;
mod table_provider;
mod table_ref;
pub mod tables;
#[cfg(feature = "experimental_traverse")]
pub mod traversal;
#[cfg(any(test, feature = "codegen_test"))]
pub mod codegen_test;
pub use font_data::FontData;
pub use offset::{Offset, ResolveNullableOffset, ResolveOffset};
pub use offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets};
pub use read::{ComputeSize, FontRead, FontReadWithArgs, ReadArgs, ReadError, VarSize};
pub use table_provider::{TableProvider, TopLevelTable};
pub use table_ref::{MinByteRange, TableRef};
/// Public re-export of the font-types crate.
pub extern crate font_types as types;
/// All the types that may be referenced in auto-generated code.
#[doc(hidden)]
pub(crate) mod codegen_prelude {
pub use crate::array::{ComputedArray, VarLenArray};
pub use crate::font_data::{Cursor, FontData};
pub use crate::offset::{Offset, ResolveNullableOffset, ResolveOffset};
pub use crate::offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets};
//pub(crate) use crate::read::sealed;
pub use crate::read::{
ComputeSize, FontRead, FontReadWithArgs, Format, ReadArgs, ReadError, VarSize,
};
pub use crate::table_provider::TopLevelTable;
pub use crate::table_ref::{MinByteRange, TableRef};
pub use std::ops::Range;
pub use types::*;
#[cfg(feature = "experimental_traverse")]
pub use crate::traversal::{self, Field, FieldType, RecordResolver, SomeRecord, SomeTable};
// used in generated traversal code to get type names of offset fields, which
// may include generics
#[cfg(feature = "experimental_traverse")]
pub(crate) fn better_type_name<T>() -> &'static str {
let raw_name = std::any::type_name::<T>();
let last = raw_name.rsplit("::").next().unwrap_or(raw_name);
// this happens if we end up getting a type name like TableRef<'a, module::SomeMarker>
last.trim_end_matches("Marker>")
}
/// named transforms used in 'count', e.g
pub(crate) mod transforms {
pub fn subtract<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
lhs.try_into()
.unwrap_or_default()
.saturating_sub(rhs.try_into().unwrap_or_default())
}
pub fn add<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
lhs.try_into()
.unwrap_or_default()
.saturating_add(rhs.try_into().unwrap_or_default())
}
#[allow(dead_code)]
pub fn bitmap_len<T: TryInto<usize>>(count: T) -> usize {
count.try_into().unwrap_or_default().div_ceil(8)
}
#[cfg(feature = "ift")]
pub fn max_value_bitmap_len<T: TryInto<usize>>(count: T) -> usize {
let count: usize = count.try_into().unwrap_or_default() + 1usize;
count.div_ceil(8)
}
pub fn add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
a: T,
b: U,
c: V,
) -> usize {
a.try_into()
.unwrap_or_default()
.saturating_add(b.try_into().unwrap_or_default())
.saturating_mul(c.try_into().unwrap_or_default())
}
#[cfg(feature = "ift")]
pub fn multiply_add<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
a: T,
b: U,
c: V,
) -> usize {
a.try_into()
.unwrap_or_default()
.saturating_mul(b.try_into().unwrap_or_default())
.saturating_add(c.try_into().unwrap_or_default())
}
pub fn half<T: TryInto<usize>>(val: T) -> usize {
val.try_into().unwrap_or_default() / 2
}
pub fn subtract_add_two<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
lhs.try_into()
.unwrap_or_default()
.saturating_sub(rhs.try_into().unwrap_or_default())
.saturating_add(2)
}
}
}
include!("../generated/font.rs");
#[derive(Clone)]
/// Reference to the content of a font or font collection file.
pub enum FileRef<'a> {
/// A single font.
Font(FontRef<'a>),
/// A collection of fonts.
Collection(CollectionRef<'a>),
}
impl<'a> FileRef<'a> {
/// Creates a new reference to a file representing a font or font collection.
pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
Ok(if let Ok(collection) = CollectionRef::new(data) {
Self::Collection(collection)
} else {
Self::Font(FontRef::new(data)?)
})
}
/// Returns an iterator over the fonts contained in the file.
pub fn fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
let (iter_one, iter_two) = match self {
Self::Font(font) => (Some(Ok(font.clone())), None),
Self::Collection(collection) => (None, Some(collection.iter())),
};
iter_two.into_iter().flatten().chain(iter_one)
}
}
/// Reference to the content of a font collection file.
#[derive(Clone)]
pub struct CollectionRef<'a> {
data: FontData<'a>,
header: TTCHeader<'a>,
}
impl<'a> CollectionRef<'a> {
/// Creates a new reference to a font collection.
pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
let data = FontData::new(data);
let header = TTCHeader::read(data)?;
if header.ttc_tag() != TTC_HEADER_TAG {
Err(ReadError::InvalidTtc(header.ttc_tag()))
} else {
Ok(Self { data, header })
}
}
/// Returns the number of fonts in the collection.
pub fn len(&self) -> u32 {
self.header.num_fonts()
}
/// Returns true if the collection is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the font in the collection at the specified index.
pub fn get(&self, index: u32) -> Result<FontRef<'a>, ReadError> {
let offset = self
.header
.table_directory_offsets()
.get(index as usize)
.ok_or(ReadError::InvalidCollectionIndex(index))?
.get() as usize;
let table_dir_data = self.data.slice(offset..).ok_or(ReadError::OutOfBounds)?;
FontRef::with_table_directory(self.data, TableDirectory::read(table_dir_data)?)
}
/// Returns an iterator over the fonts in the collection.
pub fn iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
let copy = self.clone();
(0..self.len()).map(move |ix| copy.get(ix))
}
}
/// Reference to an in-memory font.
///
/// This is a simple implementation of the [`TableProvider`] trait backed
/// by a borrowed slice containing font data.
#[derive(Clone)]
pub struct FontRef<'a> {
pub data: FontData<'a>,
pub table_directory: TableDirectory<'a>,
}
impl<'a> FontRef<'a> {
/// Creates a new reference to an in-memory font backed by the given data.
///
/// The data must be a single font (not a font collection) and must begin with a
/// [table directory] to be considered valid.
///
/// To load a font from a font collection, use [`FontRef::from_index`] instead.
///
/// [table directory]: https://github.com/googlefonts/fontations/pull/549
pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
let data = FontData::new(data);
Self::with_table_directory(data, TableDirectory::read(data)?)
}
/// Creates a new reference to an in-memory font at the specified index
/// backed by the given data.
///
/// The data slice must begin with either a
/// [table directory](https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory)
/// or a [ttc header](https://learn.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header)
/// to be considered valid.
///
/// In other words, this accepts either font collection (ttc) or single
/// font (ttf/otf) files. If a single font file is provided, the index
/// parameter must be 0.
pub fn from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError> {
let file = FileRef::new(data)?;
match file {
FileRef::Font(font) => {
if index == 0 {
Ok(font)
} else {
Err(ReadError::InvalidCollectionIndex(index))
}
}
FileRef::Collection(collection) => collection.get(index),
}
}
/// Returns the data for the table with the specified tag, if present.
pub fn table_data(&self, tag: Tag) -> Option<FontData<'a>> {
self.table_directory
.table_records()
.binary_search_by(|rec| rec.tag.get().cmp(&tag))
.ok()
.and_then(|idx| self.table_directory.table_records().get(idx))
.and_then(|record| {
let start = Offset32::new(record.offset()).non_null()?;
let len = record.length() as usize;
self.data.slice(start..start.checked_add(len)?)
})
}
fn with_table_directory(
data: FontData<'a>,
table_directory: TableDirectory<'a>,
) -> Result<Self, ReadError> {
if [TT_SFNT_VERSION, CFF_SFNT_VERSION, TRUE_SFNT_VERSION]
.contains(&table_directory.sfnt_version())
{
Ok(FontRef {
data,
table_directory,
})
} else {
Err(ReadError::InvalidSfnt(table_directory.sfnt_version()))
}
}
}
impl<'a> TableProvider<'a> for FontRef<'a> {
fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> {
self.table_data(tag)
}
}
#[cfg(test)]
mod tests {
use font_test_data::{ttc::TTC, AHEM};
use crate::FileRef;
#[test]
fn file_ref_non_collection() {
assert!(matches!(FileRef::new(AHEM), Ok(FileRef::Font(_))));
}
#[test]
fn file_ref_collection() {
let Ok(FileRef::Collection(collection)) = FileRef::new(TTC) else {
panic!("Expected a collection");
};
assert_eq!(2, collection.len());
assert!(!collection.is_empty());
}
}

96
vendor/read-fonts/src/offset.rs vendored Normal file
View File

@@ -0,0 +1,96 @@
//! Handling offsets
use super::read::{FontRead, ReadError};
use crate::{font_data::FontData, read::FontReadWithArgs};
use types::{Nullable, Offset16, Offset24, Offset32};
/// Any offset type.
pub trait Offset: Copy {
fn to_usize(self) -> usize;
fn non_null(self) -> Option<usize> {
match self.to_usize() {
0 => None,
other => Some(other),
}
}
}
macro_rules! impl_offset {
($name:ident, $width:literal) => {
impl Offset for $name {
#[inline]
fn to_usize(self) -> usize {
self.to_u32() as _
}
}
};
}
impl_offset!(Offset16, 2);
impl_offset!(Offset24, 3);
impl_offset!(Offset32, 4);
/// A helper trait providing a 'resolve' method for offset types
pub trait ResolveOffset {
fn resolve<'a, T: FontRead<'a>>(&self, data: FontData<'a>) -> Result<T, ReadError>;
fn resolve_with_args<'a, T: FontReadWithArgs<'a>>(
&self,
data: FontData<'a>,
args: &T::Args,
) -> Result<T, ReadError>;
}
/// A helper trait providing a 'resolve' method for nullable offset types
pub trait ResolveNullableOffset {
fn resolve<'a, T: FontRead<'a>>(&self, data: FontData<'a>) -> Option<Result<T, ReadError>>;
fn resolve_with_args<'a, T: FontReadWithArgs<'a>>(
&self,
data: FontData<'a>,
args: &T::Args,
) -> Option<Result<T, ReadError>>;
}
impl<O: Offset> ResolveNullableOffset for Nullable<O> {
fn resolve<'a, T: FontRead<'a>>(&self, data: FontData<'a>) -> Option<Result<T, ReadError>> {
match self.offset().resolve(data) {
Ok(thing) => Some(Ok(thing)),
Err(ReadError::NullOffset) => None,
Err(e) => Some(Err(e)),
}
}
fn resolve_with_args<'a, T: FontReadWithArgs<'a>>(
&self,
data: FontData<'a>,
args: &T::Args,
) -> Option<Result<T, ReadError>> {
match self.offset().resolve_with_args(data, args) {
Ok(thing) => Some(Ok(thing)),
Err(ReadError::NullOffset) => None,
Err(e) => Some(Err(e)),
}
}
}
impl<O: Offset> ResolveOffset for O {
fn resolve<'a, T: FontRead<'a>>(&self, data: FontData<'a>) -> Result<T, ReadError> {
self.non_null()
.ok_or(ReadError::NullOffset)
.and_then(|off| data.split_off(off).ok_or(ReadError::OutOfBounds))
.and_then(T::read)
}
fn resolve_with_args<'a, T: FontReadWithArgs<'a>>(
&self,
data: FontData<'a>,
args: &T::Args,
) -> Result<T, ReadError> {
self.non_null()
.ok_or(ReadError::NullOffset)
.and_then(|off| data.split_off(off).ok_or(ReadError::OutOfBounds))
.and_then(|data| T::read_with_args(data, args))
}
}

148
vendor/read-fonts/src/offset_array.rs vendored Normal file
View File

@@ -0,0 +1,148 @@
//! Arrays of offsets with dynamic resolution
//!
//! This module provides a number of types that wrap arrays of offsets, dynamically
//! resolving individual offsets as they are accessed.
use crate::offset::ResolveNullableOffset;
use font_types::{BigEndian, Nullable, Offset16, Scalar};
use crate::{FontData, FontReadWithArgs, Offset, ReadArgs, ReadError, ResolveOffset};
/// An array of offsets that can be resolved on access.
///
/// This bundles up the raw offsets with the data used to resolve them, along
/// with any arguments needed to resolve those offsets; it provides a simple
/// ergonomic interface that unburdens the user from needing to manually
/// determine the appropriate input data and arguments for a raw offset.
#[derive(Clone)]
pub struct ArrayOfOffsets<'a, T: ReadArgs, O: Scalar = Offset16> {
offsets: &'a [BigEndian<O>],
data: FontData<'a>,
args: T::Args,
}
/// An array of nullable offsets that can be resolved on access.
///
/// This is identical to [`ArrayOfOffsets`], except that each offset is
/// allowed to be null.
#[derive(Clone)]
pub struct ArrayOfNullableOffsets<'a, T: ReadArgs, O: Scalar = Offset16> {
offsets: &'a [BigEndian<Nullable<O>>],
data: FontData<'a>,
args: T::Args,
}
impl<'a, T, O> ArrayOfOffsets<'a, T, O>
where
O: Scalar,
T: ReadArgs,
{
pub(crate) fn new(offsets: &'a [BigEndian<O>], data: FontData<'a>, args: T::Args) -> Self {
Self {
offsets,
data,
args,
}
}
}
impl<'a, T, O> ArrayOfOffsets<'a, T, O>
where
O: Scalar + Offset,
T: ReadArgs + FontReadWithArgs<'a>,
T::Args: Copy + 'static,
{
/// The number of offsets in the array
pub fn len(&self) -> usize {
self.offsets.len()
}
/// `true` if the array is empty
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
/// Resolve the offset at the provided index.
///
/// Note: if the index is invalid this will return the `InvalidCollectionIndex`
/// error variant instead of `None`.
pub fn get(&self, idx: usize) -> Result<T, ReadError> {
self.offsets
.get(idx)
.ok_or(ReadError::InvalidCollectionIndex(idx as _))
.and_then(|o| o.get().resolve_with_args(self.data, &self.args))
}
/// Iterate over all of the offset targets.
///
/// Each offset will be resolved as it is encountered.
pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
let mut iter = self.offsets.iter();
let args = self.args;
let data = self.data;
std::iter::from_fn(move || {
iter.next()
.map(|off| off.get().resolve_with_args(data, &args))
})
}
}
impl<'a, T, O> ArrayOfNullableOffsets<'a, T, O>
where
O: Scalar + Offset,
T: ReadArgs,
{
pub(crate) fn new(
offsets: &'a [BigEndian<Nullable<O>>],
data: FontData<'a>,
args: T::Args,
) -> Self {
Self {
offsets,
data,
args,
}
}
}
impl<'a, T, O> ArrayOfNullableOffsets<'a, T, O>
where
O: Scalar + Offset,
T: ReadArgs + FontReadWithArgs<'a>,
T::Args: Copy + 'static,
{
/// The number of offsets in the array
pub fn len(&self) -> usize {
self.offsets.len()
}
/// `true` if the array is empty
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
/// Resolve the offset at the provided index.
///
/// This will return `None` only if the offset *exists*, but is null. if the
/// provided index does not exist, this will return the `InvalidCollectionIndex`
/// error variant.
pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
let Some(offset) = self.offsets.get(idx) else {
return Some(Err(ReadError::InvalidCollectionIndex(idx as _)));
};
offset.get().resolve_with_args(self.data, &self.args)
}
/// Iterate over all of the offset targets.
///
/// Each offset will be resolved as it is encountered.
pub fn iter(&self) -> impl Iterator<Item = Option<Result<T, ReadError>>> + 'a {
let mut iter = self.offsets.iter();
let args = self.args;
let data = self.data;
std::iter::from_fn(move || {
iter.next()
.map(|off| off.get().resolve_with_args(data, &args))
})
}
}

150
vendor/read-fonts/src/read.rs vendored Normal file
View File

@@ -0,0 +1,150 @@
//! Traits for interpreting font data
#![deny(clippy::arithmetic_side_effects)]
use types::{FixedSize, Scalar, Tag};
use crate::font_data::FontData;
/// A type that can be read from raw table data.
///
/// This trait is implemented for all font tables that are self-describing: that
/// is, tables that do not require any external state in order to interpret their
/// underlying bytes. (Tables that require external state implement
/// [`FontReadWithArgs`] instead)
pub trait FontRead<'a>: Sized {
/// Read an instance of `Self` from the provided data, performing validation.
///
/// In the case of a table, this method is responsible for ensuring the input
/// data is consistent: this means ensuring that any versioned fields are
/// present as required by the version, and that any array lengths are not
/// out-of-bounds.
fn read(data: FontData<'a>) -> Result<Self, ReadError>;
}
//NOTE: this is separate so that it can be a super trait of FontReadWithArgs and
//ComputeSize, without them needing to know about each other? I'm not sure this
//is necessary, but I don't know the full hierarchy of traits I'm going to need
//yet, so this seems... okay?
/// A trait for a type that needs additional arguments to be read.
pub trait ReadArgs {
type Args: Copy;
}
/// A trait for types that require external data in order to be constructed.
///
/// You should not need to use this directly; it is intended to be used from
/// generated code. Any type that requires external arguments also has a custom
/// `read` constructor where you can pass those arguments like normal.
pub trait FontReadWithArgs<'a>: Sized + ReadArgs {
/// read an item, using the provided args.
///
/// If successful, returns a new item of this type, and the number of bytes
/// used to construct it.
///
/// If a type requires multiple arguments, they will be passed as a tuple.
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError>;
}
// a blanket impl of ReadArgs/FontReadWithArgs for general FontRead types.
//
// This is used by ArrayOfOffsets/ArrayOfNullableOffsets to provide a common
// interface for regardless of whether a type has args.
impl<'a, T: FontRead<'a>> ReadArgs for T {
type Args = ();
}
impl<'a, T: FontRead<'a>> FontReadWithArgs<'a> for T {
fn read_with_args(data: FontData<'a>, _: &Self::Args) -> Result<Self, ReadError> {
Self::read(data)
}
}
/// A trait for tables that have multiple possible formats.
pub trait Format<T> {
/// The format value for this table.
const FORMAT: T;
}
/// A type that can compute its size at runtime, based on some input.
///
/// For types with a constant size, see [`FixedSize`] and
/// for types which store their size inline, see [`VarSize`].
pub trait ComputeSize: ReadArgs {
/// Compute the number of bytes required to represent this type.
fn compute_size(args: &Self::Args) -> Result<usize, ReadError>;
}
/// A trait for types that have variable length.
///
/// As a rule, these types have an initial length field.
///
/// For types with a constant size, see [`FixedSize`] and
/// for types which can pre-compute their size, see [`ComputeSize`].
pub trait VarSize {
/// The type of the first (length) field of the item.
///
/// When reading this type, we will read this value first, and use it to
/// determine the total length.
type Size: Scalar + Into<u32>;
#[doc(hidden)]
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
let asu32 = data.read_at::<Self::Size>(pos).ok()?.into();
(asu32 as usize).checked_add(Self::Size::RAW_BYTE_LEN)
}
/// Determine the total length required to store `count` items of `Self` in
/// `data` starting from `start`.
#[doc(hidden)]
fn total_len_for_count(data: FontData, count: usize) -> Result<usize, ReadError> {
(0..count).try_fold(0usize, |current_pos, _i| {
Self::read_len_at(data, current_pos)
.and_then(|i_len| current_pos.checked_add(i_len))
.ok_or(ReadError::OutOfBounds)
})
}
}
/// An error that occurs when reading font data
#[derive(Debug, Clone, PartialEq)]
pub enum ReadError {
OutOfBounds,
// i64 is flexible enough to store any value we might encounter
InvalidFormat(i64),
InvalidSfnt(u32),
InvalidTtc(Tag),
InvalidCollectionIndex(u32),
InvalidArrayLen,
ValidationError,
NullOffset,
TableIsMissing(Tag),
MetricIsMissing(Tag),
MalformedData(&'static str),
}
impl std::fmt::Display for ReadError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ReadError::OutOfBounds => write!(f, "An offset was out of bounds"),
ReadError::InvalidFormat(x) => write!(f, "Invalid format '{x}'"),
ReadError::InvalidSfnt(ver) => write!(f, "Invalid sfnt version 0x{ver:08X}"),
ReadError::InvalidTtc(tag) => write!(f, "Invalid ttc tag {tag}"),
ReadError::InvalidCollectionIndex(ix) => {
write!(f, "Invalid index {ix} for font collection")
}
ReadError::InvalidArrayLen => {
write!(f, "Specified array length not a multiple of item size")
}
ReadError::ValidationError => write!(f, "A validation error occurred"),
ReadError::NullOffset => write!(f, "An offset was unexpectedly null"),
ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
ReadError::MetricIsMissing(tag) => write!(f, "the {tag} metric is missing"),
ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ReadError {}

301
vendor/read-fonts/src/table_provider.rs vendored Normal file
View File

@@ -0,0 +1,301 @@
//! a trait for things that can serve font tables
use types::{BigEndian, Tag};
use crate::{tables, FontData, FontRead, ReadError};
/// A table that has an associated tag.
///
/// This is true of top-level tables, but not their various subtables.
pub trait TopLevelTable {
/// The table's tag.
const TAG: Tag;
}
/// An interface for accessing tables from a font (or font-like object)
pub trait TableProvider<'a> {
fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>>;
fn expect_data_for_tag(&self, tag: Tag) -> Result<FontData<'a>, ReadError> {
self.data_for_tag(tag).ok_or(ReadError::TableIsMissing(tag))
}
fn expect_table<T: TopLevelTable + FontRead<'a>>(&self) -> Result<T, ReadError> {
self.expect_data_for_tag(T::TAG).and_then(FontRead::read)
}
fn head(&self) -> Result<tables::head::Head<'a>, ReadError> {
self.expect_table()
}
fn name(&self) -> Result<tables::name::Name<'a>, ReadError> {
self.expect_table()
}
fn hhea(&self) -> Result<tables::hhea::Hhea<'a>, ReadError> {
self.expect_table()
}
fn vhea(&self) -> Result<tables::vhea::Vhea<'a>, ReadError> {
self.expect_table()
}
fn hmtx(&self) -> Result<tables::hmtx::Hmtx<'a>, ReadError> {
//FIXME: should we make the user pass these in?
let num_glyphs = self.maxp().map(|maxp| maxp.num_glyphs())?;
let number_of_h_metrics = self.hhea().map(|hhea| hhea.number_of_h_metrics())?;
let data = self.expect_data_for_tag(tables::hmtx::Hmtx::TAG)?;
tables::hmtx::Hmtx::read(data, number_of_h_metrics, num_glyphs)
}
fn hdmx(&self) -> Result<tables::hdmx::Hdmx<'a>, ReadError> {
let num_glyphs = self.maxp().map(|maxp| maxp.num_glyphs())?;
let data = self.expect_data_for_tag(tables::hdmx::Hdmx::TAG)?;
tables::hdmx::Hdmx::read(data, num_glyphs)
}
fn vmtx(&self) -> Result<tables::vmtx::Vmtx<'a>, ReadError> {
//FIXME: should we make the user pass these in?
let num_glyphs = self.maxp().map(|maxp| maxp.num_glyphs())?;
let number_of_v_metrics = self.vhea().map(|vhea| vhea.number_of_long_ver_metrics())?;
let data = self.expect_data_for_tag(tables::vmtx::Vmtx::TAG)?;
tables::vmtx::Vmtx::read(data, number_of_v_metrics, num_glyphs)
}
fn vorg(&self) -> Result<tables::vorg::Vorg<'a>, ReadError> {
self.expect_table()
}
fn fvar(&self) -> Result<tables::fvar::Fvar<'a>, ReadError> {
self.expect_table()
}
fn avar(&self) -> Result<tables::avar::Avar<'a>, ReadError> {
self.expect_table()
}
fn hvar(&self) -> Result<tables::hvar::Hvar<'a>, ReadError> {
self.expect_table()
}
fn vvar(&self) -> Result<tables::vvar::Vvar<'a>, ReadError> {
self.expect_table()
}
fn mvar(&self) -> Result<tables::mvar::Mvar<'a>, ReadError> {
self.expect_table()
}
fn maxp(&self) -> Result<tables::maxp::Maxp<'a>, ReadError> {
self.expect_table()
}
fn os2(&self) -> Result<tables::os2::Os2<'a>, ReadError> {
self.expect_table()
}
fn post(&self) -> Result<tables::post::Post<'a>, ReadError> {
self.expect_table()
}
fn gasp(&self) -> Result<tables::gasp::Gasp<'a>, ReadError> {
self.expect_table()
}
/// is_long can be optionally provided, if known, otherwise we look it up in head.
fn loca(&self, is_long: impl Into<Option<bool>>) -> Result<tables::loca::Loca<'a>, ReadError> {
let is_long = match is_long.into() {
Some(val) => val,
None => self.head()?.index_to_loc_format() == 1,
};
let data = self.expect_data_for_tag(tables::loca::Loca::TAG)?;
tables::loca::Loca::read(data, is_long)
}
fn glyf(&self) -> Result<tables::glyf::Glyf<'a>, ReadError> {
self.expect_table()
}
fn gvar(&self) -> Result<tables::gvar::Gvar<'a>, ReadError> {
self.expect_table()
}
/// Returns the array of entries for the control value table which is used
/// for TrueType hinting.
fn cvt(&self) -> Result<&'a [BigEndian<i16>], ReadError> {
let table_data = self.expect_data_for_tag(Tag::new(b"cvt "))?;
table_data.read_array(0..table_data.len())
}
fn cvar(&self) -> Result<tables::cvar::Cvar<'a>, ReadError> {
self.expect_table()
}
fn cff(&self) -> Result<tables::cff::Cff<'a>, ReadError> {
self.expect_table()
}
fn cff2(&self) -> Result<tables::cff2::Cff2<'a>, ReadError> {
self.expect_table()
}
fn cmap(&self) -> Result<tables::cmap::Cmap<'a>, ReadError> {
self.expect_table()
}
fn gdef(&self) -> Result<tables::gdef::Gdef<'a>, ReadError> {
self.expect_table()
}
fn gpos(&self) -> Result<tables::gpos::Gpos<'a>, ReadError> {
self.expect_table()
}
fn gsub(&self) -> Result<tables::gsub::Gsub<'a>, ReadError> {
self.expect_table()
}
fn feat(&self) -> Result<tables::feat::Feat<'a>, ReadError> {
self.expect_table()
}
fn ltag(&self) -> Result<tables::ltag::Ltag<'a>, ReadError> {
self.expect_table()
}
fn ankr(&self) -> Result<tables::ankr::Ankr<'a>, ReadError> {
self.expect_table()
}
fn trak(&self) -> Result<tables::trak::Trak<'a>, ReadError> {
self.expect_table()
}
fn morx(&self) -> Result<tables::morx::Morx<'a>, ReadError> {
self.expect_table()
}
fn kerx(&self) -> Result<tables::kerx::Kerx<'a>, ReadError> {
self.expect_table()
}
fn kern(&self) -> Result<tables::kern::Kern<'a>, ReadError> {
self.expect_table()
}
fn colr(&self) -> Result<tables::colr::Colr<'a>, ReadError> {
self.expect_table()
}
fn cpal(&self) -> Result<tables::cpal::Cpal<'a>, ReadError> {
self.expect_table()
}
fn cblc(&self) -> Result<tables::cblc::Cblc<'a>, ReadError> {
self.expect_table()
}
fn cbdt(&self) -> Result<tables::cbdt::Cbdt<'a>, ReadError> {
self.expect_table()
}
fn eblc(&self) -> Result<tables::eblc::Eblc<'a>, ReadError> {
self.expect_table()
}
fn ebdt(&self) -> Result<tables::ebdt::Ebdt<'a>, ReadError> {
self.expect_table()
}
fn sbix(&self) -> Result<tables::sbix::Sbix<'a>, ReadError> {
// should we make the user pass this in?
let num_glyphs = self.maxp().map(|maxp| maxp.num_glyphs())?;
let data = self.expect_data_for_tag(tables::sbix::Sbix::TAG)?;
tables::sbix::Sbix::read(data, num_glyphs)
}
fn stat(&self) -> Result<tables::stat::Stat<'a>, ReadError> {
self.expect_table()
}
fn svg(&self) -> Result<tables::svg::Svg<'a>, ReadError> {
self.expect_table()
}
fn varc(&self) -> Result<tables::varc::Varc<'a>, ReadError> {
self.expect_table()
}
#[cfg(feature = "ift")]
fn ift(&self) -> Result<tables::ift::Ift<'a>, ReadError> {
self.expect_data_for_tag(tables::ift::IFT_TAG)
.and_then(FontRead::read)
}
#[cfg(feature = "ift")]
fn iftx(&self) -> Result<tables::ift::Ift<'a>, ReadError> {
self.expect_data_for_tag(tables::ift::IFTX_TAG)
.and_then(FontRead::read)
}
fn meta(&self) -> Result<tables::meta::Meta<'a>, ReadError> {
self.expect_table()
}
fn base(&self) -> Result<tables::base::Base<'a>, ReadError> {
self.expect_table()
}
}
#[cfg(test)]
mod tests {
use super::*;
/// https://github.com/googlefonts/fontations/issues/105
#[test]
fn bug_105() {
// serve some dummy versions of the tables used to compute hmtx. The only
// fields that matter are maxp::num_glyphs and hhea::number_of_h_metrics,
// everything else is zero'd out
struct DummyProvider;
impl TableProvider<'static> for DummyProvider {
fn data_for_tag(&self, tag: Tag) -> Option<FontData<'static>> {
if tag == Tag::new(b"maxp") {
Some(FontData::new(&[
0, 0, 0x50, 0, // version 0.5
0, 3, // num_glyphs = 3
]))
} else if tag == Tag::new(b"hhea") {
Some(FontData::new(&[
0, 1, 0, 0, // version 1.0
0, 0, 0, 0, // ascender/descender
0, 0, 0, 0, // line gap/advance width
0, 0, 0, 0, // min left/right side bearing
0, 0, 0, 0, // x_max, caret_slope_rise
0, 0, 0, 0, // caret_slope_run, caret_offset
0, 0, 0, 0, // reserved1/2
0, 0, 0, 0, // reserved 3/4
0, 0, 0, 1, // metric format, number_of_h_metrics
]))
} else if tag == Tag::new(b"hmtx") {
Some(FontData::new(&[
0, 4, 0, 6, // LongHorMetric: 4, 6
0, 30, 0, 111, // two lsb entries
]))
} else {
None
}
}
}
let number_of_h_metrics = DummyProvider.hhea().unwrap().number_of_h_metrics();
let num_glyphs = DummyProvider.maxp().unwrap().num_glyphs();
let hmtx = DummyProvider.hmtx().unwrap();
assert_eq!(number_of_h_metrics, 1);
assert_eq!(num_glyphs, 3);
assert_eq!(hmtx.h_metrics().len(), 1);
assert_eq!(hmtx.left_side_bearings().len(), 2);
}
}

64
vendor/read-fonts/src/table_ref.rs vendored Normal file
View File

@@ -0,0 +1,64 @@
//! Typed font tables
use super::read::{FontRead, Format, ReadError};
use crate::{
font_data::FontData,
offset::{Offset, ResolveOffset},
};
use std::ops::Range;
/// Return the minimum range of the table bytes
///
/// This trait is implemented in generated code, and we use this to get the minimum length/bytes of a table
pub trait MinByteRange {
fn min_byte_range(&self) -> Range<usize>;
}
#[derive(Clone)]
/// Typed access to raw table data.
pub struct TableRef<'a, T> {
pub(crate) shape: T,
pub(crate) data: FontData<'a>,
}
impl<'a, T> TableRef<'a, T> {
/// Resolve the provided offset from the start of this table.
pub fn resolve_offset<O: Offset, R: FontRead<'a>>(&self, offset: O) -> Result<R, ReadError> {
offset.resolve(self.data)
}
/// Return a reference to this table's raw data.
///
/// We use this in the compile crate to resolve offsets.
pub fn offset_data(&self) -> FontData<'a> {
self.data
}
/// Return a reference to the table's 'Shape' struct.
///
/// This is a low level implementation detail, but it can be useful in
/// some cases where you want to know things about a table's layout, such
/// as the byte offsets of specific fields.
pub fn shape(&self) -> &T {
&self.shape
}
}
// a blanket impl so that the format is available through a TableRef
impl<U, T: Format<U>> Format<U> for TableRef<'_, T> {
const FORMAT: U = T::FORMAT;
}
impl<'a, T: MinByteRange> TableRef<'a, T> {
/// Return the minimum byte range of this table
pub fn min_byte_range(&self) -> Range<usize> {
self.shape.min_byte_range()
}
/// Return the minimum bytes of this table
pub fn min_table_bytes(&self) -> &'a [u8] {
self.offset_data()
.as_bytes()
.get(self.shape.min_byte_range())
.unwrap_or_default()
}
}

79
vendor/read-fonts/src/tables.rs vendored Normal file
View File

@@ -0,0 +1,79 @@
//! The various font tables
pub mod aat;
pub mod ankr;
pub mod avar;
pub mod base;
pub mod bitmap;
pub mod cbdt;
pub mod cblc;
pub mod cff;
pub mod cff2;
pub mod cmap;
pub mod colr;
pub mod cpal;
pub mod cvar;
pub mod ebdt;
pub mod eblc;
pub mod feat;
pub mod fvar;
pub mod gasp;
pub mod gdef;
pub mod glyf;
pub mod gpos;
pub mod gsub;
pub mod gvar;
pub mod hdmx;
pub mod head;
pub mod hhea;
pub mod hmtx;
pub mod hvar;
pub mod kern;
pub mod kerx;
pub mod layout;
pub mod loca;
pub mod ltag;
pub mod maxp;
pub mod meta;
pub mod morx;
pub mod mvar;
pub mod name;
pub mod os2;
pub mod post;
pub mod postscript;
pub mod sbix;
pub mod stat;
pub mod svg;
pub mod trak;
pub mod varc;
pub mod variations;
pub mod vhea;
pub mod vmtx;
pub mod vorg;
pub mod vvar;
#[cfg(feature = "ift")]
pub mod ift;
/// Computes the table checksum for the given data.
///
/// See the OpenType [specification](https://learn.microsoft.com/en-us/typography/opentype/spec/otff#calculating-checksums)
/// for details.
pub fn compute_checksum(table: &[u8]) -> u32 {
let mut sum = 0u32;
let mut iter = table.chunks_exact(4);
for quad in &mut iter {
// this can't fail, and we trust the compiler to avoid a branch
let array: [u8; 4] = quad.try_into().unwrap_or_default();
sum = sum.wrapping_add(u32::from_be_bytes(array));
}
let rem = match *iter.remainder() {
[a] => u32::from_be_bytes([a, 0, 0, 0]),
[a, b] => u32::from_be_bytes([a, b, 0, 0]),
[a, b, c] => u32::from_be_bytes([a, b, c, 0]),
_ => 0,
};
sum.wrapping_add(rem)
}

805
vendor/read-fonts/src/tables/aat.rs vendored Normal file
View File

@@ -0,0 +1,805 @@
//! Apple Advanced Typography common tables.
//!
//! See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html>
include!("../../generated/generated_aat.rs");
/// Predefined classes.
///
/// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html>
pub mod class {
pub const END_OF_TEXT: u8 = 0;
pub const OUT_OF_BOUNDS: u8 = 1;
pub const DELETED_GLYPH: u8 = 2;
}
impl Lookup0<'_> {
pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
let data = self.values_data();
let data_len = data.len();
let n_elems = data_len / T::RAW_BYTE_LEN;
let len_in_bytes = n_elems * T::RAW_BYTE_LEN;
FontData::new(&data[..len_in_bytes])
.cursor()
.read_array::<BigEndian<T>>(n_elems)?
.get(index as usize)
.map(|val| val.get())
.ok_or(ReadError::OutOfBounds)
}
}
/// Lookup segment for format 2.
#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
#[repr(C, packed)]
pub struct LookupSegment2<T>
where
T: LookupValue,
{
/// Last glyph index in this segment.
pub last_glyph: BigEndian<u16>,
/// First glyph index in this segment.
pub first_glyph: BigEndian<u16>,
/// The lookup value.
pub value: BigEndian<T>,
}
/// Note: this requires `LookupSegment2` to be `repr(packed)`.
impl<T: LookupValue> FixedSize for LookupSegment2<T> {
const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>();
}
impl Lookup2<'_> {
pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
let segments = self.segments::<T>()?;
let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {
Ok(ix) => ix,
Err(ix) => ix.saturating_sub(1),
};
let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?;
if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) {
let value = segment.value;
return Ok(value.get());
}
Err(ReadError::OutOfBounds)
}
fn segments<T: LookupValue>(&self) -> Result<&[LookupSegment2<T>], ReadError> {
FontData::new(self.segments_data())
.cursor()
.read_array(self.n_units() as usize)
}
}
impl Lookup4<'_> {
pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
let segments = self.segments();
let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {
Ok(ix) => ix,
Err(ix) => ix.saturating_sub(1),
};
let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?;
if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) {
let base_offset = segment.value_offset() as usize;
let offset = base_offset
+ index
.checked_sub(segment.first_glyph())
.ok_or(ReadError::OutOfBounds)? as usize
* T::RAW_BYTE_LEN;
return self.offset_data().read_at(offset);
}
Err(ReadError::OutOfBounds)
}
}
/// Lookup single record for format 6.
#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
#[repr(C, packed)]
pub struct LookupSingle<T>
where
T: LookupValue,
{
/// The glyph index.
pub glyph: BigEndian<u16>,
/// The lookup value.
pub value: BigEndian<T>,
}
/// Note: this requires `LookupSingle` to be `repr(packed)`.
impl<T: LookupValue> FixedSize for LookupSingle<T> {
const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>();
}
impl Lookup6<'_> {
pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
let entries = self.entries::<T>()?;
if let Ok(ix) = entries.binary_search_by_key(&index, |entry| entry.glyph.get()) {
let entry = &entries[ix];
let value = entry.value;
return Ok(value.get());
}
Err(ReadError::OutOfBounds)
}
fn entries<T: LookupValue>(&self) -> Result<&[LookupSingle<T>], ReadError> {
FontData::new(self.entries_data())
.cursor()
.read_array(self.n_units() as usize)
}
}
impl Lookup8<'_> {
pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
index
.checked_sub(self.first_glyph())
.and_then(|ix| {
self.value_array()
.get(ix as usize)
.map(|val| T::from_u16(val.get()))
})
.ok_or(ReadError::OutOfBounds)
}
}
impl Lookup10<'_> {
pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
let ix = index
.checked_sub(self.first_glyph())
.ok_or(ReadError::OutOfBounds)? as usize;
let unit_size = self.unit_size() as usize;
let offset = ix * unit_size;
let mut cursor = FontData::new(self.values_data()).cursor();
cursor.advance_by(offset);
let val = match unit_size {
1 => cursor.read::<u8>()? as u32,
2 => cursor.read::<u16>()? as u32,
4 => cursor.read::<u32>()?,
_ => {
return Err(ReadError::MalformedData(
"invalid unit_size in format 10 AAT lookup table",
))
}
};
Ok(T::from_u32(val))
}
}
impl Lookup<'_> {
pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
match self {
Lookup::Format0(lookup) => lookup.value::<T>(index),
Lookup::Format2(lookup) => lookup.value::<T>(index),
Lookup::Format4(lookup) => lookup.value::<T>(index),
Lookup::Format6(lookup) => lookup.value::<T>(index),
Lookup::Format8(lookup) => lookup.value::<T>(index),
Lookup::Format10(lookup) => lookup.value::<T>(index),
}
}
}
#[derive(Clone)]
pub struct TypedLookup<'a, T> {
lookup: Lookup<'a>,
_marker: std::marker::PhantomData<fn() -> T>,
}
impl<T: LookupValue> TypedLookup<'_, T> {
/// Returns the value associated with the given index.
pub fn value(&self, index: u16) -> Result<T, ReadError> {
self.lookup.value::<T>(index)
}
}
impl<'a, T> FontRead<'a> for TypedLookup<'a, T> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
Ok(Self {
lookup: Lookup::read(data)?,
_marker: std::marker::PhantomData,
})
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a, T> SomeTable<'a> for TypedLookup<'a, T> {
fn type_name(&self) -> &str {
"TypedLookup"
}
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
self.lookup.get_field(idx)
}
}
/// Trait for values that can be read from lookup tables.
pub trait LookupValue: Copy + Scalar + bytemuck::AnyBitPattern {
fn from_u16(v: u16) -> Self;
fn from_u32(v: u32) -> Self;
}
impl LookupValue for u16 {
fn from_u16(v: u16) -> Self {
v
}
fn from_u32(v: u32) -> Self {
// intentionally truncates
v as _
}
}
impl LookupValue for u32 {
fn from_u16(v: u16) -> Self {
v as _
}
fn from_u32(v: u32) -> Self {
v
}
}
impl LookupValue for GlyphId16 {
fn from_u16(v: u16) -> Self {
GlyphId16::from(v)
}
fn from_u32(v: u32) -> Self {
// intentionally truncates
GlyphId16::from(v as u16)
}
}
pub type LookupU16<'a> = TypedLookup<'a, u16>;
pub type LookupU32<'a> = TypedLookup<'a, u32>;
pub type LookupGlyphId<'a> = TypedLookup<'a, GlyphId16>;
/// Empty data type for a state table entry with no payload.
///
/// Note: this type is only intended for use as the type parameter for
/// `StateEntry`. The inner field is private and this type cannot be
/// constructed outside of this module.
#[derive(Copy, Clone, bytemuck::AnyBitPattern, Debug)]
pub struct NoPayload(());
impl FixedSize for NoPayload {
const RAW_BYTE_LEN: usize = 0;
}
/// Entry in an (extended) state table.
#[derive(Clone, Debug)]
pub struct StateEntry<T = NoPayload> {
/// Index of the next state.
pub new_state: u16,
/// Flag values are table specific.
pub flags: u16,
/// Payload is table specific.
pub payload: T,
}
impl<'a, T: bytemuck::AnyBitPattern + FixedSize> FontRead<'a> for StateEntry<T> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let mut cursor = data.cursor();
let new_state = cursor.read()?;
let flags = cursor.read()?;
let remaining = cursor.remaining().ok_or(ReadError::OutOfBounds)?;
let payload = *remaining.read_ref_at(0)?;
Ok(Self {
new_state,
flags,
payload,
})
}
}
impl<T> FixedSize for StateEntry<T>
where
T: FixedSize,
{
// Two u16 fields + payload
const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + T::RAW_BYTE_LEN;
}
/// Table for driving a finite state machine for layout.
///
/// The input to the state machine consists of the current state
/// and a glyph class. The output is an [entry](StateEntry) containing
/// the next state and a payload that is dependent on the type of
/// layout action being performed.
///
/// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#StateHeader>
/// for more detail.
#[derive(Clone)]
pub struct StateTable<'a> {
header: StateHeader<'a>,
}
impl StateTable<'_> {
pub const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 4;
/// Returns the class table entry for the given glyph identifier.
pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> {
let glyph_id = glyph_id.to_u16();
if glyph_id == 0xFFFF {
return Ok(class::DELETED_GLYPH);
}
let class_table = self.header.class_table()?;
glyph_id
.checked_sub(class_table.first_glyph())
.and_then(|ix| class_table.class_array().get(ix as usize).copied())
.ok_or(ReadError::OutOfBounds)
}
/// Returns the entry for the given state and class.
pub fn entry(&self, state: u16, class: u8) -> Result<StateEntry, ReadError> {
// Each state has a 1-byte entry per class so state_size == n_classes
let n_classes = self.header.state_size() as usize;
if n_classes == 0 {
// Avoid potential divide by zero below
return Err(ReadError::MalformedData("empty AAT state table"));
}
let mut class = class as usize;
if class >= n_classes {
class = class::OUT_OF_BOUNDS as usize;
}
let state_array = self.header.state_array()?.data();
let entry_ix = state_array
.get(
(state as usize)
.checked_mul(n_classes)
.ok_or(ReadError::OutOfBounds)?
+ class,
)
.copied()
.ok_or(ReadError::OutOfBounds)? as usize;
let entry_offset = entry_ix * 4;
let entry_data = self
.header
.entry_table()?
.data()
.get(entry_offset..)
.ok_or(ReadError::OutOfBounds)?;
let mut entry = StateEntry::read(FontData::new(entry_data))?;
// For legacy state tables, the newState is a byte offset into
// the state array. Convert this to an index for consistency.
let new_state = (entry.new_state as i32)
.checked_sub(self.header.state_array_offset().to_u32() as i32)
.ok_or(ReadError::OutOfBounds)?
/ n_classes as i32;
entry.new_state = new_state.try_into().map_err(|_| ReadError::OutOfBounds)?;
Ok(entry)
}
/// Reads scalar values that are referenced from state table entries.
pub fn read_value<T: Scalar>(&self, offset: usize) -> Result<T, ReadError> {
self.header.offset_data().read_at::<T>(offset)
}
}
impl<'a> FontRead<'a> for StateTable<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
Ok(Self {
header: StateHeader::read(data)?,
})
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeTable<'a> for StateTable<'a> {
fn type_name(&self) -> &str {
"StateTable"
}
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
self.header.get_field(idx)
}
}
#[derive(Clone)]
pub struct ExtendedStateTable<'a, T = NoPayload> {
n_classes: usize,
class_table: LookupU16<'a>,
state_array: &'a [BigEndian<u16>],
entry_table: &'a [u8],
_marker: std::marker::PhantomData<fn() -> T>,
}
impl<T> ExtendedStateTable<'_, T> {
pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 4;
}
/// Table for driving a finite state machine for layout.
///
/// The input to the state machine consists of the current state
/// and a glyph class. The output is an [entry](StateEntry) containing
/// the next state and a payload that is dependent on the type of
/// layout action being performed.
///
/// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#StateHeader>
/// for more detail.
impl<T> ExtendedStateTable<'_, T>
where
T: FixedSize + bytemuck::AnyBitPattern,
{
/// Returns the class table entry for the given glyph identifier.
pub fn class(&self, glyph_id: GlyphId) -> Result<u16, ReadError> {
let glyph_id: u16 = glyph_id
.to_u32()
.try_into()
.map_err(|_| ReadError::OutOfBounds)?;
if glyph_id == 0xFFFF {
return Ok(class::DELETED_GLYPH as u16);
}
self.class_table.value(glyph_id)
}
/// Returns the entry for the given state and class.
pub fn entry(&self, state: u16, class: u16) -> Result<StateEntry<T>, ReadError> {
let mut class = class as usize;
if class >= self.n_classes {
class = class::OUT_OF_BOUNDS as usize;
}
let state_ix = state as usize * self.n_classes + class;
let entry_ix = self
.state_array
.get(state_ix)
.copied()
.ok_or(ReadError::OutOfBounds)?
.get() as usize;
let entry_offset = entry_ix * StateEntry::<T>::RAW_BYTE_LEN;
let entry_data = self
.entry_table
.get(entry_offset..)
.ok_or(ReadError::OutOfBounds)?;
StateEntry::read(FontData::new(entry_data))
}
}
impl<'a, T> FontRead<'a> for ExtendedStateTable<'a, T> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let header = StxHeader::read(data)?;
let n_classes = header.n_classes() as usize;
let class_table = header.class_table()?;
let state_array = header.state_array()?.data();
let entry_table = header.entry_table()?.data();
Ok(Self {
n_classes,
class_table,
state_array,
entry_table,
_marker: std::marker::PhantomData,
})
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a, T> SomeTable<'a> for ExtendedStateTable<'a, T> {
fn type_name(&self) -> &str {
"ExtendedStateTable"
}
fn get_field(&self, _idx: usize) -> Option<Field<'a>> {
None
}
}
/// Reads an array of T from the given FontData, ensuring that the byte length
/// is a multiple of the size of T.
///
/// Many of the `morx` subtables have arrays without associated lengths so we
/// simply read to the end of the available data. The `FontData::read_array`
/// method will fail if the byte range provided is not exact so this helper
/// allows us to force the lengths to an acceptable value.
pub(crate) fn safe_read_array_to_end<'a, T: bytemuck::AnyBitPattern + FixedSize>(
data: &FontData<'a>,
offset: usize,
) -> Result<&'a [T], ReadError> {
let len = data
.len()
.checked_sub(offset)
.ok_or(ReadError::OutOfBounds)?;
let end = offset + len / T::RAW_BYTE_LEN * T::RAW_BYTE_LEN;
data.read_array(offset..end)
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn lookup_format_0() {
#[rustfmt::skip]
let words = [
0_u16, // format
0, 2, 4, 6, 8, 10, 12, 14, 16, // maps all glyphs to gid * 2
];
let mut buf = BeBuffer::new();
buf = buf.extend(words);
let lookup = LookupU16::read(buf.data().into()).unwrap();
for gid in 0..=8 {
assert_eq!(lookup.value(gid).unwrap(), gid * 2);
}
assert!(lookup.value(9).is_err());
}
// Taken from example 2 at https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
#[test]
fn lookup_format_2() {
#[rustfmt::skip]
let words = [
2_u16, // format
6, // unit size (6 bytes)
3, // number of units
12, // search range
1, // entry selector
6, // range shift
22, 20, 4, // First segment, mapping glyphs 20 through 22 to class 4
24, 23, 5, // Second segment, mapping glyph 23 and 24 to class 5
28, 25, 6, // Third segment, mapping glyphs 25 through 28 to class 6
];
let mut buf = BeBuffer::new();
buf = buf.extend(words);
let lookup = LookupU16::read(buf.data().into()).unwrap();
let expected = [(20..=22, 4), (23..=24, 5), (25..=28, 6)];
for (range, class) in expected {
for gid in range {
assert_eq!(lookup.value(gid).unwrap(), class);
}
}
for fail in [0, 10, 19, 29, 0xFFFF] {
assert!(lookup.value(fail).is_err());
}
}
#[test]
fn lookup_format_4() {
#[rustfmt::skip]
let words = [
4_u16, // format
6, // unit size (6 bytes)
3, // number of units
12, // search range
1, // entry selector
6, // range shift
22, 20, 30, // First segment, mapping glyphs 20 through 22 to mapped data at offset 30
24, 23, 36, // Second segment, mapping glyph 23 and 24 to mapped data at offset 36
28, 25, 40, // Third segment, mapping glyphs 25 through 28 to mapped data at offset 40
// mapped data
3, 2, 1,
100, 150,
8, 6, 7, 9
];
let mut buf = BeBuffer::new();
buf = buf.extend(words);
let lookup = LookupU16::read(buf.data().into()).unwrap();
let expected = [
(20, 3),
(21, 2),
(22, 1),
(23, 100),
(24, 150),
(25, 8),
(26, 6),
(27, 7),
(28, 9),
];
for (in_glyph, out_glyph) in expected {
assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
}
for fail in [0, 10, 19, 29, 0xFFFF] {
assert!(lookup.value(fail).is_err());
}
}
// Taken from example 1 at https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
#[test]
fn lookup_format_6() {
#[rustfmt::skip]
let words = [
6_u16, // format
4, // unit size (4 bytes)
4, // number of units
16, // search range
2, // entry selector
0, // range shift
50, 600, // Input glyph 50 maps to glyph 600
51, 601, // Input glyph 51 maps to glyph 601
201, 602, // Input glyph 201 maps to glyph 602
202, 900, // Input glyph 202 maps to glyph 900
];
let mut buf = BeBuffer::new();
buf = buf.extend(words);
let lookup = LookupU16::read(buf.data().into()).unwrap();
let expected = [(50, 600), (51, 601), (201, 602), (202, 900)];
for (in_glyph, out_glyph) in expected {
assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
}
for fail in [0, 10, 49, 52, 203, 0xFFFF] {
assert!(lookup.value(fail).is_err());
}
}
#[test]
fn lookup_format_8() {
#[rustfmt::skip]
let words = [
8_u16, // format
201, // first glyph
7, // glyph count
3, 8, 2, 9, 1, 200, 60, // glyphs 201..209 mapped to these values
];
let mut buf = BeBuffer::new();
buf = buf.extend(words);
let lookup = LookupU16::read(buf.data().into()).unwrap();
let expected = &words[3..];
for (gid, expected) in (201..209).zip(expected) {
assert_eq!(lookup.value(gid).unwrap(), *expected);
}
for fail in [0, 10, 200, 210, 0xFFFF] {
assert!(lookup.value(fail).is_err());
}
}
#[test]
fn lookup_format_10() {
#[rustfmt::skip]
let words = [
10_u16, // format
4, // unit size, use 4 byte values
201, // first glyph
7, // glyph count
];
// glyphs 201..209 mapped to these values
let mapped = [3_u32, 8, 2902384, 9, 1, u32::MAX, 60];
let mut buf = BeBuffer::new();
buf = buf.extend(words).extend(mapped);
let lookup = LookupU32::read(buf.data().into()).unwrap();
for (gid, expected) in (201..209).zip(mapped) {
assert_eq!(lookup.value(gid).unwrap(), expected);
}
for fail in [0, 10, 200, 210, 0xFFFF] {
assert!(lookup.value(fail).is_err());
}
}
#[test]
fn extended_state_table() {
#[rustfmt::skip]
let header = [
6_u32, // number of classes
20, // byte offset to class table
56, // byte offset to state array
92, // byte offset to entry array
0, // padding
];
#[rustfmt::skip]
let class_table = [
6_u16, // format
4, // unit size (4 bytes)
5, // number of units
16, // search range
2, // entry selector
0, // range shift
50, 4, // Input glyph 50 maps to class 4
51, 4, // Input glyph 51 maps to class 4
80, 5, // Input glyph 80 maps to class 5
201, 4, // Input glyph 201 maps to class 4
202, 4, // Input glyph 202 maps to class 4
!0, !0
];
#[rustfmt::skip]
let state_array: [u16; 18] = [
0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 2, 1,
];
#[rustfmt::skip]
let entry_table: [u16; 12] = [
0, 0, u16::MAX, u16::MAX,
2, 0, u16::MAX, u16::MAX,
0, 0, u16::MAX, 0,
];
let buf = BeBuffer::new()
.extend(header)
.extend(class_table)
.extend(state_array)
.extend(entry_table);
let table = ExtendedStateTable::<ContextualData>::read(buf.data().into()).unwrap();
// check class lookups
let [class_50, class_80, class_201] =
[50, 80, 201].map(|gid| table.class(GlyphId::new(gid)).unwrap());
assert_eq!(class_50, 4);
assert_eq!(class_80, 5);
assert_eq!(class_201, 4);
// initial state
let entry = table.entry(0, 4).unwrap();
assert_eq!(entry.new_state, 0);
assert_eq!(entry.payload.current_index, !0);
// entry (state 0, class 5) should transition to state 2
let entry = table.entry(0, 5).unwrap();
assert_eq!(entry.new_state, 2);
// from state 2, we transition back to state 0 when class is not 5
// this also enables an action (payload.current_index != -1)
let entry = table.entry(2, 4).unwrap();
assert_eq!(entry.new_state, 0);
assert_eq!(entry.payload.current_index, 0);
}
#[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)]
#[repr(C, packed)]
struct ContextualData {
_mark_index: BigEndian<u16>,
current_index: BigEndian<u16>,
}
impl FixedSize for ContextualData {
const RAW_BYTE_LEN: usize = 4;
}
// Take from example at <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html>
// with class table trimmed to 4 glyphs
#[test]
fn state_table() {
#[rustfmt::skip]
let header = [
7_u16, // number of classes
10, // byte offset to class table
18, // byte offset to state array
40, // byte offset to entry array
64, // byte offset to value array (unused here)
];
#[rustfmt::skip]
let class_table = [
3_u16, // first glyph
4, // number of glyphs
];
let classes = [1u8, 2, 3, 4];
#[rustfmt::skip]
let state_array: [u8; 22] = [
2, 0, 0, 2, 1, 0, 0,
2, 0, 0, 2, 1, 0, 0,
2, 3, 3, 2, 3, 4, 5,
0, // padding
];
#[rustfmt::skip]
let entry_table: [u16; 10] = [
// The first column are offsets from the beginning of the state
// table to some position in the state array
18, 0x8112,
32, 0x8112,
18, 0x0000,
32, 0x8114,
18, 0x8116,
];
let buf = BeBuffer::new()
.extend(header)
.extend(class_table)
.extend(classes)
.extend(state_array)
.extend(entry_table);
let table = StateTable::read(buf.data().into()).unwrap();
// check class lookups
for i in 0..4u8 {
assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
}
// (state, class) -> (new_state, flags)
let cases = [
((0, 4), (2, 0x8112)),
((2, 1), (2, 0x8114)),
((1, 3), (0, 0x0000)),
((2, 5), (0, 0x8116)),
];
for ((state, class), (new_state, flags)) in cases {
let entry = table.entry(state, class).unwrap();
assert_eq!(
entry.new_state, new_state,
"state {state}, class {class} should map to new state {new_state} (got {})",
entry.new_state
);
assert_eq!(
entry.flags, flags,
"state {state}, class {class} should map to flags 0x{flags:X} (got 0x{:X})",
entry.flags
);
}
}
}

67
vendor/read-fonts/src/tables/ankr.rs vendored Normal file
View File

@@ -0,0 +1,67 @@
//! The [anchor point](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ankr.html) table.
use super::aat::LookupU16;
include!("../../generated/generated_ankr.rs");
impl<'a> Ankr<'a> {
/// Returns the set of anchor points for the given glyph.
pub fn anchor_points(&self, glyph_id: GlyphId) -> Result<&'a [AnchorPoint], ReadError> {
let glyph_id: GlyphId16 = glyph_id.try_into().map_err(|_| ReadError::OutOfBounds)?;
let entry_offset = self.lookup_table()?.value(glyph_id.to_u16())?;
let full_offset = (self.glyph_data_table_offset() as usize)
.checked_add(entry_offset as usize)
.ok_or(ReadError::OutOfBounds)?;
let data = self
.offset_data()
.split_off(full_offset)
.ok_or(ReadError::OutOfBounds)?;
Ok(GlyphDataEntry::read(data)?.anchor_points())
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn anchor_points() {
let mut buf = BeBuffer::new();
// lookup table (glyph_id -> offset)
#[rustfmt::skip]
let lookup = [
0_u16, // format
0, 8, 24, 32 // offsets to anchor points
];
let lookup_size = lookup.len() as u32 * 2;
// header
buf = buf.extend([0u32, 0x0000000C, 12 + lookup_size]);
buf = buf.extend(lookup);
// glyph entry data
#[rustfmt::skip]
let expected_anchor_points: [&[(i16, i16)]; 4] = [
&[(-20, 20)],
&[(42, -10), (-200, 300), (i16::MIN, i16::MAX)],
&[(0, 4)],
&[(0, 0), (64, -64)],
];
for entry in &expected_anchor_points {
buf = buf
.push(entry.len() as u32)
.extend(entry.iter().flat_map(|x| [x.0, x.1]));
}
let ankr = Ankr::read(buf.data().into()).unwrap();
let anchor_points = (0..4)
.map(|gid| {
let points = ankr.anchor_points(GlyphId::new(gid)).unwrap();
points
.iter()
.map(|point| (point.x(), point.y()))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
assert!(expected_anchor_points.iter().eq(anchor_points.iter()));
}
}

171
vendor/read-fonts/src/tables/avar.rs vendored Normal file
View File

@@ -0,0 +1,171 @@
//! The [Axis Variations](https://docs.microsoft.com/en-us/typography/opentype/spec/avar) table
use super::variations::{DeltaSetIndexMap, ItemVariationStore};
include!("../../generated/generated_avar.rs");
impl SegmentMaps<'_> {
/// Applies the piecewise linear mapping to the specified coordinate.
pub fn apply(&self, coord: Fixed) -> Fixed {
let mut prev = AxisValueMap {
from_coordinate: Default::default(),
to_coordinate: Default::default(),
};
for (i, axis_value_map) in self.axis_value_maps().iter().enumerate() {
use core::cmp::Ordering::*;
let from = axis_value_map.from_coordinate().to_fixed();
match from.cmp(&coord) {
Equal => return axis_value_map.to_coordinate().to_fixed(),
Greater => {
if i == 0 {
return coord;
}
let to = axis_value_map.to_coordinate().to_fixed();
let prev_from = prev.from_coordinate().to_fixed();
let prev_to = prev.to_coordinate().to_fixed();
return prev_to + (to - prev_to).mul_div(coord - prev_from, from - prev_from);
}
_ => {}
}
prev = *axis_value_map;
}
coord
}
}
impl VarSize for SegmentMaps<'_> {
type Size = u16;
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
Some(
data.read_at::<u16>(pos).ok()? as usize * AxisValueMap::RAW_BYTE_LEN
+ u16::RAW_BYTE_LEN,
)
}
}
impl<'a> FontRead<'a> for SegmentMaps<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let mut cursor = data.cursor();
let position_map_count: BigEndian<u16> = cursor.read_be()?;
let axis_value_maps = cursor.read_array(position_map_count.get() as _)?;
Ok(SegmentMaps {
position_map_count,
axis_value_maps,
})
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
use crate::{FontRef, TableProvider};
fn value_map(from: f32, to: f32) -> [F2Dot14; 2] {
[F2Dot14::from_f32(from), F2Dot14::from_f32(to)]
}
// for the purpose of testing it is easier for us to use an array
// instead of a concrete type, since we can write that into BeBuffer
impl PartialEq<[F2Dot14; 2]> for AxisValueMap {
fn eq(&self, other: &[F2Dot14; 2]) -> bool {
self.from_coordinate == other[0] && self.to_coordinate == other[1]
}
}
#[test]
fn segment_maps() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let avar = font.avar().unwrap();
assert_eq!(avar.axis_count(), 1);
let expected_segment_maps = &[vec![
value_map(-1.0, -1.0),
value_map(-0.6667, -0.5),
value_map(-0.3333, -0.25),
value_map(0.0, 0.0),
value_map(0.2, 0.3674),
value_map(0.4, 0.52246),
value_map(0.6, 0.67755),
value_map(0.8, 0.83875),
value_map(1.0, 1.0),
]];
let segment_maps = avar
.axis_segment_maps()
.iter()
.map(|segment_map| segment_map.unwrap().axis_value_maps().to_owned())
.collect::<Vec<_>>();
assert_eq!(segment_maps, expected_segment_maps);
}
#[test]
fn segment_maps_multi_axis() {
let segment_one_maps = [
value_map(-1.0, -1.0),
value_map(-0.6667, -0.5),
value_map(-0.3333, -0.25),
];
let segment_two_maps = [value_map(0.8, 0.83875), value_map(1.0, 1.0)];
let data = BeBuffer::new()
.push(MajorMinor::VERSION_1_0)
.push(0u16) // reserved
.push(2u16) // axis count
// segment map one
.push(3u16) // position count
.extend(segment_one_maps[0])
.extend(segment_one_maps[1])
.extend(segment_one_maps[2])
// segment map two
.push(2u16) // position count
.extend(segment_two_maps[0])
.extend(segment_two_maps[1]);
let avar = super::Avar::read(data.data().into()).unwrap();
assert_eq!(avar.axis_segment_maps().iter().count(), 2);
assert_eq!(
avar.axis_segment_maps()
.get(0)
.unwrap()
.unwrap()
.axis_value_maps,
segment_one_maps,
);
assert_eq!(
avar.axis_segment_maps()
.get(1)
.unwrap()
.unwrap()
.axis_value_maps,
segment_two_maps,
);
}
#[test]
fn piecewise_linear() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let avar = font.avar().unwrap();
let segment_map = avar.axis_segment_maps().get(0).unwrap().unwrap();
let coords = [-1.0, -0.5, 0.0, 0.5, 1.0];
let expected_result = [-1.0, -0.375, 0.0, 0.600006103515625, 1.0];
assert_eq!(
&expected_result[..],
&coords
.iter()
.map(|coord| segment_map.apply(Fixed::from_f64(*coord)).to_f64())
.collect::<Vec<_>>()
);
}
#[test]
fn avar2() {
let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
let avar = font.avar().unwrap();
assert_eq!(avar.version(), MajorMinor::VERSION_2_0);
assert!(avar.axis_index_map_offset().is_some());
assert!(avar.var_store_offset().is_some());
assert!(avar.var_store().is_some());
}
}

55
vendor/read-fonts/src/tables/base.rs vendored Normal file
View File

@@ -0,0 +1,55 @@
//! The [BASE](https://learn.microsoft.com/en-us/typography/opentype/spec/base) table
use super::{layout::DeviceOrVariationIndex, variations::ItemVariationStore};
include!("../../generated/generated_base.rs");
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use font_types::MajorMinor;
use super::*;
#[test]
/// https://learn.microsoft.com/en-us/typography/opentype/spec/base#base-table-examples
fn example_1() {
let data = BeBuffer::new()
.push(MajorMinor::VERSION_1_0)
.push(8u16) // horizaxis offset
.push(0x10c_u16) // verticalaxis
// axis table
.push(4u16) //basetaglist
.push(0x12_u16) // basescript list
// base tag list
.push(3u16) // count
.push(Tag::new(b"hang"))
.push(Tag::new(b"ideo"))
.push(Tag::new(b"romn"))
// basescriptlist
.push(4u16) // basescript count
.push(Tag::new(b"cyrl"))
.push(0x1a_u16)
.push(Tag::new(b"devn"))
.push(0x60_u16)
.push(Tag::new(b"hani"))
.push(0x8a_u16)
.push(Tag::new(b"latn"))
.push(0xb4_u16);
let base = Base::read(data.data().into()).unwrap();
assert_eq!(base.version(), MajorMinor::VERSION_1_0);
let horiz = base.horiz_axis().unwrap().unwrap();
let base_tag = horiz.base_tag_list().unwrap().unwrap();
assert_eq!(
base_tag.baseline_tags(),
&[Tag::new(b"hang"), Tag::new(b"ideo"), Tag::new(b"romn")]
);
assert_eq!(base_tag.min_byte_range().end, 14);
let base_script = horiz.base_script_list().unwrap();
assert_eq!(
base_script.base_script_records()[3].base_script_tag(),
Tag::new(b"latn")
);
}
}

463
vendor/read-fonts/src/tables/bitmap.rs vendored Normal file
View File

@@ -0,0 +1,463 @@
//! Common bitmap (EBLC/EBDT/CBLC/CBDT) types.
include!("../../generated/generated_bitmap.rs");
impl BitmapSize {
/// Returns the bitmap location information for the given glyph.
///
/// The `offset_data` parameter is provided by the `offset_data()` method
/// of the parent `Eblc` or `Cblc` table.
///
/// The resulting [`BitmapLocation`] value is used by the `data()` method
/// in the associated `Ebdt` or `Cbdt` table to extract the bitmap data.
pub fn location(
&self,
offset_data: FontData,
glyph_id: GlyphId,
) -> Result<BitmapLocation, ReadError> {
if !(self.start_glyph_index()..=self.end_glyph_index()).contains(&glyph_id) {
return Err(ReadError::OutOfBounds);
}
let subtable_list = self.index_subtable_list(offset_data)?;
let mut location = BitmapLocation {
bit_depth: self.bit_depth,
..BitmapLocation::default()
};
for record in subtable_list.index_subtable_records() {
let subtable = record.index_subtable(subtable_list.offset_data())?;
if !(record.first_glyph_index()..=record.last_glyph_index()).contains(&glyph_id) {
continue;
}
// glyph index relative to the first glyph in the subtable
let glyph_ix =
glyph_id.to_u32() as usize - record.first_glyph_index().to_u32() as usize;
match &subtable {
IndexSubtable::Format1(st) => {
location.format = st.image_format();
let start = st.image_data_offset() as usize
+ st.sbit_offsets()
.get(glyph_ix)
.ok_or(ReadError::OutOfBounds)?
.get() as usize;
let end = st.image_data_offset() as usize
+ st.sbit_offsets()
.get(glyph_ix + 1)
.ok_or(ReadError::OutOfBounds)?
.get() as usize;
location.data_offset = start;
if end < start {
return Err(ReadError::OutOfBounds);
}
location.data_size = end - start;
}
IndexSubtable::Format2(st) => {
location.format = st.image_format();
let data_size = st.image_size() as usize;
location.data_size = data_size;
location.data_offset = st.image_data_offset() as usize + glyph_ix * data_size;
location.metrics = Some(st.big_metrics()[0]);
}
IndexSubtable::Format3(st) => {
location.format = st.image_format();
let start = st.image_data_offset() as usize
+ st.sbit_offsets()
.get(glyph_ix)
.ok_or(ReadError::OutOfBounds)?
.get() as usize;
let end = st.image_data_offset() as usize
+ st.sbit_offsets()
.get(glyph_ix + 1)
.ok_or(ReadError::OutOfBounds)?
.get() as usize;
location.data_offset = start;
if end < start {
return Err(ReadError::OutOfBounds);
}
location.data_size = end - start;
}
IndexSubtable::Format4(st) => {
location.format = st.image_format();
let array = st.glyph_array();
let array_ix = match array
.binary_search_by(|x| x.glyph_id().to_u32().cmp(&glyph_id.to_u32()))
{
Ok(ix) => ix,
_ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
};
let start = array[array_ix].sbit_offset() as usize;
let end = array
.get(array_ix + 1)
.ok_or(ReadError::OutOfBounds)?
.sbit_offset() as usize;
location.data_offset = start;
if end < start {
return Err(ReadError::OutOfBounds);
}
location.data_size = end - start;
}
IndexSubtable::Format5(st) => {
location.format = st.image_format();
let array = st.glyph_array();
let array_ix = match array
.binary_search_by(|gid| gid.get().to_u32().cmp(&glyph_id.to_u32()))
{
Ok(ix) => ix,
_ => return Err(ReadError::InvalidCollectionIndex(glyph_id.to_u32())),
};
let data_size = st.image_size() as usize;
location.data_size = data_size;
location.data_offset = st.image_data_offset() as usize + array_ix * data_size;
location.metrics = Some(st.big_metrics()[0]);
}
}
return Ok(location);
}
Err(ReadError::OutOfBounds)
}
/// Returns the [IndexSubtableList] associated with this size.
///
/// The `offset_data` parameter is provided by the `offset_data()` method
/// of the parent `Eblc` or `Cblc` table.
pub fn index_subtable_list<'a>(
&self,
offset_data: FontData<'a>,
) -> Result<IndexSubtableList<'a>, ReadError> {
let start = self.index_subtable_list_offset() as usize;
let end = start
.checked_add(self.index_subtable_list_size() as usize)
.ok_or(ReadError::OutOfBounds)?;
let data = offset_data
.slice(start..end)
.ok_or(ReadError::OutOfBounds)?;
IndexSubtableList::read(data, self.number_of_index_subtables())
}
}
#[derive(Clone, Default)]
pub struct BitmapLocation {
/// Format of EBDT/CBDT image data.
pub format: u16,
/// Offset in bytes from the start of the EBDT/CBDT table.
pub data_offset: usize,
/// Size of the image data in bytes.
pub data_size: usize,
/// Bit depth from the associated size. Required for computing image data
/// size when unspecified.
pub bit_depth: u8,
/// Full metrics, if present in the EBLC/CBLC table.
pub metrics: Option<BigGlyphMetrics>,
}
impl BitmapLocation {
/// Returns true if the location references an empty bitmap glyph such as
/// a space.
pub fn is_empty(&self) -> bool {
self.data_size == 0
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum BitmapDataFormat {
/// The full bitmap is tightly packed according to the bit depth.
BitAligned,
/// Each row of the data is aligned to a byte boundary.
ByteAligned,
Png,
}
#[derive(Clone)]
pub enum BitmapMetrics {
Small(SmallGlyphMetrics),
Big(BigGlyphMetrics),
}
#[derive(Clone)]
pub struct BitmapData<'a> {
pub metrics: BitmapMetrics,
pub content: BitmapContent<'a>,
}
#[derive(Clone)]
pub enum BitmapContent<'a> {
Data(BitmapDataFormat, &'a [u8]),
Composite(&'a [BdtComponent]),
}
pub(crate) fn bitmap_data<'a>(
offset_data: FontData<'a>,
location: &BitmapLocation,
is_color: bool,
) -> Result<BitmapData<'a>, ReadError> {
let mut image_data = offset_data
.slice(location.data_offset..location.data_offset + location.data_size)
.ok_or(ReadError::OutOfBounds)?
.cursor();
match location.format {
// Small metrics, byte-aligned data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-1-small-metrics-byte-aligned-data>
1 => {
let metrics = read_small_metrics(&mut image_data)?;
// The data for each row is padded to a byte boundary
let pitch = (metrics.width as usize * location.bit_depth as usize).div_ceil(8);
let height = metrics.height as usize;
let data = image_data.read_array::<u8>(pitch * height)?;
Ok(BitmapData {
metrics: BitmapMetrics::Small(metrics),
content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
})
}
// Small metrics, bit-aligned data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-2-small-metrics-bit-aligned-data>
2 => {
let metrics = read_small_metrics(&mut image_data)?;
let width = metrics.width as usize * location.bit_depth as usize;
let height = metrics.height as usize;
// The data is tightly packed
let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
Ok(BitmapData {
metrics: BitmapMetrics::Small(metrics),
content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
})
}
// Format 3 is obsolete
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-3-obsolete>
// Format 4 is not supported
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-4-not-supported-metrics-in-eblc-compressed-data>
// ---
// Metrics in EBLC/CBLC, bit-aligned image data only
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-5-metrics-in-eblc-bit-aligned-image-data-only>
5 => {
let metrics = location.metrics.ok_or(ReadError::MalformedData(
"expected metrics from location table",
))?;
let width = metrics.width as usize * location.bit_depth as usize;
let height = metrics.height as usize;
// The data is tightly packed
let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
Ok(BitmapData {
metrics: BitmapMetrics::Big(metrics),
content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
})
}
// Big metrics, byte-aligned data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-6-big-metrics-byte-aligned-data>
6 => {
let metrics = read_big_metrics(&mut image_data)?;
// The data for each row is padded to a byte boundary
let pitch = (metrics.width as usize * location.bit_depth as usize).div_ceil(8);
let height = metrics.height as usize;
let data = image_data.read_array::<u8>(pitch * height)?;
Ok(BitmapData {
metrics: BitmapMetrics::Big(metrics),
content: BitmapContent::Data(BitmapDataFormat::ByteAligned, data),
})
}
// Big metrics, bit-aligned data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format7-big-metrics-bit-aligned-data>
7 => {
let metrics = read_big_metrics(&mut image_data)?;
let width = metrics.width as usize * location.bit_depth as usize;
let height = metrics.height as usize;
// The data is tightly packed
let data = image_data.read_array::<u8>((width * height).div_ceil(8))?;
Ok(BitmapData {
metrics: BitmapMetrics::Big(metrics),
content: BitmapContent::Data(BitmapDataFormat::BitAligned, data),
})
}
// Small metrics, component data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-8-small-metrics-component-data>
8 => {
let metrics = read_small_metrics(&mut image_data)?;
let _pad = image_data.read::<u8>()?;
let count = image_data.read::<u16>()? as usize;
let components = image_data.read_array::<BdtComponent>(count)?;
Ok(BitmapData {
metrics: BitmapMetrics::Small(metrics),
content: BitmapContent::Composite(components),
})
}
// Big metrics, component data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/ebdt#format-9-big-metrics-component-data>
9 => {
let metrics = read_big_metrics(&mut image_data)?;
let count = image_data.read::<u16>()? as usize;
let components = image_data.read_array::<BdtComponent>(count)?;
Ok(BitmapData {
metrics: BitmapMetrics::Big(metrics),
content: BitmapContent::Composite(components),
})
}
// Small metrics, PNG image data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-17-small-metrics-png-image-data>
17 if is_color => {
let metrics = read_small_metrics(&mut image_data)?;
let data_len = image_data.read::<u32>()? as usize;
let data = image_data.read_array::<u8>(data_len)?;
Ok(BitmapData {
metrics: BitmapMetrics::Small(metrics),
content: BitmapContent::Data(BitmapDataFormat::Png, data),
})
}
// Big metrics, PNG image data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-18-big-metrics-png-image-data>
18 if is_color => {
let metrics = read_big_metrics(&mut image_data)?;
let data_len = image_data.read::<u32>()? as usize;
let data = image_data.read_array::<u8>(data_len)?;
Ok(BitmapData {
metrics: BitmapMetrics::Big(metrics),
content: BitmapContent::Data(BitmapDataFormat::Png, data),
})
}
// Metrics in CBLC table, PNG image data
// <https://learn.microsoft.com/en-us/typography/opentype/spec/cbdt#format-19-metrics-in-cblc-table-png-image-data>
19 if is_color => {
let metrics = location.metrics.ok_or(ReadError::MalformedData(
"expected metrics from location table",
))?;
let data_len = image_data.read::<u32>()? as usize;
let data = image_data.read_array::<u8>(data_len)?;
Ok(BitmapData {
metrics: BitmapMetrics::Big(metrics),
content: BitmapContent::Data(BitmapDataFormat::Png, data),
})
}
_ => Err(ReadError::MalformedData("unexpected bitmap data format")),
}
}
fn read_small_metrics(cursor: &mut Cursor) -> Result<SmallGlyphMetrics, ReadError> {
Ok(cursor.read_array::<SmallGlyphMetrics>(1)?[0])
}
fn read_big_metrics(cursor: &mut Cursor) -> Result<BigGlyphMetrics, ReadError> {
Ok(cursor.read_array::<BigGlyphMetrics>(1)?[0])
}
#[cfg(feature = "experimental_traverse")]
impl SbitLineMetrics {
pub(crate) fn traversal_type<'a>(&self, data: FontData<'a>) -> FieldType<'a> {
FieldType::Record(self.traverse(data))
}
}
/// [IndexSubtables](https://learn.microsoft.com/en-us/typography/opentype/spec/eblc#indexsubtables) format type.
#[derive(Clone)]
pub enum IndexSubtable<'a> {
Format1(IndexSubtable1<'a>),
Format2(IndexSubtable2<'a>),
Format3(IndexSubtable3<'a>),
Format4(IndexSubtable4<'a>),
Format5(IndexSubtable5<'a>),
}
impl<'a> IndexSubtable<'a> {
///Return the `FontData` used to resolve offsets for this table.
pub fn offset_data(&self) -> FontData<'a> {
match self {
Self::Format1(item) => item.offset_data(),
Self::Format2(item) => item.offset_data(),
Self::Format3(item) => item.offset_data(),
Self::Format4(item) => item.offset_data(),
Self::Format5(item) => item.offset_data(),
}
}
/// Format of this IndexSubTable.
pub fn index_format(&self) -> u16 {
match self {
Self::Format1(item) => item.index_format(),
Self::Format2(item) => item.index_format(),
Self::Format3(item) => item.index_format(),
Self::Format4(item) => item.index_format(),
Self::Format5(item) => item.index_format(),
}
}
/// Format of EBDT image data.
pub fn image_format(&self) -> u16 {
match self {
Self::Format1(item) => item.image_format(),
Self::Format2(item) => item.image_format(),
Self::Format3(item) => item.image_format(),
Self::Format4(item) => item.image_format(),
Self::Format5(item) => item.image_format(),
}
}
/// Offset to image data in EBDT table.
pub fn image_data_offset(&self) -> u32 {
match self {
Self::Format1(item) => item.image_data_offset(),
Self::Format2(item) => item.image_data_offset(),
Self::Format3(item) => item.image_data_offset(),
Self::Format4(item) => item.image_data_offset(),
Self::Format5(item) => item.image_data_offset(),
}
}
}
impl ReadArgs for IndexSubtable<'_> {
type Args = (GlyphId16, GlyphId16);
}
impl<'a> FontReadWithArgs<'a> for IndexSubtable<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
let format: u16 = data.read_at(0usize)?;
match format {
IndexSubtable1Marker::FORMAT => {
Ok(Self::Format1(FontReadWithArgs::read_with_args(data, args)?))
}
IndexSubtable2Marker::FORMAT => Ok(Self::Format2(FontRead::read(data)?)),
IndexSubtable3Marker::FORMAT => {
Ok(Self::Format3(FontReadWithArgs::read_with_args(data, args)?))
}
IndexSubtable4Marker::FORMAT => Ok(Self::Format4(FontRead::read(data)?)),
IndexSubtable5Marker::FORMAT => Ok(Self::Format5(FontRead::read(data)?)),
other => Err(ReadError::InvalidFormat(other.into())),
}
}
}
impl MinByteRange for IndexSubtable<'_> {
fn min_byte_range(&self) -> Range<usize> {
match self {
Self::Format1(item) => item.min_byte_range(),
Self::Format2(item) => item.min_byte_range(),
Self::Format3(item) => item.min_byte_range(),
Self::Format4(item) => item.min_byte_range(),
Self::Format5(item) => item.min_byte_range(),
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> IndexSubtable<'a> {
fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> {
match self {
Self::Format1(table) => table,
Self::Format2(table) => table,
Self::Format3(table) => table,
Self::Format4(table) => table,
Self::Format5(table) => table,
}
}
}
#[cfg(feature = "experimental_traverse")]
impl std::fmt::Debug for IndexSubtable<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.dyn_inner().fmt(f)
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeTable<'a> for IndexSubtable<'a> {
fn type_name(&self) -> &str {
self.dyn_inner().type_name()
}
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
self.dyn_inner().get_field(idx)
}
}

87
vendor/read-fonts/src/tables/cbdt.rs vendored Normal file
View File

@@ -0,0 +1,87 @@
//! The [CBDT (Color Bitmap Data)](https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt) table
use super::bitmap::{BitmapData, BitmapLocation};
include!("../../generated/generated_cbdt.rs");
impl<'a> Cbdt<'a> {
pub fn data(&self, location: &BitmapLocation) -> Result<BitmapData<'a>, ReadError> {
super::bitmap::bitmap_data(self.offset_data(), location, true)
}
}
#[cfg(test)]
mod tests {
use super::super::bitmap::{BitmapDataFormat, SmallGlyphMetrics};
use crate::{
types::{GlyphId, GlyphId16},
FontRef, TableProvider,
};
#[test]
fn read_cblc_1_cbdt_17() {
let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap();
let cblc = font.cblc().unwrap();
let cbdt = font.cbdt().unwrap();
let size = &cblc.bitmap_sizes()[0];
// Metrics for size at index 0
assert!(
size.hori.ascender() == 101
&& size.hori.descender() == -27
&& size.hori.width_max() == 136
&& size.vert.ascender() == 101
&& size.vert.descender() == -27
&& size.vert.width_max() == 136
&& size.start_glyph_index() == GlyphId16::new(4)
&& size.end_glyph_index() == GlyphId16::new(4)
&& size.ppem_x() == 109
&& size.ppem_y() == 109
&& size.bit_depth() == 32
);
let expected: &[(GlyphId, &[u8], SmallGlyphMetrics)] = &[(
GlyphId::new(4),
&[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
SmallGlyphMetrics {
height: 128,
width: 136,
bearing_x: 0.into(),
bearing_y: 101.into(),
advance: 136,
},
)];
for (gid, data, metrics) in expected {
let location = size.location(cblc.offset_data(), *gid).unwrap();
assert_eq!(location.format, 17);
let bitmap_data = cbdt.data(&location).unwrap();
let (img_fmt, img_data) = bitmap_data.content.extract_data();
assert_eq!(img_fmt, BitmapDataFormat::Png);
assert_eq!(img_data, *data);
assert_eq!(bitmap_data.extract_small_metrics(), metrics);
}
}
#[test]
fn sparse_glyph_ids() {
let font = FontRef::new(font_test_data::CBDT).unwrap();
let cblc = font.cblc().unwrap();
let cbdt = font.cbdt().unwrap();
let size = &cblc.bitmap_sizes()[0];
// this font has a sparse set with gid 1 missing
for gid in 0..=3 {
let location = size
.location(cblc.offset_data(), GlyphId::new(gid))
.unwrap();
if gid == 1 {
assert!(
cbdt.data(&location).is_err(),
"expected bitmap for {gid} to be empty"
);
} else {
assert!(
cbdt.data(&location).is_ok(),
"expected bitmap for {gid} to be present"
);
}
}
}
}

5
vendor/read-fonts/src/tables/cblc.rs vendored Normal file
View File

@@ -0,0 +1,5 @@
//! The [CBLC (Color Bitmap Location)](https://docs.microsoft.com/en-us/typography/opentype/spec/cblc) table
use super::bitmap::*;
include!("../../generated/generated_cblc.rs");

225
vendor/read-fonts/src/tables/cff.rs vendored Normal file
View File

@@ -0,0 +1,225 @@
//! The [CFF](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) table
include!("../../generated/generated_cff.rs");
use super::postscript::{dict, Charset, Error, Index1, Latin1String, StringId};
/// The [Compact Font Format](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) table.
#[derive(Clone)]
pub struct Cff<'a> {
header: CffHeader<'a>,
names: Index1<'a>,
top_dicts: Index1<'a>,
strings: Index1<'a>,
global_subrs: Index1<'a>,
}
impl<'a> Cff<'a> {
pub fn offset_data(&self) -> FontData<'a> {
self.header.offset_data()
}
pub fn header(&self) -> CffHeader<'a> {
self.header.clone()
}
/// Returns the name index.
///
/// This contains the PostScript names of all fonts in the font set.
///
/// See "Name INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=13>
pub fn names(&self) -> Index1<'a> {
self.names.clone()
}
/// Returns the PostScript name for the font in the font set at the
/// given index.
pub fn name(&self, index: usize) -> Option<Latin1String<'a>> {
Some(Latin1String::new(self.names.get(index).ok()?))
}
/// Returns the top dict index.
///
/// This contains the top-level DICTs of all fonts in the font set. The
/// objects here correspond to those in the name index.
///
/// See "Top DICT INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=14>
pub fn top_dicts(&self) -> Index1<'a> {
self.top_dicts.clone()
}
/// Returns the string index.
///
/// This contains all of the strings used by fonts within the font set.
/// They are referenced by string identifiers represented by the
/// [`StringId`] type.
///
/// See "String INDEX" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=17>
pub fn strings(&self) -> Index1<'a> {
self.strings.clone()
}
/// Returns the associated string for the given identifier.
///
/// If the identifier does not represent a standard string, the result is
/// looked up in the string index.
pub fn string(&self, id: StringId) -> Option<Latin1String<'a>> {
match id.standard_string() {
Ok(name) => Some(name),
Err(ix) => self.strings.get(ix).ok().map(Latin1String::new),
}
}
/// Returns the global subroutine index.
///
/// This contains sub-programs that are referenced by one or more
/// charstrings in the font set.
///
/// See "Local/Global Subrs INDEXes" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=25>
pub fn global_subrs(&self) -> Index1<'a> {
self.global_subrs.clone()
}
/// Returns the character set associated with the top dict at the given
/// index.
///
/// See "Charsets" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=21>
pub fn charset(&self, top_dict_index: usize) -> Result<Option<Charset<'a>>, Error> {
let top_dict = self.top_dicts().get(top_dict_index)?;
let offset_data = self.offset_data();
let mut charset_offset: Option<usize> = None;
let mut num_glyphs: Option<u32> = None;
for entry in dict::entries(top_dict, None) {
match entry {
Ok(dict::Entry::Charset(offset)) => {
charset_offset = Some(offset);
}
Ok(dict::Entry::CharstringsOffset(offset)) => {
num_glyphs = Some(
Index1::read(
offset_data
.split_off(offset)
.ok_or(ReadError::OutOfBounds)?,
)?
.count() as u32,
);
}
// The ROS operator signifies a CID-keyed font and the charset
// maps to CIDs rather than SIDs which we don't parse for
// glyph names.
// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=28>
Ok(dict::Entry::Ros { .. }) => {
return Ok(None);
}
_ => {}
}
}
if let Some((charset_offset, num_glyphs)) = charset_offset.zip(num_glyphs) {
Ok(Some(Charset::new(offset_data, charset_offset, num_glyphs)?))
} else {
Ok(None)
}
}
}
impl TopLevelTable for Cff<'_> {
const TAG: Tag = Tag::new(b"CFF ");
}
impl<'a> FontRead<'a> for Cff<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let header = CffHeader::read(data)?;
let mut data = FontData::new(header.trailing_data());
let names = Index1::read(data)?;
data = data
.split_off(names.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let top_dicts = Index1::read(data)?;
data = data
.split_off(top_dicts.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let strings = Index1::read(data)?;
data = data
.split_off(strings.size_in_bytes()?)
.ok_or(ReadError::OutOfBounds)?;
let global_subrs = Index1::read(data)?;
Ok(Self {
header,
names,
top_dicts,
strings,
global_subrs,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{tables::postscript::StringId, FontRef, TableProvider};
#[test]
fn read_noto_serif_display_cff() {
let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
let cff = font.cff().unwrap();
assert_eq!(cff.header().major(), 1);
assert_eq!(cff.header().minor(), 0);
assert_eq!(cff.top_dicts().count(), 1);
assert_eq!(cff.names().count(), 1);
assert_eq!(cff.global_subrs.count(), 17);
let name = Latin1String::new(cff.names().get(0).unwrap());
assert_eq!(name, "NotoSerifDisplay-Regular");
assert_eq!(cff.strings().count(), 5);
// Version
assert_eq!(cff.string(StringId::new(391)).unwrap(), "2.9");
// Notice
assert_eq!(
cff.string(StringId::new(392)).unwrap(),
"Noto is a trademark of Google LLC."
);
// Copyright
assert_eq!(
cff.string(StringId::new(393)).unwrap(),
"Copyright 2022 The Noto Project Authors https:github.comnotofontslatin-greek-cyrillic"
);
// FullName
assert_eq!(
cff.string(StringId::new(394)).unwrap(),
"Noto Serif Display Regular"
);
// FamilyName
assert_eq!(
cff.string(StringId::new(395)).unwrap(),
"Noto Serif Display"
);
}
#[test]
fn glyph_names() {
test_glyph_names(
font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
&[".notdef", "i", "j", "k", "l"],
);
}
#[test]
fn icons_glyph_names() {
test_glyph_names(font_test_data::MATERIAL_ICONS_SUBSET, &[".notdef", "_10k"]);
}
fn test_glyph_names(font_data: &[u8], expected_names: &[&str]) {
let font = FontRef::new(font_data).unwrap();
let cff = font.cff().unwrap();
let charset = cff.charset(0).unwrap().unwrap();
let sid_to_string = |sid| std::str::from_utf8(cff.string(sid).unwrap().bytes()).unwrap();
let names_by_lookup = (0..charset.num_glyphs())
.map(|gid| sid_to_string(charset.string_id(GlyphId::new(gid)).unwrap()))
.collect::<Vec<_>>();
assert_eq!(names_by_lookup, expected_names);
let names_by_iter = charset
.iter()
.map(|(_gid, sid)| sid_to_string(sid))
.collect::<Vec<_>>();
assert_eq!(names_by_iter, expected_names);
}
}

81
vendor/read-fonts/src/tables/cff2.rs vendored Normal file
View File

@@ -0,0 +1,81 @@
//! The [CFF2](https://learn.microsoft.com/en-us/typography/opentype/spec/cff2) table
include!("../../generated/generated_cff2.rs");
use super::postscript::Index2;
/// The [Compact Font Format (CFF) version 2](https://learn.microsoft.com/en-us/typography/opentype/spec/cff2) table
#[derive(Clone)]
pub struct Cff2<'a> {
header: Cff2Header<'a>,
global_subrs: Index2<'a>,
}
impl<'a> Cff2<'a> {
pub fn offset_data(&self) -> FontData<'a> {
self.header.offset_data()
}
pub fn header(&self) -> &Cff2Header<'a> {
&self.header
}
/// Returns the raw data containing the top dict.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>
pub fn top_dict_data(&self) -> &'a [u8] {
self.header.top_dict_data()
}
/// Returns the global subroutine index.
///
/// This contains sub-programs that are referenced by one or more
/// charstrings in the font set.
///
/// See "Local/Global Subrs INDEXes" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=25>
pub fn global_subrs(&self) -> Index2<'a> {
self.global_subrs.clone()
}
}
impl TopLevelTable for Cff2<'_> {
const TAG: Tag = Tag::new(b"CFF2");
}
impl<'a> FontRead<'a> for Cff2<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let header = Cff2Header::read(data)?;
let global_subrs = Index2::read(FontData::new(header.trailing_data()))?;
Ok(Self {
header,
global_subrs,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FontData, FontRead, FontRef, TableProvider};
#[test]
fn read_example_cff2_table() {
let cff2 = Cff2::read(FontData::new(font_test_data::cff2::EXAMPLE)).unwrap();
assert_eq!(cff2.header().major_version(), 2);
assert_eq!(cff2.header().minor_version(), 0);
assert_eq!(cff2.header().header_size(), 5);
assert_eq!(cff2.top_dict_data().len(), 7);
assert_eq!(cff2.global_subrs().count(), 0);
}
#[test]
fn read_cantarell() {
let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
let cff2 = font.cff2().unwrap();
assert_eq!(cff2.header().major_version(), 2);
assert_eq!(cff2.header().minor_version(), 0);
assert_eq!(cff2.header().header_size(), 5);
assert_eq!(cff2.top_dict_data().len(), 7);
assert_eq!(cff2.global_subrs().count(), 0);
}
}

1199
vendor/read-fonts/src/tables/cmap.rs vendored Normal file

File diff suppressed because it is too large Load Diff

107
vendor/read-fonts/src/tables/colr.rs vendored Normal file
View File

@@ -0,0 +1,107 @@
//! The [COLR](https://docs.microsoft.com/en-us/typography/opentype/spec/colr) table
#[cfg(feature = "std")]
mod closure;
use super::variations::{DeltaSetIndexMap, ItemVariationStore};
include!("../../generated/generated_colr.rs");
/// Unique paint identifier used for detecting cycles in the paint graph.
pub type PaintId = usize;
impl<'a> Colr<'a> {
/// Returns the COLRv0 base glyph for the given glyph identifier.
///
/// The return value is a range of layer indices that can be passed to
/// [`v0_layer`](Self::v0_layer) to retrieve the layer glyph identifiers
/// and palette color indices.
pub fn v0_base_glyph(&self, glyph_id: GlyphId) -> Result<Option<Range<usize>>, ReadError> {
let records = self.base_glyph_records().ok_or(ReadError::NullOffset)??;
let Ok(glyph_id) = glyph_id.try_into() else {
return Ok(None);
};
let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
Ok(ix) => &records[ix],
_ => return Ok(None),
};
let start = record.first_layer_index() as usize;
let end = start + record.num_layers() as usize;
Ok(Some(start..end))
}
/// Returns the COLRv0 layer at the given index.
///
/// The layer is represented by a tuple containing the glyph identifier of
/// the associated outline and the palette color index.
pub fn v0_layer(&self, index: usize) -> Result<(GlyphId16, u16), ReadError> {
let layers = self.layer_records().ok_or(ReadError::NullOffset)??;
let layer = layers.get(index).ok_or(ReadError::OutOfBounds)?;
Ok((layer.glyph_id(), layer.palette_index()))
}
/// Returns the COLRv1 base glyph for the given glyph identifier.
///
/// The second value in the tuple is a unique identifier for the paint that
/// may be used to detect recursion in the paint graph.
pub fn v1_base_glyph(
&self,
glyph_id: GlyphId,
) -> Result<Option<(Paint<'a>, PaintId)>, ReadError> {
let Ok(glyph_id) = glyph_id.try_into() else {
return Ok(None);
};
let list = self.base_glyph_list().ok_or(ReadError::NullOffset)??;
let records = list.base_glyph_paint_records();
let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
Ok(ix) => &records[ix],
_ => return Ok(None),
};
let offset_data = list.offset_data();
// Use the address of the paint as an identifier for the recursion
// blacklist.
let id = record.paint_offset().to_u32() as usize + offset_data.as_ref().as_ptr() as usize;
Ok(Some((record.paint(offset_data)?, id)))
}
/// Returns the COLRv1 layer at the given index.
///
/// The second value in the tuple is a unique identifier for the paint that
/// may be used to detect recursion in the paint graph.
pub fn v1_layer(&self, index: usize) -> Result<(Paint<'a>, PaintId), ReadError> {
let list = self.layer_list().ok_or(ReadError::NullOffset)??;
let offset = list
.paint_offsets()
.get(index)
.ok_or(ReadError::OutOfBounds)?
.get();
let offset_data = list.offset_data();
// Use the address of the paint as an identifier for the recursion
// blacklist.
let id = offset.to_u32() as usize + offset_data.as_ref().as_ptr() as usize;
Ok((offset.resolve(offset_data)?, id))
}
/// Returns the COLRv1 clip box for the given glyph identifier.
pub fn v1_clip_box(&self, glyph_id: GlyphId) -> Result<Option<ClipBox<'a>>, ReadError> {
use core::cmp::Ordering;
let Ok(glyph_id): Result<GlyphId16, _> = glyph_id.try_into() else {
return Ok(None);
};
let list = self.clip_list().ok_or(ReadError::NullOffset)??;
let clips = list.clips();
let clip = match clips.binary_search_by(|clip| {
if glyph_id < clip.start_glyph_id() {
Ordering::Greater
} else if glyph_id > clip.end_glyph_id() {
Ordering::Less
} else {
Ordering::Equal
}
}) {
Ok(ix) => &clips[ix],
_ => return Ok(None),
};
Ok(Some(clip.clip_box(list.offset_data())?))
}
}

View File

@@ -0,0 +1,734 @@
//! computing closure for the colr table
use font_types::{GlyphId, GlyphId16};
use crate::{collections::IntSet, tables::variations::NO_VARIATION_INDEX, ResolveOffset};
use super::{
Clip, ClipBox, ClipBoxFormat2, ClipList, ColorLine, ColorStop, Colr, Paint, PaintColrGlyph,
PaintColrLayers, PaintComposite, PaintGlyph, PaintLinearGradient, PaintRadialGradient,
PaintRotate, PaintRotateAroundCenter, PaintScale, PaintScaleAroundCenter, PaintScaleUniform,
PaintScaleUniformAroundCenter, PaintSkew, PaintSkewAroundCenter, PaintSolid,
PaintSweepGradient, PaintTransform, PaintTranslate, PaintVarLinearGradient,
PaintVarRadialGradient, PaintVarRotate, PaintVarRotateAroundCenter, PaintVarScale,
PaintVarScaleAroundCenter, PaintVarScaleUniform, PaintVarScaleUniformAroundCenter,
PaintVarSkew, PaintVarSkewAroundCenter, PaintVarSolid, PaintVarSweepGradient,
PaintVarTransform, PaintVarTranslate, VarAffine2x3, VarColorLine, VarColorStop,
};
impl Colr<'_> {
//Collect the transitive closure of V0 palette indices needed for all of the input glyphs set
//It's similar to closure glyphs but in a separate fn, because v1 closure might adds more v0 glyphs, so this fn needs to be called after v1 closure
pub fn v0_closure_palette_indices(
&self,
glyph_set: &IntSet<GlyphId>,
palette_indices: &mut IntSet<u16>,
) {
let Some(Ok(records)) = self.base_glyph_records() else {
return;
};
for glyph_id in glyph_set.iter() {
let Ok(glyph_id) = glyph_id.try_into() else {
continue;
};
let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
Ok(idx) => records[idx],
_ => continue,
};
let start = record.first_layer_index() as usize;
let end = start + record.num_layers() as usize;
for layer_index in start..end {
if let Ok((_gid, palette_id)) = self.v0_layer(layer_index) {
palette_indices.insert(palette_id);
}
}
}
}
/// Collect the transitive closure of v1 glyphs,layer/paletted indices and variation/delta set indices for COLRv1
pub fn v1_closure(
&self,
glyph_set: &mut IntSet<GlyphId>,
layer_indices: &mut IntSet<u32>,
palette_indices: &mut IntSet<u16>,
variation_indices: &mut IntSet<u32>,
) {
if self.version() < 1 {
return;
}
let mut c =
Colrv1ClosureContext::new(layer_indices, palette_indices, variation_indices, self);
if let Some(Ok(base_glyph_list)) = self.base_glyph_list() {
let base_glyph_records = base_glyph_list.base_glyph_paint_records();
let offset_data = base_glyph_list.offset_data();
for paint_record in base_glyph_records {
let gid = paint_record.glyph_id();
if !glyph_set.contains(GlyphId::from(gid)) {
continue;
}
if let Ok(paint) = paint_record.paint(offset_data) {
c.dispatch(&paint);
}
}
glyph_set.union(&c.glyph_set);
}
if let Some(Ok(clip_list)) = self.clip_list() {
c.glyph_set.union(glyph_set);
for clip_record in clip_list.clips() {
clip_record.v1_closure(&mut c, &clip_list);
}
}
}
/// Collect the transitive closure of V0 glyphs needed for all of the input glyphs set
pub fn v0_closure_glyphs(
&self,
glyph_set: &IntSet<GlyphId>,
glyphset_colrv0: &mut IntSet<GlyphId>,
) {
glyphset_colrv0.union(glyph_set);
let Some(Ok(records)) = self.base_glyph_records() else {
return;
};
for glyph_id in glyph_set.iter() {
let Ok(glyph_id) = glyph_id.try_into() else {
continue;
};
let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
Ok(idx) => records[idx],
_ => continue,
};
let start = record.first_layer_index() as usize;
let end = start + record.num_layers() as usize;
for layer_index in start..end {
if let Ok((gid, _palette_id)) = self.v0_layer(layer_index) {
glyphset_colrv0.insert(GlyphId::from(gid));
}
}
}
}
}
struct Colrv1ClosureContext<'a> {
glyph_set: IntSet<GlyphId>,
layer_indices: &'a mut IntSet<u32>,
palette_indices: &'a mut IntSet<u16>,
variation_indices: &'a mut IntSet<u32>,
colr: &'a Colr<'a>,
nesting_level_left: u8,
visited_paints: IntSet<u32>,
colr_head: usize,
}
impl<'a> Colrv1ClosureContext<'a> {
pub fn new(
layer_indices: &'a mut IntSet<u32>,
palette_indices: &'a mut IntSet<u16>,
variation_indices: &'a mut IntSet<u32>,
colr: &'a Colr,
) -> Self {
let colr_head = colr.offset_data().as_bytes().as_ptr() as usize;
Self {
glyph_set: IntSet::empty(),
layer_indices,
palette_indices,
variation_indices,
colr,
nesting_level_left: 64,
visited_paints: IntSet::empty(),
colr_head,
}
}
fn dispatch(&mut self, paint: &Paint) {
if self.nesting_level_left == 0 {
return;
}
if self.paint_visited(paint) {
return;
}
self.nesting_level_left -= 1;
paint.v1_closure(self);
self.nesting_level_left += 1;
}
fn paint_visited(&mut self, paint: &Paint) -> bool {
let offset = (paint.offset_data().as_bytes().as_ptr() as usize - self.colr_head) as u32;
if self.visited_paints.contains(offset) {
return true;
}
self.visited_paints.insert(offset);
false
}
fn add_layer_indices(&mut self, first_layer_index: u32, last_layer_index: u32) {
self.layer_indices
.insert_range(first_layer_index..=last_layer_index);
}
fn add_palette_index(&mut self, palette_index: u16) {
self.palette_indices.insert(palette_index);
}
fn add_variation_indices(&mut self, var_index_base: u32, num_vars: u8) {
if num_vars == 0 || var_index_base == NO_VARIATION_INDEX {
return;
}
let last_var_index = var_index_base + num_vars as u32 - 1;
self.variation_indices
.insert_range(var_index_base..=last_var_index);
}
fn add_glyph_id(&mut self, gid: GlyphId16) {
self.glyph_set.insert(GlyphId::from(gid));
}
}
impl ColorStop {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
c.add_palette_index(self.palette_index());
}
}
impl VarColorStop {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
c.add_palette_index(self.palette_index());
c.add_variation_indices(self.var_index_base(), 2);
}
}
impl ColorLine<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
for colorstop in self.color_stops() {
colorstop.v1_closure(c);
}
}
}
impl VarColorLine<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
for var_colorstop in self.color_stops() {
var_colorstop.v1_closure(c);
}
}
}
impl Paint<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
match self {
Self::ColrLayers(item) => item.v1_closure(c),
Self::Solid(item) => item.v1_closure(c),
Self::VarSolid(item) => item.v1_closure(c),
Self::LinearGradient(item) => item.v1_closure(c),
Self::VarLinearGradient(item) => item.v1_closure(c),
Self::RadialGradient(item) => item.v1_closure(c),
Self::VarRadialGradient(item) => item.v1_closure(c),
Self::SweepGradient(item) => item.v1_closure(c),
Self::VarSweepGradient(item) => item.v1_closure(c),
Self::Glyph(item) => item.v1_closure(c),
Self::ColrGlyph(item) => item.v1_closure(c),
Self::Transform(item) => item.v1_closure(c),
Self::VarTransform(item) => item.v1_closure(c),
Self::Translate(item) => item.v1_closure(c),
Self::VarTranslate(item) => item.v1_closure(c),
Self::Scale(item) => item.v1_closure(c),
Self::VarScale(item) => item.v1_closure(c),
Self::ScaleAroundCenter(item) => item.v1_closure(c),
Self::VarScaleAroundCenter(item) => item.v1_closure(c),
Self::ScaleUniform(item) => item.v1_closure(c),
Self::VarScaleUniform(item) => item.v1_closure(c),
Self::ScaleUniformAroundCenter(item) => item.v1_closure(c),
Self::VarScaleUniformAroundCenter(item) => item.v1_closure(c),
Self::Rotate(item) => item.v1_closure(c),
Self::VarRotate(item) => item.v1_closure(c),
Self::RotateAroundCenter(item) => item.v1_closure(c),
Self::VarRotateAroundCenter(item) => item.v1_closure(c),
Self::Skew(item) => item.v1_closure(c),
Self::VarSkew(item) => item.v1_closure(c),
Self::SkewAroundCenter(item) => item.v1_closure(c),
Self::VarSkewAroundCenter(item) => item.v1_closure(c),
Self::Composite(item) => item.v1_closure(c),
}
}
}
impl PaintColrLayers<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
let num_layers = self.num_layers();
if num_layers == 0 {
return;
}
let Some(Ok(layer_list)) = c.colr.layer_list() else {
return;
};
let first_layer_index = self.first_layer_index();
let last_layer_index = first_layer_index + num_layers as u32 - 1;
c.add_layer_indices(first_layer_index, last_layer_index);
let offset_data = layer_list.offset_data();
for layer_index in first_layer_index..=last_layer_index {
if let Some(paint_offset) = layer_list.paint_offsets().get(layer_index as usize) {
if let Ok(paint) = paint_offset.get().resolve::<Paint>(offset_data) {
c.dispatch(&paint);
}
}
}
}
}
impl PaintSolid<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
c.add_palette_index(self.palette_index());
}
}
impl PaintVarSolid<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
c.add_palette_index(self.palette_index());
c.add_variation_indices(self.var_index_base(), 1);
}
}
impl PaintLinearGradient<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(colorline) = self.color_line() {
colorline.v1_closure(c);
}
}
}
impl PaintVarLinearGradient<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(var_colorline) = self.color_line() {
var_colorline.v1_closure(c);
}
c.add_variation_indices(self.var_index_base(), 6);
}
}
impl PaintRadialGradient<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(colorline) = self.color_line() {
colorline.v1_closure(c);
}
}
}
impl PaintVarRadialGradient<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(var_colorline) = self.color_line() {
var_colorline.v1_closure(c);
}
c.add_variation_indices(self.var_index_base(), 6);
}
}
impl PaintSweepGradient<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(colorline) = self.color_line() {
colorline.v1_closure(c);
}
}
}
impl PaintVarSweepGradient<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(var_colorline) = self.color_line() {
var_colorline.v1_closure(c);
}
c.add_variation_indices(self.var_index_base(), 4);
}
}
impl PaintGlyph<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
c.add_glyph_id(self.glyph_id());
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintColrGlyph<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
let glyph_id = self.glyph_id();
let Some(Ok(list)) = c.colr.base_glyph_list() else {
return;
};
let records = list.base_glyph_paint_records();
let record = match records.binary_search_by(|rec| rec.glyph_id().cmp(&glyph_id)) {
Ok(ix) => &records[ix],
_ => return,
};
if let Ok(paint) = record.paint(list.offset_data()) {
c.add_glyph_id(glyph_id);
c.dispatch(&paint);
}
}
}
impl PaintTransform<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl VarAffine2x3<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
c.add_variation_indices(self.var_index_base(), 6);
}
}
impl PaintVarTransform<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
if let Ok(affine2x3) = self.transform() {
affine2x3.v1_closure(c);
}
}
}
}
impl PaintTranslate<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarTranslate<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 2);
}
}
}
impl PaintScale<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarScale<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 2);
}
}
}
impl PaintScaleAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarScaleAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 4);
}
}
}
impl PaintScaleUniform<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarScaleUniform<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 1);
}
}
}
impl PaintScaleUniformAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarScaleUniformAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 3);
}
}
}
impl PaintRotate<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarRotate<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 1);
}
}
}
impl PaintRotateAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarRotateAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 3);
}
}
}
impl PaintSkew<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarSkew<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 2);
}
}
}
impl PaintSkewAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
}
}
}
impl PaintVarSkewAroundCenter<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(paint) = self.paint() {
c.dispatch(&paint);
c.add_variation_indices(self.var_index_base(), 4);
}
}
}
impl PaintComposite<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Ok(source_paint) = self.source_paint() {
c.dispatch(&source_paint);
}
if let Ok(backdrop_paint) = self.backdrop_paint() {
c.dispatch(&backdrop_paint);
}
}
}
impl Clip {
fn v1_closure(&self, c: &mut Colrv1ClosureContext, clip_list: &ClipList) {
let Ok(clip_box) = self.clip_box(clip_list.offset_data()) else {
return;
};
//TODO: replace below code when we have intersects(Range) available for int-set
let mut included_gids = IntSet::empty();
let start_id = GlyphId::from(self.start_glyph_id());
let end_id = GlyphId::from(self.end_glyph_id());
included_gids.insert_range(start_id..=end_id);
included_gids.intersect(&c.glyph_set);
if included_gids.is_empty() {
return;
}
clip_box.v1_closure(c);
}
}
impl ClipBox<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
if let Self::Format2(item) = self {
item.v1_closure(c)
}
}
}
impl ClipBoxFormat2<'_> {
fn v1_closure(&self, c: &mut Colrv1ClosureContext) {
c.add_variation_indices(self.var_index_base(), 4);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FontRef, GlyphId, TableProvider};
#[test]
fn test_colr_v0_closure() {
let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
let colr = font.colr().unwrap();
let mut input_glyph_set = IntSet::empty();
input_glyph_set.insert(GlyphId::new(168));
let mut glyph_set_colred = IntSet::empty();
colr.v0_closure_glyphs(&input_glyph_set, &mut glyph_set_colred);
assert_eq!(glyph_set_colred.len(), 9);
assert!(glyph_set_colred.contains(GlyphId::new(5)));
assert!(glyph_set_colred.contains(GlyphId::new(168)));
assert!(glyph_set_colred.contains(GlyphId::new(170)));
assert!(glyph_set_colred.contains(GlyphId::new(171)));
assert!(glyph_set_colred.contains(GlyphId::new(172)));
assert!(glyph_set_colred.contains(GlyphId::new(173)));
assert!(glyph_set_colred.contains(GlyphId::new(174)));
assert!(glyph_set_colred.contains(GlyphId::new(175)));
assert!(glyph_set_colred.contains(GlyphId::new(176)));
let mut palette_indices = IntSet::empty();
colr.v0_closure_palette_indices(&glyph_set_colred, &mut palette_indices);
assert_eq!(palette_indices.len(), 8);
assert!(palette_indices.contains(0));
assert!(palette_indices.contains(1));
assert!(palette_indices.contains(2));
assert!(palette_indices.contains(3));
assert!(palette_indices.contains(4));
assert!(palette_indices.contains(5));
assert!(palette_indices.contains(6));
assert!(palette_indices.contains(10));
}
#[test]
fn test_colr_v0_closure_not_found() {
let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
let colr = font.colr().unwrap();
let mut input_glyph_set = IntSet::empty();
input_glyph_set.insert(GlyphId::new(8));
let mut glyph_set_colred = IntSet::empty();
colr.v0_closure_glyphs(&input_glyph_set, &mut glyph_set_colred);
assert_eq!(glyph_set_colred.len(), 1);
assert!(glyph_set_colred.contains(GlyphId::new(8)));
}
#[test]
fn test_colr_v1_closure_no_var() {
let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
let colr = font.colr().unwrap();
let mut glyph_set = IntSet::empty();
glyph_set.insert(GlyphId::new(220));
glyph_set.insert(GlyphId::new(120));
let mut layer_indices = IntSet::empty();
let mut palette_indices = IntSet::empty();
let mut variation_indices = IntSet::empty();
colr.v1_closure(
&mut glyph_set,
&mut layer_indices,
&mut palette_indices,
&mut variation_indices,
);
assert_eq!(glyph_set.len(), 6);
assert!(glyph_set.contains(GlyphId::new(6)));
assert!(glyph_set.contains(GlyphId::new(7)));
assert!(glyph_set.contains(GlyphId::new(220)));
assert!(glyph_set.contains(GlyphId::new(3)));
assert!(glyph_set.contains(GlyphId::new(2)));
assert!(glyph_set.contains(GlyphId::new(120)));
assert_eq!(palette_indices.len(), 5);
assert!(palette_indices.contains(0));
assert!(palette_indices.contains(4));
assert!(palette_indices.contains(10));
assert!(palette_indices.contains(11));
assert!(palette_indices.contains(12));
assert_eq!(layer_indices.len(), 2);
assert!(layer_indices.contains(0));
assert!(layer_indices.contains(1));
assert!(variation_indices.is_empty());
}
#[test]
fn test_colr_v1_closure_w_var() {
let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
let colr = font.colr().unwrap();
let mut glyph_set = IntSet::empty();
glyph_set.insert(GlyphId::new(109));
let mut layer_indices = IntSet::empty();
let mut palette_indices = IntSet::empty();
let mut variation_indices = IntSet::empty();
colr.v1_closure(
&mut glyph_set,
&mut layer_indices,
&mut palette_indices,
&mut variation_indices,
);
assert_eq!(glyph_set.len(), 2);
assert!(glyph_set.contains(GlyphId::new(3)));
assert!(glyph_set.contains(GlyphId::new(109)));
assert_eq!(palette_indices.len(), 2);
assert!(palette_indices.contains(1));
assert!(palette_indices.contains(4));
assert!(layer_indices.is_empty());
assert_eq!(variation_indices.len(), 6);
assert!(variation_indices.contains(51));
assert!(variation_indices.contains(52));
assert!(variation_indices.contains(53));
assert!(variation_indices.contains(54));
assert!(variation_indices.contains(55));
assert!(variation_indices.contains(56));
}
}

36
vendor/read-fonts/src/tables/cpal.rs vendored Normal file
View File

@@ -0,0 +1,36 @@
//! The [CPAL](https://docs.microsoft.com/en-us/typography/opentype/spec/cpal) table
include!("../../generated/generated_cpal.rs");
#[cfg(test)]
mod tests {
use crate::{FontRef, TableProvider};
#[test]
fn read_sample() {
let font = FontRef::new(font_test_data::COLR_GRADIENT_RECT).unwrap();
let table = font.cpal().unwrap();
assert_eq!(table.version(), 0);
assert_eq!(table.num_palette_entries(), 2);
assert_eq!(table.num_palettes(), 2);
assert_eq!(table.num_color_records(), 4);
let color_records = table.color_records_array().unwrap().unwrap();
assert_eq!(color_records.len(), 4);
let color_tuples: Vec<[u8; 4]> = color_records
.iter()
.map(|cr| [cr.red(), cr.green(), cr.blue(), cr.alpha()])
.collect();
assert_eq!(
color_tuples,
vec![
[0x00, 0x00, 0xFF, 0xFF],
[0x00, 0xFF, 0xFF, 0xFF],
[0xAA, 0x00, 0xFF, 0xFF],
[0xAA, 0xFF, 0xFF, 0xFF],
]
);
}
}

267
vendor/read-fonts/src/tables/cvar.rs vendored Normal file
View File

@@ -0,0 +1,267 @@
//! The [cvar (CVT Variations)](https://learn.microsoft.com/en-us/typography/opentype/spec/cvar)
//! table.
include!("../../generated/generated_cvar.rs");
use super::variations::{
PackedPointNumbers, TupleDelta, TupleVariationCount, TupleVariationData, TupleVariationHeader,
};
/// Variation data specialized for the CVT variation table.
pub type CvtVariationData<'a> = TupleVariationData<'a, CvtDelta>;
impl<'a> Cvar<'a> {
/// Returns the variation data containing the tuples and deltas for the
/// control value table.
///
/// This table doesn't contain an axis count field so this must be provided
/// by the user and can be read from the `fvar` table.
pub fn variation_data(&self, axis_count: u16) -> Result<CvtVariationData<'a>, ReadError> {
let count = self.tuple_variation_count();
let data = self.data()?;
let header_data = self.raw_tuple_header_data();
// if there are shared point numbers, get them now
let (shared_point_numbers, serialized_data) = if count.shared_point_numbers() {
let (packed, data) = PackedPointNumbers::split_off_front(data);
(Some(packed), data)
} else {
(None, data)
};
Ok(CvtVariationData {
tuple_count: count,
axis_count,
shared_tuples: None,
shared_point_numbers,
header_data,
serialized_data,
_marker: std::marker::PhantomData,
})
}
/// Computes the accumulated deltas for the given set of normalized
/// coordinates and stores them in `deltas`.
///
/// The `axis_count` parameter expects the value from the `fvar`
/// table.
///
/// The `deltas` slice should have a length greater than or equal
/// to the number of values in the `cvt` table. The values are
/// computed in 16.16 format.
pub fn deltas(
&self,
axis_count: u16,
coords: &[F2Dot14],
deltas: &mut [i32],
) -> Result<(), ReadError> {
let var_data = self.variation_data(axis_count)?;
for (tuple, scalar) in var_data.active_tuples_at(coords) {
for delta in tuple.deltas() {
let ix = delta.position as usize;
if let Some(value) = deltas.get_mut(ix) {
*value += delta.apply_scalar(scalar).to_bits();
}
}
}
Ok(())
}
fn raw_tuple_header_data(&self) -> FontData<'a> {
let range = self.shape.tuple_variation_headers_byte_range();
self.data.split_off(range.start).unwrap()
}
}
/// Delta for an entry in the control value table.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CvtDelta {
/// The index in the CVT.
pub position: u16,
/// The delta to apply to the value in the CVT.
pub value: i32,
}
impl CvtDelta {
/// Applies a tuple scalar to this delta.
pub fn apply_scalar(self, scalar: Fixed) -> Fixed {
Fixed::from_i32(self.value) * scalar
}
}
impl TupleDelta for CvtDelta {
fn is_point() -> bool {
false
}
fn new(position: u16, x: i32, _y: i32) -> Self {
Self { position, value: x }
}
}
#[cfg(test)]
mod tests {
use font_types::F2Dot14;
use crate::{FontRef, TableProvider};
#[test]
fn scaled_deltas() {
let font = FontRef::new(font_test_data::CVAR).unwrap();
// Elements are ([coords], [deltas]) where deltas are fixed point
// values.
// These were generated by fancy printf debugging in FreeType.
let cases = &[
(
[0.5, 0.5],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 720896, 3276800, 1179648, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 622592, 0, 1179648, 0, 0, 0, 0, 0, 622592,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
),
(
[-0.5, 0.5],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1441792, -2162688, -1277952, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -917504, 0, -1277952, 0, 0, 0, 0, 0,
-720896, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
),
(
[0.5, -0.5],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1900544, 2621440, 2129920, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 360448, 0, 1015808, 0, 0, 0, 0, 0,
524288, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
],
),
(
[-0.5, -0.5],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1212416, -2293760, -1130496, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1097728, 0, -1277952, 0, 0, 0, 0, 0,
-737280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
),
(
[-1.0, -1.0],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2490368, -4325376, -2490368, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1835008, 0, -2555904, 0, 0, 0, 0, 0,
-1441792, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
),
(
[1.0, 1.0],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1441792, 6553600, 2359296, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1245184, 0, 2359296, 0, 0, 0, 0, 0,
1245184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
),
(
[-1.0, 1.0],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2883584, -4325376, -2555904, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1835008, 0, -2555904, 0, 0, 0, 0, 0,
-1441792, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
),
(
[1.0, -1.0],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5636096, 4456448, 5636096, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 917504, 0, 1703936, 0, 0, 0, 0, 0,
917504, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
],
),
];
let cvar = font.cvar().unwrap();
let axis_count = font.fvar().unwrap().axis_count();
let cvar_data = cvar.variation_data(axis_count).unwrap();
for (coords, expected_deltas) in cases {
let coords = coords.map(F2Dot14::from_f32);
let mut deltas = vec![0; expected_deltas.len()];
for tuple in cvar_data.tuples() {
let Some(scalar) = tuple.compute_scalar(&coords) else {
continue;
};
for delta in tuple.deltas() {
let scaled_delta = delta.apply_scalar(scalar);
deltas[delta.position as usize] += scaled_delta.to_bits();
}
}
assert_eq!(&deltas, expected_deltas);
}
}
#[test]
fn raw_tuple_deltas() {
let font = FontRef::new(font_test_data::CVAR).unwrap();
let cvar = font.cvar().unwrap();
let axis_count = font.fvar().unwrap().axis_count();
let cvar_data = cvar.variation_data(axis_count).unwrap();
// An array of slices of (point number, delta) pairs, one for each
// tuple.
// These were taken directly from the ttx
let expected = [
&[(65, 8), (66, -8), (67, 8), (85, -11), (87, 0), (93, -1)],
&[(65, -2), (66, 8), (67, -7), (85, 11), (87, 0), (93, 1)],
&[(65, 56), (66, -24), (67, 42), (85, 6), (87, -10), (93, -4)],
&[
(65, -44),
(66, -66),
(67, -39),
(85, -28),
(87, -39),
(93, -22),
],
&[(65, 22), (66, 100), (67, 36), (85, 19), (87, 36), (93, 19)],
&[(65, 8), (66, 0), (67, 8), (85, -43), (87, -49), (93, -32)],
&[(65, -8), (66, 0), (67, -8), (85, 11), (87, 9), (93, 1)],
&[(65, -80), (66, 0), (67, -90), (85, -6), (87, -47), (93, 4)],
&[(65, -16), (66, 0), (67, -21), (85, 28), (87, 39), (93, 22)],
&[
(65, -46),
(66, 0),
(67, -22),
(85, -19),
(87, 35),
(93, -19),
],
&[(65, 2), (66, 0), (67, 7), (85, -11), (87, -9), (93, -1)],
];
let mut count = 0;
for (tuple, expected) in cvar_data.tuples().zip(&expected) {
count += 1;
let deltas = tuple
.deltas()
.map(|delta| (delta.position, delta.value))
.collect::<Vec<_>>();
assert_eq!(&deltas, expected);
}
assert_eq!(count, expected.len());
}
}

150
vendor/read-fonts/src/tables/ebdt.rs vendored Normal file
View File

@@ -0,0 +1,150 @@
//! The [EBDT (Embedded Bitmap Data)](https://docs.microsoft.com/en-us/typography/opentype/spec/ebdt) table
use super::bitmap::{BitmapData, BitmapLocation};
include!("../../generated/generated_ebdt.rs");
impl<'a> Ebdt<'a> {
pub fn data(&self, location: &BitmapLocation) -> Result<BitmapData<'a>, ReadError> {
super::bitmap::bitmap_data(self.offset_data(), location, false)
}
}
#[cfg(test)]
mod tests {
use super::super::bitmap::{
BigGlyphMetrics, BitmapContent, BitmapData, BitmapDataFormat, BitmapMetrics,
SmallGlyphMetrics,
};
use crate::{
types::{GlyphId, GlyphId16},
FontRef, TableProvider,
};
impl<'a> BitmapContent<'a> {
pub(crate) fn extract_data(&self) -> (BitmapDataFormat, &'a [u8]) {
match self {
BitmapContent::Data(fmt, data) => (*fmt, *data),
_ => panic!("expected data content"),
}
}
}
impl BitmapData<'_> {
pub(crate) fn extract_small_metrics(&self) -> &SmallGlyphMetrics {
match &self.metrics {
BitmapMetrics::Small(small) => small,
_ => panic!("expected small glyph metrics"),
}
}
}
#[test]
fn read_eblc_3_ebdt_2() {
let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap();
let eblc = font.eblc().unwrap();
let ebdt = font.ebdt().unwrap();
let size = &eblc.bitmap_sizes()[0];
// Metrics for size at index 0
assert!(
size.hori.ascender() == 6
&& size.hori.descender() == 2
&& size.hori.width_max() == 4
&& size.hori.max_before_bl() == 6
&& size.hori.min_after_bl() == -2
&& size.vert.ascender() == 6
&& size.vert.descender() == 2
&& size.start_glyph_index() == GlyphId16::new(1)
&& size.end_glyph_index() == GlyphId16::new(2)
&& size.ppem_x() == 7
&& size.ppem_y() == 7
&& size.bit_depth() == 1
);
// Bit aligned formats in this strike:
let expected: &[(GlyphId, &[u8], SmallGlyphMetrics)] = &[
(
GlyphId::new(1),
&[0xee, 0xae, 0xea],
SmallGlyphMetrics {
height: 8,
width: 3,
bearing_x: 1.into(),
bearing_y: 6.into(),
advance: 4,
},
),
(
GlyphId::new(2),
&[0xf0, 0xf0, 0xf0, 0xf0],
SmallGlyphMetrics {
height: 8,
width: 4,
bearing_x: 0.into(),
bearing_y: 6.into(),
advance: 4,
},
),
];
for (gid, data, metrics) in expected {
let location = size.location(eblc.offset_data(), *gid).unwrap();
assert_eq!(location.format, 2);
let bitmap_data = ebdt.data(&location).unwrap();
let (img_fmt, img_data) = bitmap_data.content.extract_data();
assert_eq!(img_fmt, BitmapDataFormat::BitAligned);
assert_eq!(img_data, *data);
assert_eq!(bitmap_data.extract_small_metrics(), metrics);
}
}
#[test]
fn read_eblc_2_ebdt_5() {
let font = FontRef::new(font_test_data::EMBEDDED_BITMAPS).unwrap();
let eblc = font.eblc().unwrap();
let ebdt = font.ebdt().unwrap();
let size = &eblc.bitmap_sizes()[1];
// Metrics for size at index 1
assert!(
size.hori.ascender() == 12
&& size.hori.descender() == 5
&& size.hori.width_max() == 9
&& size.hori.max_before_bl() == 12
&& size.hori.min_after_bl() == -5
&& size.vert.ascender() == 12
&& size.vert.descender() == 5
&& size.start_glyph_index() == GlyphId16::new(3)
&& size.end_glyph_index() == GlyphId16::new(3)
&& size.ppem_x() == 15
&& size.ppem_y() == 15
&& size.bit_depth() == 1
);
let expected: &[(GlyphId, &[u8])] = &[(
GlyphId::new(3),
&[
0xaa, 0xbb, 0xcc, 0xdd, 0x00, 0x11, 0x22, 0x33, 0xff, 0xee, 0x12, 0x34, 0x42, 0x42,
0x42, 0xaa, 0x88, 0x99, 0x00, 0x11,
],
)];
for (gid, data) in expected {
let location = size.location(eblc.offset_data(), *gid).unwrap();
// Metrics are in EBLC, so the same for all glyphs
assert_eq!(
&location.metrics,
&Some(BigGlyphMetrics {
height: 17,
width: 9,
hori_bearing_x: 0.into(),
hori_bearing_y: 12.into(),
hori_advance: 9,
vert_bearing_x: (-4).into(),
vert_bearing_y: (-9).into(),
vert_advance: 0,
})
);
assert_eq!(location.format, 5);
let bitmap_data = ebdt.data(&location).unwrap();
let (img_fmt, img_data) = bitmap_data.content.extract_data();
assert_eq!(img_fmt, BitmapDataFormat::BitAligned);
assert_eq!(img_data, *data);
}
}
}

5
vendor/read-fonts/src/tables/eblc.rs vendored Normal file
View File

@@ -0,0 +1,5 @@
//! The [EBLC (Embedded Bitmap Location)](https://docs.microsoft.com/en-us/typography/opentype/spec/eblc) table
use super::bitmap::*;
include!("../../generated/generated_eblc.rs");

118
vendor/read-fonts/src/tables/feat.rs vendored Normal file
View File

@@ -0,0 +1,118 @@
//! The [feature name](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6feat.html) table.
include!("../../generated/generated_feat.rs");
impl Feat<'_> {
/// Returns the name for the given feature code.
pub fn find(&self, feature: u16) -> Option<FeatureName> {
let names = self.names();
let ix = names
.binary_search_by(|name| name.feature().cmp(&feature))
.ok()?;
names.get(ix).copied()
}
}
impl FeatureName {
/// Returns true if the feature settings are mutually exclusive.
pub fn is_exclusive(&self) -> bool {
// Bit 31 signifies a mutually exclusive feature
self.feature_flags() & 0x8000 != 0
}
/// Returns the index of the default setting for the feature.
pub fn default_setting_index(&self) -> u16 {
// If bit 30 is set, the default setting index is in the low byte
if self.feature_flags() & 0x4000 != 0 {
self.feature_flags() & 0xFF
} else {
0
}
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn feat_example() {
let feat_data = build_feat_example();
let feat = Feat::read(feat_data.data().into()).unwrap();
let names = feat.names();
#[rustfmt::skip]
let expected_name_fields = [
// (feature, n_settings, flags, name, exclusive, default_index)
(0, 1, 0, NameId::new(260), false, 0),
(1, 1, 0, NameId::new(256), false, 0),
(3, 3, 0x8000, NameId::new(262), true, 0),
(6, 2, 0xC001, NameId::new(258), true, 1),
];
let name_fields = names
.iter()
.map(|name| {
(
name.feature(),
name.n_settings(),
name.feature_flags(),
name.name_index(),
name.is_exclusive(),
name.default_setting_index(),
)
})
.collect::<Vec<_>>();
assert_eq!(name_fields, expected_name_fields);
#[rustfmt::skip]
let expected_setting_names: [&[(u16, NameId)]; 4] = [
&[(0, NameId::new(261))],
&[(2, NameId::new(257))],
&[(0, NameId::new(268)), (3, NameId::new(264)), (4, NameId::new(265))],
&[(0, NameId::new(259)), (1, NameId::new(260))],
];
let setting_names = names
.iter()
.map(|name| {
let settings = name.setting_table(feat.offset_data()).unwrap();
settings
.settings()
.iter()
.map(|setting| (setting.setting(), setting.name_index()))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
assert!(expected_setting_names.iter().eq(setting_names.iter()));
}
#[test]
fn feat_find() {
let feat_data = build_feat_example();
let feat = Feat::read(feat_data.data().into()).unwrap();
// List of available feature types
let valid_features = [0, 1, 3, 6];
for i in 0..10 {
let is_valid = valid_features.contains(&i);
let name = feat.find(i);
if is_valid {
assert_eq!(name.unwrap().feature(), i);
} else {
assert!(name.is_none());
}
}
}
fn build_feat_example() -> BeBuffer {
// Example taken from bottom of <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6feat.html>
let mut buf = BeBuffer::new();
// header
buf = buf.push(0x00010000u32).extend([4u16, 0, 0, 0]);
// feature name array
buf = buf.extend([0u16, 1]).push(60u32).extend([0u16, 260]);
buf = buf.extend([1u16, 1]).push(64u32).extend([0u16, 256]);
buf = buf.extend([3u16, 3]).push(68u32).extend([0x8000u16, 262]);
buf = buf.extend([6u16, 2]).push(80u32).extend([0xC001u16, 258]);
// The setting name array
buf.extend([0u16, 261, 2, 257, 0, 268, 3, 264, 4, 265, 0, 259, 1, 260])
}
}

293
vendor/read-fonts/src/tables/fvar.rs vendored Normal file
View File

@@ -0,0 +1,293 @@
//! The [Font Variations](https://docs.microsoft.com/en-us/typography/opentype/spec/fvar) table
include!("../../generated/generated_fvar.rs");
#[path = "./instance_record.rs"]
mod instance_record;
use super::{
avar::Avar,
variations::{DeltaSetIndex, FloatItemDeltaTarget},
};
pub use instance_record::InstanceRecord;
impl<'a> Fvar<'a> {
/// Returns the array of variation axis records.
pub fn axes(&self) -> Result<&'a [VariationAxisRecord], ReadError> {
Ok(self.axis_instance_arrays()?.axes())
}
/// Returns the array of instance records.
pub fn instances(&self) -> Result<ComputedArray<'a, InstanceRecord<'a>>, ReadError> {
Ok(self.axis_instance_arrays()?.instances())
}
/// Converts user space coordinates provided by an unordered iterator
/// of `(tag, value)` pairs to normalized coordinates in axis list order.
///
/// Stores the resulting normalized coordinates in the given slice.
///
/// * User coordinate tags that don't match an axis are ignored.
/// * User coordinate values are clamped to the range of their associated
/// axis before normalization.
/// * If more than one user coordinate is provided for the same tag, the
/// last one is used.
/// * If no user coordinate for an axis is provided, the associated
/// coordinate is set to the normalized value 0.0, representing the
/// default location in variation space.
/// * The length of `normalized_coords` should equal the number of axes
/// present in the this table. If the length is smaller, axes at
/// out of bounds indices are ignored. If the length is larger, the
/// excess entries will be filled with zeros.
///
/// If the [`Avar`] table is provided, applies remapping of coordinates
/// according to the specification.
pub fn user_to_normalized(
&self,
avar: Option<&Avar>,
user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
normalized_coords: &mut [F2Dot14],
) {
normalized_coords.fill(F2Dot14::default());
let axes = self.axes().unwrap_or_default();
let avar_mappings = avar.map(|avar| avar.axis_segment_maps());
for user_coord in user_coords {
// To permit non-linear interpolation, iterate over all axes to ensure we match
// multiple axes with the same tag:
// https://github.com/PeterConstable/OT_Drafts/blob/master/NLI/UnderstandingNLI.md
// We accept quadratic behavior here to avoid dynamic allocation and with the assumption
// that fonts contain a relatively small number of axes.
for (i, axis) in axes
.iter()
.enumerate()
.filter(|(_, axis)| axis.axis_tag() == user_coord.0)
{
if let Some(target_coord) = normalized_coords.get_mut(i) {
let coord = axis.normalize(user_coord.1);
*target_coord = avar_mappings
.as_ref()
.and_then(|mappings| mappings.get(i).transpose().ok())
.flatten()
.map(|mapping| mapping.apply(coord))
.unwrap_or(coord)
.to_f2dot14();
}
}
}
let Some(avar) = avar else { return };
if avar.version() == MajorMinor::VERSION_1_0 {
return;
}
let var_store = avar.var_store();
let var_index_map = avar.axis_index_map();
let actual_len = axes.len().min(normalized_coords.len());
let mut new_coords = [F2Dot14::ZERO; 64];
if actual_len > 64 {
// No avar2 for monster fonts.
// <https://github.com/googlefonts/fontations/issues/1148>
return;
}
let new_coords = &mut new_coords[..actual_len];
let normalized_coords = &mut normalized_coords[..actual_len];
new_coords.copy_from_slice(normalized_coords);
for (i, v) in normalized_coords.iter().enumerate() {
let var_index = if let Some(Ok(ref map)) = var_index_map {
map.get(i as u32).ok()
} else {
Some(DeltaSetIndex {
outer: 0,
inner: i as u16,
})
};
if var_index.is_none() {
continue;
}
if let Some(Ok(varstore)) = var_store.as_ref() {
if let Ok(delta) =
varstore.compute_float_delta(var_index.unwrap(), normalized_coords)
{
new_coords[i] = F2Dot14::from_f32((*v).apply_float_delta(delta))
.clamp(F2Dot14::MIN, F2Dot14::MAX);
}
}
}
normalized_coords.copy_from_slice(new_coords);
}
}
impl VariationAxisRecord {
/// Returns a normalized coordinate for the given value.
pub fn normalize(&self, mut value: Fixed) -> Fixed {
use core::cmp::Ordering::*;
let min_value = self.min_value();
let default_value = self.default_value();
// Make sure max is >= min to avoid potential panic in clamp.
let max_value = self.max_value().max(min_value);
value = value.clamp(min_value, max_value);
value = match value.cmp(&default_value) {
Less => {
-((default_value.saturating_sub(value)) / (default_value.saturating_sub(min_value)))
}
Greater => {
(value.saturating_sub(default_value)) / (max_value.saturating_sub(default_value))
}
Equal => Fixed::ZERO,
};
value.clamp(-Fixed::ONE, Fixed::ONE)
}
}
#[cfg(test)]
mod tests {
use crate::{FontRef, TableProvider};
use types::{F2Dot14, Fixed, NameId, Tag};
#[test]
fn axes() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let fvar = font.fvar().unwrap();
assert_eq!(fvar.axis_count(), 1);
let wght = &fvar.axes().unwrap().first().unwrap();
assert_eq!(wght.axis_tag(), Tag::new(b"wght"));
assert_eq!(wght.min_value(), Fixed::from_f64(100.0));
assert_eq!(wght.default_value(), Fixed::from_f64(400.0));
assert_eq!(wght.max_value(), Fixed::from_f64(900.0));
assert_eq!(wght.flags(), 0);
assert_eq!(wght.axis_name_id(), NameId::new(257));
}
#[test]
fn instances() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let fvar = font.fvar().unwrap();
assert_eq!(fvar.instance_count(), 9);
// There are 9 instances equally spaced from 100.0 to 900.0
// with name id monotonically increasing starting at 258.
let instances = fvar.instances().unwrap();
for i in 0..9 {
let value = 100.0 * (i + 1) as f64;
let name_id = NameId::new(258 + i as u16);
let instance = instances.get(i).unwrap();
assert_eq!(instance.coordinates.len(), 1);
assert_eq!(
instance.coordinates.first().unwrap().get(),
Fixed::from_f64(value)
);
assert_eq!(instance.subfamily_name_id, name_id);
assert_eq!(instance.post_script_name_id, None);
}
}
#[test]
fn normalize() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let fvar = font.fvar().unwrap();
let axis = fvar.axes().unwrap().first().unwrap();
let values = [100.0, 220.0, 250.0, 400.0, 650.0, 900.0];
let expected = [-1.0, -0.60001, -0.5, 0.0, 0.5, 1.0];
for (value, expected) in values.into_iter().zip(expected) {
assert_eq!(
axis.normalize(Fixed::from_f64(value)),
Fixed::from_f64(expected)
);
}
}
#[test]
fn normalize_overflow() {
// From: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69787
// & https://oss-fuzz.com/testcase?key=6159008335986688
// fvar entry triggering overflow:
// min: -26335.87451171875 def 8224.12548828125 max 8224.12548828125
let test_case = &[
79, 84, 84, 79, 0, 1, 32, 32, 255, 32, 32, 32, 102, 118, 97, 114, 32, 32, 32, 32, 0, 0,
0, 28, 0, 0, 0, 41, 32, 0, 0, 0, 0, 1, 32, 32, 0, 2, 32, 32, 32, 32, 0, 0, 32, 32, 32,
32, 32, 0, 0, 0, 0, 153, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
];
let font = FontRef::new(test_case).unwrap();
let fvar = font.fvar().unwrap();
let axis = fvar.axes().unwrap()[1];
// Should not panic with "attempt to subtract with overflow".
assert_eq!(
axis.normalize(Fixed::from_f64(0.0)),
Fixed::from_f64(-0.2509765625)
);
}
#[test]
fn user_to_normalized() {
let font = FontRef::from_index(font_test_data::VAZIRMATN_VAR, 0).unwrap();
let fvar = font.fvar().unwrap();
let avar = font.avar().ok();
let wght = Tag::new(b"wght");
let axis = fvar.axes().unwrap()[0];
let mut normalized_coords = [F2Dot14::default(); 1];
// avar table maps 0.8 to 0.83875
let avar_user = axis.default_value().to_f32()
+ (axis.max_value().to_f32() - axis.default_value().to_f32()) * 0.8;
let avar_normalized = 0.83875;
#[rustfmt::skip]
let cases = [
// (user, normalized)
(-1000.0, -1.0f32),
(100.0, -1.0),
(200.0, -0.5),
(400.0, 0.0),
(900.0, 1.0),
(avar_user, avar_normalized),
(1251.5, 1.0),
];
for (user, normalized) in cases {
fvar.user_to_normalized(
avar.as_ref(),
[(wght, Fixed::from_f64(user as f64))],
&mut normalized_coords,
);
assert_eq!(normalized_coords[0], F2Dot14::from_f32(normalized));
}
}
#[test]
fn avar2() {
let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
let avar = font.avar().ok();
let fvar = font.fvar().unwrap();
let avar_axis = Tag::new(b"AVAR");
let avwk_axis = Tag::new(b"AVWK");
let mut normalized_coords = [F2Dot14::default(); 2];
let cases = [
((100.0, 0.0), (1.0, 1.0)),
((50.0, 0.0), (0.5, 0.5)),
((0.0, 50.0), (0.0, 0.5)),
];
for (user, expected) in cases {
fvar.user_to_normalized(
avar.as_ref(),
[
(avar_axis, Fixed::from_f64(user.0)),
(avwk_axis, Fixed::from_f64(user.1)),
],
&mut normalized_coords,
);
assert_eq!(normalized_coords[0], F2Dot14::from_f32(expected.0));
assert_eq!(normalized_coords[1], F2Dot14::from_f32(expected.1));
}
}
#[test]
fn avar2_no_panic_with_wrong_size_coords_array() {
// this font has 2 axes
let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
let avar = font.avar().ok();
let fvar = font.fvar().unwrap();
// output array too small
let mut normalized_coords = [F2Dot14::default(); 1];
fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
// output array too large
let mut normalized_coords = [F2Dot14::default(); 4];
fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
}
}

45
vendor/read-fonts/src/tables/gasp.rs vendored Normal file
View File

@@ -0,0 +1,45 @@
//! The [gasp](https://learn.microsoft.com/en-us/typography/opentype/spec/gasp) table
include!("../../generated/generated_gasp.rs");
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn smoke_test() {
let buf = BeBuffer::new()
.push(1u16) // version
.push(2u16) // number of records
.push(404u16) // record 1 ppem
.push(GaspRangeBehavior::GASP_GRIDFIT | GaspRangeBehavior::GASP_DOGRAY)
.push(u16::MAX)
.push(
GaspRangeBehavior::GASP_SYMMETRIC_GRIDFIT
| GaspRangeBehavior::GASP_SYMMETRIC_SMOOTHING,
);
let gasp = Gasp::read(buf.data().into()).unwrap();
assert_eq!(gasp.version(), 1);
assert_eq!(
gasp.gasp_ranges()[0],
GaspRange {
range_max_ppem: 404.into(),
range_gasp_behavior: (GaspRangeBehavior::GASP_GRIDFIT
| GaspRangeBehavior::GASP_DOGRAY)
.into(),
}
);
assert_eq!(
gasp.gasp_ranges()[1],
GaspRange {
range_max_ppem: u16::MAX.into(),
range_gasp_behavior: (GaspRangeBehavior::GASP_SYMMETRIC_GRIDFIT
| GaspRangeBehavior::GASP_SYMMETRIC_SMOOTHING)
.into(),
}
);
}
}

16
vendor/read-fonts/src/tables/gdef.rs vendored Normal file
View File

@@ -0,0 +1,16 @@
//! the [GDEF] table
//!
//! [GDEF]: https://docs.microsoft.com/en-us/typography/opentype/spec/gdef
pub use super::layout::{
ChainedSequenceContext, ClassDef, CoverageTable, Device, DeviceOrVariationIndex, FeatureList,
FeatureVariations, Lookup, LookupList, ScriptList, SequenceContext,
};
use super::variations::ItemVariationStore;
#[cfg(test)]
#[path = "../tests/test_gdef.rs"]
mod tests;
include!("../../generated/generated_gdef.rs");

1064
vendor/read-fonts/src/tables/glyf.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
//! TrueType hinting bytecode.
mod decode;
mod instruction;
mod opcode;
pub use decode::{decode_all, DecodeError, Decoder};
pub use instruction::{InlineOperands, Instruction};
pub use opcode::Opcode;
// Exported publicly for use by skrifa when the scaler_test feature is
// enabled.
#[cfg(any(test, feature = "scaler_test"))]
pub use instruction::MockInlineOperands;

View File

@@ -0,0 +1,237 @@
//! TrueType bytecode decoder.
use super::{InlineOperands, Instruction, Opcode};
/// An error returned by [`Decoder::decode`] if the end of the bytecode
/// stream is reached unexpectedly.
#[derive(Copy, Clone, Debug)]
pub struct DecodeError;
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("unexpected end of bytecode")
}
}
/// Decodes instructions from TrueType bytecode.
#[derive(Copy, Clone)]
pub struct Decoder<'a> {
/// The bytecode for the program.
pub bytecode: &'a [u8],
/// The "program counter" or current offset into the bytecode.
pub pc: usize,
}
impl<'a> Decoder<'a> {
/// Creates a new decoder for the given bytecode and program counter.
pub fn new(bytecode: &'a [u8], pc: usize) -> Self {
Self { bytecode, pc }
}
/// Decodes the next instruction.
///
/// Returns `None` at the end of the bytecode stream.
pub fn decode(&mut self) -> Option<Result<Instruction<'a>, DecodeError>> {
let opcode = Opcode::from_byte(*self.bytecode.get(self.pc)?);
Some(self.decode_inner(opcode))
}
fn decode_inner(&mut self, opcode: Opcode) -> Result<Instruction<'a>, DecodeError> {
let mut opcode_len = opcode.len();
let mut count_len = 0;
// If the opcode length is negative the next byte contains the number
// of inline operands and |opcode_len| is the size of each operand.
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L7046>
if opcode_len < 0 {
let inline_count = *self.bytecode.get(self.pc + 1).ok_or(DecodeError)?;
opcode_len = opcode_len.abs() * inline_count as i32 + 2;
count_len = 1;
}
let opcode_len = opcode_len as usize;
let pc = self.pc;
let next_pc = pc + opcode_len;
// Skip opcode and potential inline operand count byte.
let inline_start = pc + 1 + count_len;
let inline_size = next_pc - inline_start;
let mut inline_operands = InlineOperands::default();
if inline_size > 0 {
inline_operands.bytes = self
.bytecode
.get(inline_start..inline_start + inline_size)
.ok_or(DecodeError)?;
inline_operands.is_words = opcode.is_push_words();
}
self.pc += opcode_len;
Ok(Instruction {
opcode,
inline_operands,
pc,
})
}
}
/// Returns an iterator that yields all instructions in the given bytecode
/// starting at the specified program counter.
pub fn decode_all(
bytecode: &[u8],
pc: usize,
) -> impl Iterator<Item = Result<Instruction<'_>, DecodeError>> + '_ + Clone {
let mut decoder = Decoder::new(bytecode, pc);
std::iter::from_fn(move || decoder.decode())
}
#[cfg(test)]
mod tests {
use super::Opcode;
#[test]
fn mixed_ops() {
let mut enc = Encoder::default();
// intermix push and non-push ops of various sizes to test boundary
// conditions
let cases: &[(Opcode, &[i16])] = &[
(Opcode::PUSHB100, &[1, 2, 3, 255, 5]),
(Opcode::PUSHW010, &[-1, 4508, -3]),
(Opcode::IUP0, &[]),
(Opcode::NPUSHB, &[55; 255]),
(Opcode::MDRP00110, &[]),
(Opcode::NPUSHW, &[i16::MIN; 32]),
(Opcode::LOOPCALL, &[]),
(Opcode::FLIPOFF, &[]),
(
Opcode::PUSHW011,
&[i16::MIN, i16::MIN / 2, i16::MAX, i16::MAX / 2],
),
(Opcode::GETVARIATION, &[]),
];
for (opcode, values) in cases {
if !values.is_empty() {
enc.encode_push(values);
} else {
enc.encode(*opcode);
}
}
let all_ins = super::decode_all(&enc.0, 0)
.map(|ins| ins.unwrap())
.collect::<Vec<_>>();
for (ins, (expected_opcode, expected_values)) in all_ins.iter().zip(cases) {
assert_eq!(ins.opcode, *expected_opcode);
let values = ins
.inline_operands
.values()
.map(|v| v as i16)
.collect::<Vec<_>>();
assert_eq!(&values, expected_values);
}
}
#[test]
fn non_push_ops() {
// test decoding of all single byte (non-push) opcodes
let non_push_ops: Vec<_> = (0..=255)
.filter(|b| !Opcode::from_byte(*b).is_push())
.collect();
let decoded: Vec<_> = super::decode_all(&non_push_ops, 0)
.map(|ins| ins.unwrap().opcode as u8)
.collect();
assert_eq!(non_push_ops, decoded);
}
#[test]
fn real_bytecode() {
// taken from NotoSerif-Regular, glyph Rturnedsmall, gid 1272
let bytecode = [
181, 5, 1, 9, 3, 1, 76, 75, 176, 45, 80, 88, 64, 35, 0, 3, 0, 9, 7, 3, 9, 105, 6, 4, 2,
1, 1, 2, 97, 5, 1, 2, 2, 109, 77, 11, 8, 2, 7, 7, 0, 95, 10, 1, 0, 0, 107, 0, 78, 27,
64, 41, 0, 7, 8, 0, 8, 7, 114, 0, 3, 0, 9, 8, 3, 9, 105, 6, 4, 2, 1, 1, 2, 97, 5, 1, 2,
2, 109, 77, 11, 1, 8, 8, 0, 95, 10, 1, 0, 0, 107, 0, 78, 89, 64, 31, 37, 36, 1, 0, 40,
38, 36, 44, 37, 44, 34, 32, 27, 25, 24, 23, 22, 20, 17, 16, 12, 10, 9, 8, 0, 35, 1, 35,
12, 13, 22, 43,
];
// comments below contain the ttx assembly
let expected = [
// PUSHB[ ] /* 6 values pushed */
// 5 1 9 3 1 76
"PUSHB[5] 5 1 9 3 1 76",
// MPPEM[ ] /* MeasurePixelPerEm */
"MPPEM",
// PUSHB[ ] /* 1 value pushed */
// 45
"PUSHB[0] 45",
// LT[ ] /* LessThan */
"LT",
// IF[ ] /* If */
"IF",
// NPUSHB[ ] /* 35 values pushed */
// 0 3 0 9 7 3 9 105 6 4 2 1 1 2 97 5 1 2 2 109 77 11 8 2 7
// 7 0 95 10 1 0 0 107 0 78
"NPUSHB 0 3 0 9 7 3 9 105 6 4 2 1 1 2 97 5 1 2 2 109 77 11 8 2 7 7 0 95 10 1 0 0 107 0 78",
// ELSE[ ] /* Else */
"ELSE",
// NPUSHB[ ] /* 41 values pushed */
// 0 7 8 0 8 7 114 0 3 0 9 8 3 9 105 6 4 2 1 1 2 97 5 1 2
// 2 109 77 11 1 8 8 0 95 10 1 0 0 107 0 78
"NPUSHB 0 7 8 0 8 7 114 0 3 0 9 8 3 9 105 6 4 2 1 1 2 97 5 1 2 2 109 77 11 1 8 8 0 95 10 1 0 0 107 0 78",
// EIF[ ] /* EndIf */
"EIF",
// NPUSHB[ ] /* 31 values pushed */
// 37 36 1 0 40 38 36 44 37 44 34 32 27 25 24 23 22 20 17 16 12 10 9 8 0
// 35 1 35 12 13 22
"NPUSHB 37 36 1 0 40 38 36 44 37 44 34 32 27 25 24 23 22 20 17 16 12 10 9 8 0 35 1 35 12 13 22",
// CALL[ ] /* CallFunction */
"CALL",
];
let decoded: Vec<_> = super::decode_all(&bytecode, 0)
.map(|ins| ins.unwrap())
.collect();
let decoded_asm: Vec<_> = decoded.iter().map(|ins| ins.to_string()).collect();
assert_eq!(decoded_asm, expected);
}
/// Simple encoder used for testing.
#[derive(Default)]
struct Encoder(Vec<u8>);
impl Encoder {
pub fn encode(&mut self, opcode: Opcode) {
assert!(!opcode.is_push(), "use the encode_push method instead");
self.0.push(opcode as u8);
}
pub fn encode_push(&mut self, values: &[i16]) {
if values.is_empty() {
return;
}
let is_bytes = values.iter().all(|&x| x >= 0 && x <= u8::MAX as _);
if values.len() < 256 {
if is_bytes {
if values.len() <= 8 {
let opcode =
Opcode::from_byte(Opcode::PUSHB000 as u8 + values.len() as u8 - 1);
self.0.push(opcode as u8);
} else {
self.0.push(Opcode::NPUSHB as _);
self.0.push(values.len() as _);
}
self.0.extend(values.iter().map(|&x| x as u8));
} else {
if values.len() <= 8 {
let opcode =
Opcode::from_byte(Opcode::PUSHW000 as u8 + values.len() as u8 - 1);
self.0.push(opcode as u8);
} else {
self.0.push(Opcode::NPUSHW as _);
self.0.push(values.len() as _)
}
for &value in values {
let value = value as u16;
self.0.push((value >> 8) as _);
self.0.push((value & 0xFF) as _);
}
}
} else {
panic!("too many values to push in a single instruction");
}
}
}
}

View File

@@ -0,0 +1,124 @@
/// Decoded representation of a TrueType instruction.
use super::Opcode;
/// Decoded TrueType instruction.
#[derive(Copy, Clone, Debug)]
pub struct Instruction<'a> {
/// Operation code.
pub opcode: Opcode,
/// Instruction operands that were decoded from the bytecode.
pub inline_operands: InlineOperands<'a>,
/// Program counter -- offset into the bytecode where this
/// instruction was decoded.
pub pc: usize,
}
impl std::fmt::Display for Instruction<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.opcode.name())?;
for value in self.inline_operands.values() {
write!(f, " {value}")?;
}
Ok(())
}
}
/// Sequence of instruction operands that are encoded directly in the bytecode.
///
/// This is only used for push instructions.
#[derive(Copy, Clone, Default, Debug)]
pub struct InlineOperands<'a> {
pub(super) bytes: &'a [u8],
pub(super) is_words: bool,
}
impl<'a> InlineOperands<'a> {
/// Returns the number of operands.
#[inline]
pub fn len(&self) -> usize {
if self.is_words {
self.bytes.len() / 2
} else {
self.bytes.len()
}
}
/// Returns true if there are no operands.
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
/// Returns an iterator over the operand values.
#[inline]
pub fn values(&self) -> impl Iterator<Item = i32> + 'a + Clone {
let (bytes, words) = if self.is_words {
(&[][..], self.bytes)
} else {
(self.bytes, &[][..])
};
bytes
.iter()
.map(|byte| *byte as u32 as i32)
.chain(words.chunks_exact(2).map(|chunk| {
let word = ((chunk[0] as u16) << 8) | chunk[1] as u16;
// Double cast to ensure sign extension
word as i16 as i32
}))
}
}
/// Mock for testing inline operands.
#[cfg(any(test, feature = "scaler_test"))]
pub struct MockInlineOperands {
bytes: Vec<u8>,
is_words: bool,
}
#[cfg(any(test, feature = "scaler_test"))]
impl MockInlineOperands {
pub fn from_bytes(bytes: &[u8]) -> Self {
Self {
bytes: bytes.into(),
is_words: false,
}
}
pub fn from_words(words: &[i16]) -> Self {
Self {
bytes: words
.iter()
.map(|word| *word as u16)
.flat_map(|word| vec![(word >> 8) as u8, word as u8])
.collect(),
is_words: true,
}
}
pub fn operands(&self) -> InlineOperands {
InlineOperands {
bytes: &self.bytes,
is_words: self.is_words,
}
}
}
#[cfg(test)]
mod tests {
use super::MockInlineOperands;
#[test]
fn byte_operands() {
let values = [5, 2, 85, 92, 26, 42, u8::MIN, u8::MAX];
let mock = MockInlineOperands::from_bytes(&values);
let decoded = mock.operands().values().collect::<Vec<_>>();
assert!(values.iter().map(|x| *x as i32).eq(decoded.iter().copied()));
}
#[test]
fn word_operands() {
let values = [-5, 2, 2845, 92, -26, 42, i16::MIN, i16::MAX];
let mock = MockInlineOperands::from_words(&values);
let decoded = mock.operands().values().collect::<Vec<_>>();
assert!(values.iter().map(|x| *x as i32).eq(decoded.iter().copied()));
}
}

View File

@@ -0,0 +1,845 @@
//! TrueType hinting opcodes.
/// Operation code for a TrueType instruction.
///
/// See [the TrueType instruction set](https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions)
/// from the OpenType specification for more detail.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u8)]
pub enum Opcode {
SVTCA0 = 0x00,
SVTCA1 = 0x01,
SPVTCA0 = 0x02,
SPVTCA1 = 0x03,
SFVTCA0 = 0x04,
SFVTCA1 = 0x05,
SPVTL0 = 0x06,
SPVTL1 = 0x07,
SFVTL0 = 0x08,
SFVTL1 = 0x09,
SPVFS = 0x0A,
SFVFS = 0x0B,
GPV = 0x0C,
GFV = 0x0D,
SFVTPV = 0x0E,
ISECT = 0x0F,
SRP0 = 0x10,
SRP1 = 0x11,
SRP2 = 0x12,
SZP0 = 0x13,
SZP1 = 0x14,
SZP2 = 0x15,
SZPS = 0x16,
SLOOP = 0x17,
RTG = 0x18,
RTHG = 0x19,
SMD = 0x1A,
ELSE = 0x1B,
JMPR = 0x1C,
SCVTCI = 0x1D,
SSWCI = 0x1E,
SSW = 0x1F,
DUP = 0x20,
POP = 0x21,
CLEAR = 0x22,
SWAP = 0x23,
DEPTH = 0x24,
CINDEX = 0x25,
MINDEX = 0x26,
ALIGNPTS = 0x27,
INS28 = 0x28,
UTP = 0x29,
LOOPCALL = 0x2A,
CALL = 0x2B,
FDEF = 0x2C,
ENDF = 0x2D,
MDAP0 = 0x2E,
MDAP1 = 0x2F,
IUP0 = 0x30,
IUP1 = 0x31,
SHP0 = 0x32,
SHP1 = 0x33,
SHC0 = 0x34,
SHC1 = 0x35,
SHZ0 = 0x36,
SHZ1 = 0x37,
SHPIX = 0x38,
IP = 0x39,
MSIRP0 = 0x3A,
MSIRP1 = 0x3B,
ALIGNRP = 0x3C,
RTDG = 0x3D,
MIAP0 = 0x3E,
MIAP1 = 0x3F,
NPUSHB = 0x40,
NPUSHW = 0x41,
WS = 0x42,
RS = 0x43,
WCVTP = 0x44,
RCVT = 0x45,
GC0 = 0x46,
GC1 = 0x47,
SCFS = 0x48,
MD0 = 0x49,
MD1 = 0x4A,
MPPEM = 0x4B,
MPS = 0x4C,
FLIPON = 0x4D,
FLIPOFF = 0x4E,
DEBUG = 0x4F,
LT = 0x50,
LTEQ = 0x51,
GT = 0x52,
GTEQ = 0x53,
EQ = 0x54,
NEQ = 0x55,
ODD = 0x56,
EVEN = 0x57,
IF = 0x58,
EIF = 0x59,
AND = 0x5A,
OR = 0x5B,
NOT = 0x5C,
DELTAP1 = 0x5D,
SDB = 0x5E,
SDS = 0x5F,
ADD = 0x60,
SUB = 0x61,
DIV = 0x62,
MUL = 0x63,
ABS = 0x64,
NEG = 0x65,
FLOOR = 0x66,
CEILING = 0x67,
ROUND00 = 0x68,
ROUND01 = 0x69,
ROUND10 = 0x6A,
ROUND11 = 0x6B,
NROUND00 = 0x6C,
NROUND01 = 0x6D,
NROUND10 = 0x6E,
NROUND11 = 0x6F,
WCVTF = 0x70,
DELTAP2 = 0x71,
DELTAP3 = 0x72,
DELTAC1 = 0x73,
DELTAC2 = 0x74,
DELTAC3 = 0x75,
SROUND = 0x76,
S45ROUND = 0x77,
JROT = 0x78,
JROF = 0x79,
ROFF = 0x7A,
INS7B = 0x7B,
RUTG = 0x7C,
RDTG = 0x7D,
SANGW = 0x7E,
AA = 0x7F,
FLIPPT = 0x80,
FLIPRGON = 0x81,
FLIPRGOFF = 0x82,
INS83 = 0x83,
INS84 = 0x84,
SCANCTRL = 0x85,
SDPVTL0 = 0x86,
SDPVTL1 = 0x87,
GETINFO = 0x88,
IDEF = 0x89,
ROLL = 0x8A,
MAX = 0x8B,
MIN = 0x8C,
SCANTYPE = 0x8D,
INSTCTRL = 0x8E,
INS8F = 0x8F,
INS90 = 0x90,
GETVARIATION = 0x91,
GETDATA = 0x92,
INS93 = 0x93,
INS94 = 0x94,
INS95 = 0x95,
INS96 = 0x96,
INS97 = 0x97,
INS98 = 0x98,
INS99 = 0x99,
INS9A = 0x9A,
INS9B = 0x9B,
INS9C = 0x9C,
INS9D = 0x9D,
INS9E = 0x9E,
INS9F = 0x9F,
INSA0 = 0xA0,
INSA1 = 0xA1,
INSA2 = 0xA2,
INSA3 = 0xA3,
INSA4 = 0xA4,
INSA5 = 0xA5,
INSA6 = 0xA6,
INSA7 = 0xA7,
INSA8 = 0xA8,
INSA9 = 0xA9,
INSAA = 0xAA,
INSAB = 0xAB,
INSAC = 0xAC,
INSAD = 0xAD,
INSAE = 0xAE,
INSAF = 0xAF,
PUSHB000 = 0xB0,
PUSHB001 = 0xB1,
PUSHB010 = 0xB2,
PUSHB011 = 0xB3,
PUSHB100 = 0xB4,
PUSHB101 = 0xB5,
PUSHB110 = 0xB6,
PUSHB111 = 0xB7,
PUSHW000 = 0xB8,
PUSHW001 = 0xB9,
PUSHW010 = 0xBA,
PUSHW011 = 0xBB,
PUSHW100 = 0xBC,
PUSHW101 = 0xBD,
PUSHW110 = 0xBE,
PUSHW111 = 0xBF,
MDRP00000 = 0xC0,
MDRP00001 = 0xC1,
MDRP00010 = 0xC2,
MDRP00011 = 0xC3,
MDRP00100 = 0xC4,
MDRP00101 = 0xC5,
MDRP00110 = 0xC6,
MDRP00111 = 0xC7,
MDRP01000 = 0xC8,
MDRP01001 = 0xC9,
MDRP01010 = 0xCA,
MDRP01011 = 0xCB,
MDRP01100 = 0xCC,
MDRP01101 = 0xCD,
MDRP01110 = 0xCE,
MDRP01111 = 0xCF,
MDRP10000 = 0xD0,
MDRP10001 = 0xD1,
MDRP10010 = 0xD2,
MDRP10011 = 0xD3,
MDRP10100 = 0xD4,
MDRP10101 = 0xD5,
MDRP10110 = 0xD6,
MDRP10111 = 0xD7,
MDRP11000 = 0xD8,
MDRP11001 = 0xD9,
MDRP11010 = 0xDA,
MDRP11011 = 0xDB,
MDRP11100 = 0xDC,
MDRP11101 = 0xDD,
MDRP11110 = 0xDE,
MDRP11111 = 0xDF,
MIRP00000 = 0xE0,
MIRP00001 = 0xE1,
MIRP00010 = 0xE2,
MIRP00011 = 0xE3,
MIRP00100 = 0xE4,
MIRP00101 = 0xE5,
MIRP00110 = 0xE6,
MIRP00111 = 0xE7,
MIRP01000 = 0xE8,
MIRP01001 = 0xE9,
MIRP01010 = 0xEA,
MIRP01011 = 0xEB,
MIRP01100 = 0xEC,
MIRP01101 = 0xED,
MIRP01110 = 0xEE,
MIRP01111 = 0xEF,
MIRP10000 = 0xF0,
MIRP10001 = 0xF1,
MIRP10010 = 0xF2,
MIRP10011 = 0xF3,
MIRP10100 = 0xF4,
MIRP10101 = 0xF5,
MIRP10110 = 0xF6,
MIRP10111 = 0xF7,
MIRP11000 = 0xF8,
MIRP11001 = 0xF9,
MIRP11010 = 0xFA,
MIRP11011 = 0xFB,
MIRP11100 = 0xFC,
MIRP11101 = 0xFD,
MIRP11110 = 0xFE,
MIRP11111 = 0xFF,
}
impl Opcode {
/// Creates an opcode from the given byte.
///
/// There is a 1:1 mapping between bytes and opcodes.
#[inline]
pub fn from_byte(byte: u8) -> Self {
use Opcode::*;
match byte {
0x00 => SVTCA0,
0x01 => SVTCA1,
0x02 => SPVTCA0,
0x03 => SPVTCA1,
0x04 => SFVTCA0,
0x05 => SFVTCA1,
0x06 => SPVTL0,
0x07 => SPVTL1,
0x08 => SFVTL0,
0x09 => SFVTL1,
0x0A => SPVFS,
0x0B => SFVFS,
0x0C => GPV,
0x0D => GFV,
0x0E => SFVTPV,
0x0F => ISECT,
0x10 => SRP0,
0x11 => SRP1,
0x12 => SRP2,
0x13 => SZP0,
0x14 => SZP1,
0x15 => SZP2,
0x16 => SZPS,
0x17 => SLOOP,
0x18 => RTG,
0x19 => RTHG,
0x1A => SMD,
0x1B => ELSE,
0x1C => JMPR,
0x1D => SCVTCI,
0x1E => SSWCI,
0x1F => SSW,
0x20 => DUP,
0x21 => POP,
0x22 => CLEAR,
0x23 => SWAP,
0x24 => DEPTH,
0x25 => CINDEX,
0x26 => MINDEX,
0x27 => ALIGNPTS,
0x28 => INS28,
0x29 => UTP,
0x2A => LOOPCALL,
0x2B => CALL,
0x2C => FDEF,
0x2D => ENDF,
0x2E => MDAP0,
0x2F => MDAP1,
0x30 => IUP0,
0x31 => IUP1,
0x32 => SHP0,
0x33 => SHP1,
0x34 => SHC0,
0x35 => SHC1,
0x36 => SHZ0,
0x37 => SHZ1,
0x38 => SHPIX,
0x39 => IP,
0x3A => MSIRP0,
0x3B => MSIRP1,
0x3C => ALIGNRP,
0x3D => RTDG,
0x3E => MIAP0,
0x3F => MIAP1,
0x40 => NPUSHB,
0x41 => NPUSHW,
0x42 => WS,
0x43 => RS,
0x44 => WCVTP,
0x45 => RCVT,
0x46 => GC0,
0x47 => GC1,
0x48 => SCFS,
0x49 => MD0,
0x4A => MD1,
0x4B => MPPEM,
0x4C => MPS,
0x4D => FLIPON,
0x4E => FLIPOFF,
0x4F => DEBUG,
0x50 => LT,
0x51 => LTEQ,
0x52 => GT,
0x53 => GTEQ,
0x54 => EQ,
0x55 => NEQ,
0x56 => ODD,
0x57 => EVEN,
0x58 => IF,
0x59 => EIF,
0x5A => AND,
0x5B => OR,
0x5C => NOT,
0x5D => DELTAP1,
0x5E => SDB,
0x5F => SDS,
0x60 => ADD,
0x61 => SUB,
0x62 => DIV,
0x63 => MUL,
0x64 => ABS,
0x65 => NEG,
0x66 => FLOOR,
0x67 => CEILING,
0x68 => ROUND00,
0x69 => ROUND01,
0x6A => ROUND10,
0x6B => ROUND11,
0x6C => NROUND00,
0x6D => NROUND01,
0x6E => NROUND10,
0x6F => NROUND11,
0x70 => WCVTF,
0x71 => DELTAP2,
0x72 => DELTAP3,
0x73 => DELTAC1,
0x74 => DELTAC2,
0x75 => DELTAC3,
0x76 => SROUND,
0x77 => S45ROUND,
0x78 => JROT,
0x79 => JROF,
0x7A => ROFF,
0x7B => INS7B,
0x7C => RUTG,
0x7D => RDTG,
0x7E => SANGW,
0x7F => AA,
0x80 => FLIPPT,
0x81 => FLIPRGON,
0x82 => FLIPRGOFF,
0x83 => INS83,
0x84 => INS84,
0x85 => SCANCTRL,
0x86 => SDPVTL0,
0x87 => SDPVTL1,
0x88 => GETINFO,
0x89 => IDEF,
0x8A => ROLL,
0x8B => MAX,
0x8C => MIN,
0x8D => SCANTYPE,
0x8E => INSTCTRL,
0x8F => INS8F,
0x90 => INS90,
0x91 => GETVARIATION,
0x92 => GETDATA,
0x93 => INS93,
0x94 => INS94,
0x95 => INS95,
0x96 => INS96,
0x97 => INS97,
0x98 => INS98,
0x99 => INS99,
0x9A => INS9A,
0x9B => INS9B,
0x9C => INS9C,
0x9D => INS9D,
0x9E => INS9E,
0x9F => INS9F,
0xA0 => INSA0,
0xA1 => INSA1,
0xA2 => INSA2,
0xA3 => INSA3,
0xA4 => INSA4,
0xA5 => INSA5,
0xA6 => INSA6,
0xA7 => INSA7,
0xA8 => INSA8,
0xA9 => INSA9,
0xAA => INSAA,
0xAB => INSAB,
0xAC => INSAC,
0xAD => INSAD,
0xAE => INSAE,
0xAF => INSAF,
0xB0 => PUSHB000,
0xB1 => PUSHB001,
0xB2 => PUSHB010,
0xB3 => PUSHB011,
0xB4 => PUSHB100,
0xB5 => PUSHB101,
0xB6 => PUSHB110,
0xB7 => PUSHB111,
0xB8 => PUSHW000,
0xB9 => PUSHW001,
0xBA => PUSHW010,
0xBB => PUSHW011,
0xBC => PUSHW100,
0xBD => PUSHW101,
0xBE => PUSHW110,
0xBF => PUSHW111,
0xC0 => MDRP00000,
0xC1 => MDRP00001,
0xC2 => MDRP00010,
0xC3 => MDRP00011,
0xC4 => MDRP00100,
0xC5 => MDRP00101,
0xC6 => MDRP00110,
0xC7 => MDRP00111,
0xC8 => MDRP01000,
0xC9 => MDRP01001,
0xCA => MDRP01010,
0xCB => MDRP01011,
0xCC => MDRP01100,
0xCD => MDRP01101,
0xCE => MDRP01110,
0xCF => MDRP01111,
0xD0 => MDRP10000,
0xD1 => MDRP10001,
0xD2 => MDRP10010,
0xD3 => MDRP10011,
0xD4 => MDRP10100,
0xD5 => MDRP10101,
0xD6 => MDRP10110,
0xD7 => MDRP10111,
0xD8 => MDRP11000,
0xD9 => MDRP11001,
0xDA => MDRP11010,
0xDB => MDRP11011,
0xDC => MDRP11100,
0xDD => MDRP11101,
0xDE => MDRP11110,
0xDF => MDRP11111,
0xE0 => MIRP00000,
0xE1 => MIRP00001,
0xE2 => MIRP00010,
0xE3 => MIRP00011,
0xE4 => MIRP00100,
0xE5 => MIRP00101,
0xE6 => MIRP00110,
0xE7 => MIRP00111,
0xE8 => MIRP01000,
0xE9 => MIRP01001,
0xEA => MIRP01010,
0xEB => MIRP01011,
0xEC => MIRP01100,
0xED => MIRP01101,
0xEE => MIRP01110,
0xEF => MIRP01111,
0xF0 => MIRP10000,
0xF1 => MIRP10001,
0xF2 => MIRP10010,
0xF3 => MIRP10011,
0xF4 => MIRP10100,
0xF5 => MIRP10101,
0xF6 => MIRP10110,
0xF7 => MIRP10111,
0xF8 => MIRP11000,
0xF9 => MIRP11001,
0xFA => MIRP11010,
0xFB => MIRP11011,
0xFC => MIRP11100,
0xFD => MIRP11101,
0xFE => MIRP11110,
0xFF => MIRP11111,
}
}
/// Returns a more descriptive name for the opcode.
pub fn name(self) -> &'static str {
OPCODE_NAMES[self as usize]
}
/// Returns true if this is an instruction that pushes values onto the
/// stack.
#[inline]
pub fn is_push(self) -> bool {
(self >= Self::PUSHB000 && self <= Self::PUSHW111)
|| self == Self::NPUSHB
|| self == Self::NPUSHW
}
pub(super) fn is_push_words(self) -> bool {
(self >= Self::PUSHW000 && self <= Self::PUSHW111) || self == Self::NPUSHW
}
pub(super) fn len(self) -> i32 {
OPCODE_LENGTHS[self as usize] as i32
}
}
impl std::fmt::Display for Opcode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}
/// There doesn't seem to be any prevailing set of mnemonics for these
/// instructions. These are pulled from FreeType with the justification
/// that diffing FreeType hinting traces with our own is the most
/// efficient way to track down discrepancies.
/// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L837>
const OPCODE_NAMES: [&str; 256] = [
"SVTCA[y]",
"SVTCA[x]",
"SPVTCA[y]",
"SPVTCA[x]",
"SFVTCA[y]",
"SFVTCA[x]",
"SPVTL[||]",
"SPVTL[+]",
"SFVTL[||]",
"SFVTL[+]",
"SPVFS",
"SFVFS",
"GPV",
"GFV",
"SFVTPV",
"ISECT",
"SRP0",
"SRP1",
"SRP2",
"SZP0",
"SZP1",
"SZP2",
"SZPS",
"SLOOP",
"RTG",
"RTHG",
"SMD",
"ELSE",
"JMPR",
"SCVTCI",
"SSWCI",
"SSW",
"DUP",
"POP",
"CLEAR",
"SWAP",
"DEPTH",
"CINDEX",
"MINDEX",
"ALIGNPTS",
"INS_$28",
"UTP",
"LOOPCALL",
"CALL",
"FDEF",
"ENDF",
"MDAP[]",
"MDAP[rnd]",
"IUP[y]",
"IUP[x]",
"SHP[rp2]",
"SHP[rp1]",
"SHC[rp2]",
"SHC[rp1]",
"SHZ[rp2]",
"SHZ[rp1]",
"SHPIX",
"IP",
"MSIRP[]",
"MSIRP[rp0]",
"ALIGNRP",
"RTDG",
"MIAP[]",
"MIAP[rnd]",
"NPUSHB",
"NPUSHW",
"WS",
"RS",
"WCVTP",
"RCVT",
"GC[curr]",
"GC[orig]",
"SCFS",
"MD[curr]",
"MD[orig]",
"MPPEM",
"MPS",
"FLIPON",
"FLIPOFF",
"DEBUG",
"LT",
"LTEQ",
"GT",
"GTEQ",
"EQ",
"NEQ",
"ODD",
"EVEN",
"IF",
"EIF",
"AND",
"OR",
"NOT",
"DELTAP1",
"SDB",
"SDS",
"ADD",
"SUB",
"DIV",
"MUL",
"ABS",
"NEG",
"FLOOR",
"CEILING",
"ROUND[G]",
"ROUND[B]",
"ROUND[W]",
"ROUND[]",
"NROUND[G]",
"NROUND[B]",
"NROUND[W]",
"NROUND[]",
"WCVTF",
"DELTAP2",
"DELTAP3",
"DELTAC1",
"DELTAC2",
"DELTAC3",
"SROUND",
"S45ROUND",
"JROT",
"JROF",
"ROFF",
"INS_$7B",
"RUTG",
"RDTG",
"SANGW",
"AA",
"FLIPPT",
"FLIPRGON",
"FLIPRGOFF",
"INS_$83",
"INS_$84",
"SCANCTRL",
"SDPVTL[||]",
"SDPVTL[+]",
"GETINFO",
"IDEF",
"ROLL",
"MAX",
"MIN",
"SCANTYPE",
"INSTCTRL",
"INS_$8F",
"INS_$90",
"GETVARIATION",
"GETDATA",
"INS_$93",
"INS_$94",
"INS_$95",
"INS_$96",
"INS_$97",
"INS_$98",
"INS_$99",
"INS_$9A",
"INS_$9B",
"INS_$9C",
"INS_$9D",
"INS_$9E",
"INS_$9F",
"INS_$A0",
"INS_$A1",
"INS_$A2",
"INS_$A3",
"INS_$A4",
"INS_$A5",
"INS_$A6",
"INS_$A7",
"INS_$A8",
"INS_$A9",
"INS_$AA",
"INS_$AB",
"INS_$AC",
"INS_$AD",
"INS_$AE",
"INS_$AF",
"PUSHB[0]",
"PUSHB[1]",
"PUSHB[2]",
"PUSHB[3]",
"PUSHB[4]",
"PUSHB[5]",
"PUSHB[6]",
"PUSHB[7]",
"PUSHW[0]",
"PUSHW[1]",
"PUSHW[2]",
"PUSHW[3]",
"PUSHW[4]",
"PUSHW[5]",
"PUSHW[6]",
"PUSHW[7]",
"MDRP[G]",
"MDRP[B]",
"MDRP[W]",
"MDRP[]",
"MDRP[rG]",
"MDRP[rB]",
"MDRP[rW]",
"MDRP[r]",
"MDRP[mG]",
"MDRP[mB]",
"MDRP[mW]",
"MDRP[m]",
"MDRP[mrG]",
"MDRP[mrB]",
"MDRP[mrW]",
"MDRP[mr]",
"MDRP[pG]",
"MDRP[pB]",
"MDRP[pW]",
"MDRP[p]",
"MDRP[prG]",
"MDRP[prB]",
"MDRP[prW]",
"MDRP[pr]",
"MDRP[pmG]",
"MDRP[pmB]",
"MDRP[pmW]",
"MDRP[pm]",
"MDRP[pmrG]",
"MDRP[pmrB]",
"MDRP[pmrW]",
"MDRP[pmr]",
"MIRP[G]",
"MIRP[B]",
"MIRP[W]",
"MIRP[]",
"MIRP[rG]",
"MIRP[rB]",
"MIRP[rW]",
"MIRP[r]",
"MIRP[mG]",
"MIRP[mB]",
"MIRP[mW]",
"MIRP[m]",
"MIRP[mrG]",
"MIRP[mrB]",
"MIRP[mrW]",
"MIRP[mr]",
"MIRP[pG]",
"MIRP[pB]",
"MIRP[pW]",
"MIRP[p]",
"MIRP[prG]",
"MIRP[prB]",
"MIRP[prW]",
"MIRP[pr]",
"MIRP[pmG]",
"MIRP[pmB]",
"MIRP[pmW]",
"MIRP[pm]",
"MIRP[pmrG]",
"MIRP[pmrB]",
"MIRP[pmrW]",
"MIRP[pmr]",
];
/// Size in bytes of an instruction.
///
/// The negative values represent variable length instructions where the
/// next byte in the stream is the count of following operands and the
/// absolute value of the length in this table is the size in bytes of
/// each operand. These are just the NPUSHB and NPUSHW instructions.
/// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L1137>
const OPCODE_LENGTHS: [i8; 256] = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-1, -2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 3, 5, 7, 9, 11, 13,
15, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
];

143
vendor/read-fonts/src/tables/gpos.rs vendored Normal file
View File

@@ -0,0 +1,143 @@
//! the [GPOS] table
//!
//! [GPOS]: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
#[path = "./value_record.rs"]
mod value_record;
#[cfg(feature = "std")]
mod closure;
use crate::array::ComputedArray;
/// reexport stuff from layout that we use
pub use super::layout::{
ClassDef, CoverageTable, Device, DeviceOrVariationIndex, FeatureList, FeatureVariations,
Lookup, ScriptList,
};
use super::layout::{ExtensionLookup, LookupFlag, Subtables};
pub use value_record::ValueRecord;
#[cfg(test)]
#[path = "../tests/test_gpos.rs"]
mod spec_tests;
include!("../../generated/generated_gpos.rs");
/// A typed GPOS [LookupList](super::layout::LookupList) table
pub type PositionLookupList<'a> = super::layout::LookupList<'a, PositionLookup<'a>>;
/// A GPOS [SequenceContext](super::layout::SequenceContext)
pub type PositionSequenceContext<'a> = super::layout::SequenceContext<'a>;
/// A GPOS [ChainedSequenceContext](super::layout::ChainedSequenceContext)
pub type PositionChainContext<'a> = super::layout::ChainedSequenceContext<'a>;
impl<'a> AnchorTable<'a> {
/// Attempt to resolve the `Device` or `VariationIndex` table for the
/// x_coordinate, if present
pub fn x_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
match self {
AnchorTable::Format3(inner) => inner.x_device(),
_ => None,
}
}
/// Attempt to resolve the `Device` or `VariationIndex` table for the
/// y_coordinate, if present
pub fn y_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
match self {
AnchorTable::Format3(inner) => inner.y_device(),
_ => None,
}
}
}
impl<'a, T: FontRead<'a>> ExtensionLookup<'a, T> for ExtensionPosFormat1<'a, T> {
fn extension(&self) -> Result<T, ReadError> {
self.extension()
}
}
type PosSubtables<'a, T> = Subtables<'a, T, ExtensionPosFormat1<'a, T>>;
/// The subtables from a GPOS lookup.
///
/// This type is a convenience that removes the need to dig into the
/// [`PositionLookup`] enum in order to access subtables, and it also abstracts
/// away the distinction between extension and non-extension lookups.
pub enum PositionSubtables<'a> {
Single(PosSubtables<'a, SinglePos<'a>>),
Pair(PosSubtables<'a, PairPos<'a>>),
Cursive(PosSubtables<'a, CursivePosFormat1<'a>>),
MarkToBase(PosSubtables<'a, MarkBasePosFormat1<'a>>),
MarkToLig(PosSubtables<'a, MarkLigPosFormat1<'a>>),
MarkToMark(PosSubtables<'a, MarkMarkPosFormat1<'a>>),
Contextual(PosSubtables<'a, PositionSequenceContext<'a>>),
ChainContextual(PosSubtables<'a, PositionChainContext<'a>>),
}
impl<'a> PositionLookup<'a> {
pub fn lookup_flag(&self) -> LookupFlag {
self.of_unit_type().lookup_flag()
}
/// Different enumerations for GSUB and GPOS
pub fn lookup_type(&self) -> u16 {
self.of_unit_type().lookup_type()
}
pub fn mark_filtering_set(&self) -> Option<u16> {
self.of_unit_type().mark_filtering_set()
}
/// Return the subtables for this lookup.
///
/// This method handles both extension and non-extension lookups, and saves
/// the caller needing to dig into the `PositionLookup` enum itself.
pub fn subtables(&self) -> Result<PositionSubtables<'a>, ReadError> {
let raw_lookup = self.of_unit_type();
let offsets = raw_lookup.subtable_offsets();
let data = raw_lookup.offset_data();
match raw_lookup.lookup_type() {
1 => Ok(PositionSubtables::Single(Subtables::new(offsets, data))),
2 => Ok(PositionSubtables::Pair(Subtables::new(offsets, data))),
3 => Ok(PositionSubtables::Cursive(Subtables::new(offsets, data))),
4 => Ok(PositionSubtables::MarkToBase(Subtables::new(offsets, data))),
5 => Ok(PositionSubtables::MarkToLig(Subtables::new(offsets, data))),
6 => Ok(PositionSubtables::MarkToMark(Subtables::new(offsets, data))),
7 => Ok(PositionSubtables::Contextual(Subtables::new(offsets, data))),
8 => Ok(PositionSubtables::ChainContextual(Subtables::new(
offsets, data,
))),
9 => {
let first = offsets.first().ok_or(ReadError::OutOfBounds)?.get();
let ext: ExtensionPosFormat1<()> = first.resolve(data)?;
match ext.extension_lookup_type() {
1 => Ok(PositionSubtables::Single(Subtables::new_ext(offsets, data))),
2 => Ok(PositionSubtables::Pair(Subtables::new_ext(offsets, data))),
3 => Ok(PositionSubtables::Cursive(Subtables::new_ext(
offsets, data,
))),
4 => Ok(PositionSubtables::MarkToBase(Subtables::new_ext(
offsets, data,
))),
5 => Ok(PositionSubtables::MarkToLig(Subtables::new_ext(
offsets, data,
))),
6 => Ok(PositionSubtables::MarkToMark(Subtables::new_ext(
offsets, data,
))),
7 => Ok(PositionSubtables::Contextual(Subtables::new_ext(
offsets, data,
))),
8 => Ok(PositionSubtables::ChainContextual(Subtables::new_ext(
offsets, data,
))),
other => Err(ReadError::InvalidFormat(other as _)),
}
}
other => Err(ReadError::InvalidFormat(other as _)),
}
}
}

View File

@@ -0,0 +1,196 @@
//! support closure for GPOS
use super::{
CursivePosFormat1, Gpos, MarkBasePosFormat1, MarkLigPosFormat1, MarkMarkPosFormat1, PairPos,
PairPosFormat1, PairPosFormat2, PairSet, PositionLookup, PositionLookupList, PositionSubtables,
SinglePos, SinglePosFormat1, SinglePosFormat2,
};
use crate::{collections::IntSet, GlyphId, ReadError, Tag};
#[cfg(feature = "std")]
use crate::tables::layout::{LookupClosure, LookupClosureCtx};
impl Gpos<'_> {
/// Return a set of all feature indices underneath the specified scripts, languages and features
pub fn collect_features(
&self,
scripts: &IntSet<Tag>,
languages: &IntSet<Tag>,
features: &IntSet<Tag>,
) -> Result<IntSet<u16>, ReadError> {
let feature_list = self.feature_list()?;
let script_list = self.script_list()?;
let head_ptr = self.offset_data().as_bytes().as_ptr() as usize;
script_list.collect_features(head_ptr, &feature_list, scripts, languages, features)
}
}
impl PositionLookupList<'_> {
pub fn closure_lookups(
&self,
glyph_set: &IntSet<GlyphId>,
lookup_indices: &mut IntSet<u16>,
) -> Result<(), ReadError> {
let mut c = LookupClosureCtx::new(glyph_set);
let lookups = self.lookups();
for idx in lookup_indices.iter() {
let lookup = lookups.get(idx as usize)?;
lookup.closure_lookups(&mut c, idx)?;
}
lookup_indices.union(c.visited_lookups());
lookup_indices.subtract(c.inactive_lookups());
Ok(())
}
}
impl LookupClosure for PositionLookup<'_> {
fn closure_lookups(
&self,
c: &mut LookupClosureCtx,
lookup_index: u16,
) -> Result<(), ReadError> {
if !c.should_visit_lookup(lookup_index) {
return Ok(());
}
if !self.intersects(c.glyphs())? {
c.set_lookup_inactive(lookup_index);
return Ok(());
}
let lookup_type = self.lookup_type();
self.subtables()?.closure_lookups(c, lookup_type)
}
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
self.subtables()?.intersects(glyph_set)
}
}
impl LookupClosure for PositionSubtables<'_> {
fn closure_lookups(&self, _c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
Ok(())
}
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
PositionSubtables::Single(subtables) => subtables.intersects(glyph_set),
PositionSubtables::Pair(subtables) => subtables.intersects(glyph_set),
PositionSubtables::Cursive(subtables) => subtables.intersects(glyph_set),
PositionSubtables::MarkToBase(subtables) => subtables.intersects(glyph_set),
PositionSubtables::MarkToLig(subtables) => subtables.intersects(glyph_set),
PositionSubtables::MarkToMark(subtables) => subtables.intersects(glyph_set),
PositionSubtables::Contextual(subtables) => subtables.intersects(glyph_set),
PositionSubtables::ChainContextual(subtables) => subtables.intersects(glyph_set),
}
}
}
impl LookupClosure for SinglePos<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
Self::Format1(item) => item.intersects(glyph_set),
Self::Format2(item) => item.intersects(glyph_set),
}
}
}
impl LookupClosure for SinglePosFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for SinglePosFormat2<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for PairPos<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
Self::Format1(item) => item.intersects(glyph_set),
Self::Format2(item) => item.intersects(glyph_set),
}
}
}
impl LookupClosure for PairPosFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let coverage = self.coverage()?;
let pair_sets = self.pair_sets();
let num_pair_sets = self.pair_set_count();
let num_bits = 16 - num_pair_sets.leading_zeros();
if num_pair_sets as u64 > glyph_set.len() * num_bits as u64 {
for g in glyph_set.iter() {
let Some(i) = coverage.get(g) else {
continue;
};
let pair_set = pair_sets.get(i as usize)?;
if pair_set.intersects(glyph_set)? {
return Ok(true);
}
}
} else {
for (g, pair_set) in coverage.iter().zip(pair_sets.iter()) {
if !glyph_set.contains(GlyphId::from(g)) {
continue;
}
if pair_set?.intersects(glyph_set)? {
return Ok(true);
}
}
}
Ok(false)
}
}
impl LookupClosure for PairSet<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
for record in self.pair_value_records().iter() {
let second_glyph = record?.second_glyph();
if glyph_set.contains(GlyphId::from(second_glyph)) {
return Ok(true);
}
}
Ok(false)
}
}
impl LookupClosure for PairPosFormat2<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set) && self.class_def2()?.intersects(glyph_set)?)
}
}
impl LookupClosure for CursivePosFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for MarkBasePosFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.mark_coverage()?.intersects(glyph_set)
&& self.base_coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for MarkLigPosFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.mark_coverage()?.intersects(glyph_set)
&& self.ligature_coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for MarkMarkPosFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.mark1_coverage()?.intersects(glyph_set)
&& self.mark2_coverage()?.intersects(glyph_set))
}
}

124
vendor/read-fonts/src/tables/gsub.rs vendored Normal file
View File

@@ -0,0 +1,124 @@
//! the [GSUB] table
//!
//! [GSUB]: https://docs.microsoft.com/en-us/typography/opentype/spec/gsub
pub use super::layout::{
ChainedSequenceContext, ClassDef, CoverageTable, Device, FeatureList, FeatureVariations,
Lookup, LookupList, ScriptList, SequenceContext,
};
use super::layout::{ExtensionLookup, LookupFlag, Subtables};
#[cfg(feature = "std")]
mod closure;
#[cfg(test)]
#[path = "../tests/test_gsub.rs"]
mod tests;
include!("../../generated/generated_gsub.rs");
/// A typed GSUB [LookupList] table
pub type SubstitutionLookupList<'a> = LookupList<'a, SubstitutionLookup<'a>>;
/// A GSUB [SequenceContext]
pub type SubstitutionSequenceContext<'a> = super::layout::SequenceContext<'a>;
/// A GSUB [ChainedSequenceContext]
pub type SubstitutionChainContext<'a> = super::layout::ChainedSequenceContext<'a>;
impl<'a, T: FontRead<'a>> ExtensionLookup<'a, T> for ExtensionSubstFormat1<'a, T> {
fn extension(&self) -> Result<T, ReadError> {
self.extension()
}
}
type SubSubtables<'a, T> = Subtables<'a, T, ExtensionSubstFormat1<'a, T>>;
/// The subtables from a GPOS lookup.
///
/// This type is a convenience that removes the need to dig into the
/// [`SubstitutionLookup`] enum in order to access subtables, and it also abstracts
/// away the distinction between extension and non-extension lookups.
pub enum SubstitutionSubtables<'a> {
Single(SubSubtables<'a, SingleSubst<'a>>),
Multiple(SubSubtables<'a, MultipleSubstFormat1<'a>>),
Alternate(SubSubtables<'a, AlternateSubstFormat1<'a>>),
Ligature(SubSubtables<'a, LigatureSubstFormat1<'a>>),
Contextual(SubSubtables<'a, SubstitutionSequenceContext<'a>>),
ChainContextual(SubSubtables<'a, SubstitutionChainContext<'a>>),
Reverse(SubSubtables<'a, ReverseChainSingleSubstFormat1<'a>>),
}
impl<'a> SubstitutionLookup<'a> {
pub fn lookup_flag(&self) -> LookupFlag {
self.of_unit_type().lookup_flag()
}
/// Different enumerations for GSUB and GPOS
pub fn lookup_type(&self) -> u16 {
self.of_unit_type().lookup_type()
}
pub fn mark_filtering_set(&self) -> Option<u16> {
self.of_unit_type().mark_filtering_set()
}
/// Return the subtables for this lookup.
///
/// This method handles both extension and non-extension lookups, and saves
/// the caller needing to dig into the `SubstitutionLookup` enum itself.
pub fn subtables(&self) -> Result<SubstitutionSubtables<'a>, ReadError> {
let raw_lookup = self.of_unit_type();
let offsets = raw_lookup.subtable_offsets();
let data = raw_lookup.offset_data();
match raw_lookup.lookup_type() {
1 => Ok(SubstitutionSubtables::Single(Subtables::new(offsets, data))),
2 => Ok(SubstitutionSubtables::Multiple(Subtables::new(
offsets, data,
))),
3 => Ok(SubstitutionSubtables::Alternate(Subtables::new(
offsets, data,
))),
4 => Ok(SubstitutionSubtables::Ligature(Subtables::new(
offsets, data,
))),
5 => Ok(SubstitutionSubtables::Contextual(Subtables::new(
offsets, data,
))),
6 => Ok(SubstitutionSubtables::ChainContextual(Subtables::new(
offsets, data,
))),
8 => Ok(SubstitutionSubtables::Reverse(Subtables::new(
offsets, data,
))),
7 => {
let first = offsets.first().ok_or(ReadError::OutOfBounds)?.get();
let ext: ExtensionSubstFormat1<()> = first.resolve(data)?;
match ext.extension_lookup_type() {
1 => Ok(SubstitutionSubtables::Single(Subtables::new_ext(
offsets, data,
))),
2 => Ok(SubstitutionSubtables::Multiple(Subtables::new_ext(
offsets, data,
))),
3 => Ok(SubstitutionSubtables::Alternate(Subtables::new_ext(
offsets, data,
))),
4 => Ok(SubstitutionSubtables::Ligature(Subtables::new_ext(
offsets, data,
))),
5 => Ok(SubstitutionSubtables::Contextual(Subtables::new_ext(
offsets, data,
))),
6 => Ok(SubstitutionSubtables::ChainContextual(Subtables::new_ext(
offsets, data,
))),
8 => Ok(SubstitutionSubtables::Reverse(Subtables::new_ext(
offsets, data,
))),
other => Err(ReadError::InvalidFormat(other as _)),
}
}
other => Err(ReadError::InvalidFormat(other as _)),
}
}
}

View File

@@ -0,0 +1,977 @@
//! Computing the closure over a set of glyphs
//!
//! This means taking a set of glyphs and updating it to include any other glyphs
//! reachable from those glyphs via substitution, recursively.
use font_types::{GlyphId, GlyphId16};
use crate::{
collections::IntSet,
tables::layout::{ExtensionLookup, Subtables},
FontRead, ReadError, Tag,
};
use super::{
AlternateSubstFormat1, ChainedSequenceContext, ClassDef, CoverageTable, Gsub, Ligature,
LigatureSet, LigatureSubstFormat1, MultipleSubstFormat1, ReverseChainSingleSubstFormat1,
SequenceContext, SingleSubst, SingleSubstFormat1, SingleSubstFormat2, SubstitutionLookup,
SubstitutionLookupList, SubstitutionSubtables,
};
#[cfg(feature = "std")]
use crate::tables::layout::{
ContextFormat1, ContextFormat2, ContextFormat3, LookupClosure, LookupClosureCtx,
};
// we put ClosureCtx in its own module to enforce visibility rules;
// specifically we don't want cur_glyphs to be reachable directly
mod ctx {
use std::collections::HashMap;
use types::GlyphId16;
use crate::{collections::IntSet, tables::gsub::SubstitutionLookup};
use super::GlyphClosure as _;
pub(super) struct ClosureCtx<'a> {
/// the current closure glyphs. This is updated as we go.
glyphs: &'a mut IntSet<GlyphId16>,
// in certain situations (like when recursing into contextual lookups) we
// consider a smaller subset of glyphs to be 'active'.
cur_glyphs: Option<IntSet<GlyphId16>>,
finished_lookups: HashMap<u16, (u64, Option<IntSet<GlyphId16>>)>,
// when we encounter contextual lookups we want to visit the lookups
// they reference, but only with the glyphs that would trigger those
// subtable lookups.
//
// here we store tuples of (LookupId, relevant glyphs); these todos can
// be done at the end of each pass.
contextual_lookup_todos: Vec<super::ContextualLookupRef>,
}
impl<'a> ClosureCtx<'a> {
pub(super) fn new(glyphs: &'a mut IntSet<GlyphId16>) -> Self {
Self {
glyphs,
cur_glyphs: Default::default(),
contextual_lookup_todos: Default::default(),
finished_lookups: Default::default(),
}
}
pub(super) fn current_glyphs(&self) -> &IntSet<GlyphId16> {
self.cur_glyphs.as_ref().unwrap_or(self.glyphs)
}
pub(super) fn glyphs(&self) -> &IntSet<GlyphId16> {
self.glyphs
}
pub(super) fn add_glyph(&mut self, gid: GlyphId16) {
self.glyphs.insert(gid);
}
pub(super) fn extend_glyphs(&mut self, iter: impl IntoIterator<Item = GlyphId16>) {
self.glyphs.extend(iter)
}
pub(super) fn add_todo(
&mut self,
lookup_id: u16,
active_glyphs: Option<IntSet<GlyphId16>>,
) {
self.contextual_lookup_todos
.push(super::ContextualLookupRef {
lookup_id,
active_glyphs,
})
}
pub(super) fn pop_a_todo(&mut self) -> Option<super::ContextualLookupRef> {
self.contextual_lookup_todos.pop()
}
pub(super) fn closure_glyphs(
&mut self,
lookup: SubstitutionLookup,
lookup_id: u16,
current_glyphs: Option<IntSet<GlyphId16>>,
) -> Result<(), crate::ReadError> {
if self.needs_to_do_lookup(lookup_id, current_glyphs.as_ref()) {
self.cur_glyphs = current_glyphs;
lookup.add_reachable_glyphs(self)?;
self.cur_glyphs = None;
}
Ok(())
}
/// skip lookups if we've already seen them with our current state
/// <https://github.com/fonttools/fonttools/blob/a6f59a4f87a0111060/Lib/fontTools/subset/__init__.py#L1510>
fn needs_to_do_lookup(
&mut self,
id: u16,
current_glyphs: Option<&IntSet<GlyphId16>>,
) -> bool {
let (count, covered) = self.finished_lookups.entry(id).or_insert((0, None));
if *count != self.glyphs.len() {
*count = self.glyphs.len();
*covered = Some(IntSet::new());
}
//TODO: would be nice to have IntSet::is_subset
if current_glyphs.unwrap_or(self.glyphs).iter().all(|gid| {
covered
.as_ref()
.map(|cov| cov.contains(gid))
// only true if self.glyphs is empty, which means it's a noop anyway?
.unwrap_or(false)
}) {
return false;
}
covered
.get_or_insert_with(Default::default)
.extend(current_glyphs.unwrap_or(self.glyphs).iter());
true
}
}
}
use ctx::ClosureCtx;
/// a lookup referenced by a contextual lookup
#[derive(Debug)]
struct ContextualLookupRef {
lookup_id: u16,
// 'none' means the graph is too complex, assume all glyphs are active
active_glyphs: Option<IntSet<GlyphId16>>,
}
/// A trait for tables which participate in closure
trait GlyphClosure {
/// Update the set of glyphs with any glyphs reachable via substitution.
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError>;
}
impl Gsub<'_> {
/// Return the set of glyphs reachable from the input set via any substitution.
pub fn closure_glyphs(
&self,
mut glyphs: IntSet<GlyphId16>,
) -> Result<IntSet<GlyphId16>, ReadError> {
// we need to do this iteratively, since any glyph found in one pass
// over the lookups could also be the target of substitutions.
let mut ctx = ClosureCtx::new(&mut glyphs);
let reachable_lookups = self.find_reachable_lookups()?;
let mut prev_lookup_count = 0;
let mut prev_glyph_count = 0;
let mut new_glyph_count = ctx.glyphs().len();
let mut new_lookup_count = reachable_lookups.len();
while (prev_glyph_count, prev_lookup_count) != (new_glyph_count, new_lookup_count) {
(prev_glyph_count, prev_lookup_count) = (new_glyph_count, new_lookup_count);
// we always call this once, and then keep calling if it produces
// additional glyphs
self.closure_glyphs_once(&mut ctx, &reachable_lookups)?;
new_lookup_count = reachable_lookups.len();
new_glyph_count = ctx.glyphs().len();
}
Ok(glyphs)
}
fn closure_glyphs_once(
&self,
ctx: &mut ClosureCtx,
lookups_to_use: &IntSet<u16>,
) -> Result<(), ReadError> {
let lookup_list = self.lookup_list()?;
for idx in lookups_to_use.iter() {
let lookup = lookup_list.lookups().get(idx as usize)?;
ctx.closure_glyphs(lookup, idx, None)?;
}
// then do any lookups referenced by contextual lookups
while let Some(todo) = ctx.pop_a_todo() {
let lookup = lookup_list.lookups().get(todo.lookup_id as _)?;
ctx.closure_glyphs(lookup, todo.lookup_id, todo.active_glyphs)?;
}
Ok(())
}
fn find_reachable_lookups(&self) -> Result<IntSet<u16>, ReadError> {
let feature_list = self.feature_list()?;
let mut lookup_ids = IntSet::new();
let feature_variations = self
.feature_variations()
.transpose()?
.map(|vars| {
let data = vars.offset_data();
vars.feature_variation_records()
.iter()
.filter_map(move |rec| {
rec.feature_table_substitution(data)
.transpose()
.ok()
.flatten()
})
.flat_map(|subs| {
subs.substitutions()
.iter()
.map(move |sub| sub.alternate_feature(subs.offset_data()))
})
})
.into_iter()
.flatten();
for feature in feature_list
.feature_records()
.iter()
.map(|rec| rec.feature(feature_list.offset_data()))
.chain(feature_variations)
{
lookup_ids.extend(feature?.lookup_list_indices().iter().map(|idx| idx.get()));
}
Ok(lookup_ids)
}
/// Return a set of all feature indices underneath the specified scripts, languages and features
pub fn collect_features(
&self,
scripts: &IntSet<Tag>,
languages: &IntSet<Tag>,
features: &IntSet<Tag>,
) -> Result<IntSet<u16>, ReadError> {
let feature_list = self.feature_list()?;
let script_list = self.script_list()?;
let head_ptr = self.offset_data().as_bytes().as_ptr() as usize;
script_list.collect_features(head_ptr, &feature_list, scripts, languages, features)
}
}
impl GlyphClosure for SubstitutionLookup<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
self.subtables()?.add_reachable_glyphs(ctx)
}
}
impl GlyphClosure for SubstitutionSubtables<'_> {
fn add_reachable_glyphs(&self, glyphs: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
match self {
SubstitutionSubtables::Single(tables) => tables.add_reachable_glyphs(glyphs),
SubstitutionSubtables::Multiple(tables) => tables.add_reachable_glyphs(glyphs),
SubstitutionSubtables::Alternate(tables) => tables.add_reachable_glyphs(glyphs),
SubstitutionSubtables::Ligature(tables) => tables.add_reachable_glyphs(glyphs),
SubstitutionSubtables::Reverse(tables) => tables.add_reachable_glyphs(glyphs),
SubstitutionSubtables::Contextual(tables) => tables.add_reachable_glyphs(glyphs),
SubstitutionSubtables::ChainContextual(tables) => tables.add_reachable_glyphs(glyphs),
}
}
}
impl<'a, T: FontRead<'a> + GlyphClosure + 'a, Ext: ExtensionLookup<'a, T> + 'a> GlyphClosure
for Subtables<'a, T, Ext>
{
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
self.iter().try_for_each(|t| t?.add_reachable_glyphs(ctx))
}
}
impl GlyphClosure for SingleSubst<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
for (target, replacement) in self.iter_subs()? {
if ctx.current_glyphs().contains(target) {
ctx.add_glyph(replacement);
}
}
Ok(())
}
}
impl SingleSubst<'_> {
fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
let (left, right) = match self {
SingleSubst::Format1(t) => (Some(t.iter_subs()?), None),
SingleSubst::Format2(t) => (None, Some(t.iter_subs()?)),
};
Ok(left
.into_iter()
.flatten()
.chain(right.into_iter().flatten()))
}
}
impl SingleSubstFormat1<'_> {
fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
let delta = self.delta_glyph_id();
let coverage = self.coverage()?;
Ok(coverage.iter().filter_map(move |gid| {
let raw = (gid.to_u16() as i32).checked_add(delta as i32);
let raw = raw.and_then(|raw| u16::try_from(raw).ok())?;
Some((gid, GlyphId16::new(raw)))
}))
}
}
impl SingleSubstFormat2<'_> {
fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
let coverage = self.coverage()?;
let subs = self.substitute_glyph_ids();
Ok(coverage.iter().zip(subs.iter().map(|id| id.get())))
}
}
impl GlyphClosure for MultipleSubstFormat1<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
let coverage = self.coverage()?;
let sequences = self.sequences();
for (gid, replacements) in coverage.iter().zip(sequences.iter()) {
let replacements = replacements?;
if ctx.current_glyphs().contains(gid) {
ctx.extend_glyphs(
replacements
.substitute_glyph_ids()
.iter()
.map(|gid| gid.get()),
);
}
}
Ok(())
}
}
impl GlyphClosure for AlternateSubstFormat1<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
let coverage = self.coverage()?;
let alts = self.alternate_sets();
for (gid, alts) in coverage.iter().zip(alts.iter()) {
let alts = alts?;
if ctx.current_glyphs().contains(gid) {
ctx.extend_glyphs(alts.alternate_glyph_ids().iter().map(|gid| gid.get()));
}
}
Ok(())
}
}
impl GlyphClosure for LigatureSubstFormat1<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
let coverage = self.coverage()?;
let ligs = self.ligature_sets();
for (gid, lig_set) in coverage.iter().zip(ligs.iter()) {
let lig_set = lig_set?;
if ctx.current_glyphs().contains(gid) {
for lig in lig_set.ligatures().iter() {
let lig = lig?;
if lig
.component_glyph_ids()
.iter()
.all(|gid| ctx.glyphs().contains(gid.get()))
{
ctx.add_glyph(lig.ligature_glyph());
}
}
}
}
Ok(())
}
}
impl GlyphClosure for ReverseChainSingleSubstFormat1<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
for coverage in self
.backtrack_coverages()
.iter()
.chain(self.lookahead_coverages().iter())
{
if !coverage?.iter().any(|gid| ctx.glyphs().contains(gid)) {
return Ok(());
}
}
for (gid, sub) in self.coverage()?.iter().zip(self.substitute_glyph_ids()) {
if ctx.current_glyphs().contains(gid) {
ctx.add_glyph(sub.get());
}
}
Ok(())
}
}
impl GlyphClosure for SequenceContext<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
match self {
Self::Format1(table) => ContextFormat1::Plain(table.clone()).add_reachable_glyphs(ctx),
Self::Format2(table) => ContextFormat2::Plain(table.clone()).add_reachable_glyphs(ctx),
Self::Format3(table) => ContextFormat3::Plain(table.clone()).add_reachable_glyphs(ctx),
}
}
}
impl GlyphClosure for ChainedSequenceContext<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
match self {
Self::Format1(table) => ContextFormat1::Chain(table.clone()).add_reachable_glyphs(ctx),
Self::Format2(table) => ContextFormat2::Chain(table.clone()).add_reachable_glyphs(ctx),
Self::Format3(table) => ContextFormat3::Chain(table.clone()).add_reachable_glyphs(ctx),
}
}
}
//https://github.com/fonttools/fonttools/blob/a6f59a4f8/Lib/fontTools/subset/__init__.py#L1182
impl GlyphClosure for ContextFormat1<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
let coverage = self.coverage()?;
let Some(cur_glyphs) = intersect_coverage(&coverage, ctx.current_glyphs()) else {
return Ok(());
};
// now for each rule set that applies to a current glyph:
for (i, seq) in coverage
.iter()
.zip(self.rule_sets())
.enumerate()
.filter_map(|(i, (gid, seq))| {
seq.filter(|_| cur_glyphs.contains(gid)).map(|seq| (i, seq))
})
{
for rule in seq?.rules() {
let rule = rule?;
// skip rules if the whole input sequence isn't in our glyphset
if !rule.matches_glyphs(ctx.glyphs()) {
continue;
}
// python calls this 'chaos'. Basically: if there are multiple
// lookups applied at a single position they can interact, and
// we can no longer trivially determine the state of the context
// at that point. In this case we give up, and assume that the
// second lookup is reachable by all glyphs.
let mut seen_sequence_indices = IntSet::new();
for lookup_record in rule.lookup_records() {
let lookup_id = lookup_record.lookup_list_index();
let sequence_idx = lookup_record.sequence_index();
let active_glyphs = if !seen_sequence_indices.insert(sequence_idx) {
// During processing, when we see an empty set we will replace
// it with the full current glyph set
None
} else if sequence_idx == 0 {
Some(IntSet::from([coverage.iter().nth(i).unwrap()]))
} else {
Some(IntSet::from([rule.input_sequence()
[sequence_idx as usize - 1]
.get()]))
};
ctx.add_todo(lookup_id, active_glyphs);
}
}
}
Ok(())
}
}
//https://github.com/fonttools/fonttools/blob/a6f59a4f87a0111/Lib/fontTools/subset/__init__.py#L1215
impl GlyphClosure for ContextFormat2<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
let coverage = self.coverage()?;
let Some(cur_glyphs) = intersect_coverage(&coverage, ctx.current_glyphs()) else {
return Ok(());
};
let classdef = self.input_class_def()?;
let our_classes = make_class_set(ctx.glyphs(), &classdef);
for (class_i, seq) in self
.rule_sets()
.enumerate()
.filter_map(|(i, seq)| seq.map(|seq| (i as u16, seq)))
.filter(|x| our_classes.contains(x.0))
{
for rule in seq?.rules() {
let rule = rule?;
if !rule.matches_classes(&our_classes) {
continue;
}
let mut seen_sequence_indices = IntSet::new();
for lookup_record in rule.lookup_records() {
let lookup_id = lookup_record.lookup_list_index();
let seq_idx = lookup_record.sequence_index();
let active_glyphs = if !seen_sequence_indices.insert(seq_idx) {
None
} else if seq_idx == 0 {
Some(intersect_class(&classdef, &cur_glyphs, class_i))
} else {
Some(intersect_class(
&classdef,
ctx.glyphs(),
rule.input_sequence()[seq_idx as usize - 1].get(),
))
};
ctx.add_todo(lookup_id, active_glyphs);
}
}
}
Ok(())
}
}
impl GlyphClosure for ContextFormat3<'_> {
fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
let cov0 = self.coverages().get(0)?;
let Some(cur_glyphs) = intersect_coverage(&cov0, ctx.current_glyphs()) else {
return Ok(());
};
let glyphs = ctx.glyphs().iter().map(GlyphId::from).collect();
if !self.matches_glyphs(&glyphs)? {
return Ok(());
}
for record in self.lookup_records() {
let mut seen_sequence_indices = IntSet::new();
let seq_idx = record.sequence_index();
let lookup_id = record.lookup_list_index();
let active_glyphs = if !seen_sequence_indices.insert(seq_idx) {
None
} else if seq_idx == 0 {
Some(cur_glyphs.clone())
} else {
Some(
self.coverages()
.get(seq_idx as _)?
.iter()
.filter(|gid| ctx.glyphs().contains(*gid))
.collect(),
)
};
ctx.add_todo(lookup_id, active_glyphs);
}
Ok(())
}
}
/// The set of classes for this set of glyphs
fn make_class_set(glyphs: &IntSet<GlyphId16>, classdef: &ClassDef) -> IntSet<u16> {
glyphs.iter().map(|gid| classdef.get(gid)).collect()
}
/// Return the subset of `glyphs` that has the given class in this classdef
// https://github.com/fonttools/fonttools/blob/a6f59a4f87a01110/Lib/fontTools/subset/__init__.py#L516
fn intersect_class(
classdef: &ClassDef,
glyphs: &IntSet<GlyphId16>,
class: u16,
) -> IntSet<GlyphId16> {
glyphs
.iter()
.filter(|gid| classdef.get(*gid) == class)
.collect()
}
fn intersect_coverage(
coverage: &CoverageTable,
glyphs: &IntSet<GlyphId16>,
) -> Option<IntSet<GlyphId16>> {
let r = coverage
.iter()
.filter(|gid| glyphs.contains(*gid))
.collect::<IntSet<_>>();
Some(r).filter(|set| !set.is_empty())
}
impl SubstitutionLookupList<'_> {
pub fn closure_lookups(
&self,
glyph_set: &IntSet<GlyphId>,
lookup_indices: &mut IntSet<u16>,
) -> Result<(), ReadError> {
let mut c = LookupClosureCtx::new(glyph_set);
let lookups = self.lookups();
for idx in lookup_indices.iter() {
let lookup = lookups.get(idx as usize)?;
lookup.closure_lookups(&mut c, idx)?;
}
lookup_indices.union(c.visited_lookups());
lookup_indices.subtract(c.inactive_lookups());
Ok(())
}
}
impl LookupClosure for SubstitutionLookup<'_> {
fn closure_lookups(
&self,
c: &mut LookupClosureCtx,
lookup_index: u16,
) -> Result<(), ReadError> {
if !c.should_visit_lookup(lookup_index) {
return Ok(());
}
if !self.intersects(c.glyphs())? {
c.set_lookup_inactive(lookup_index);
return Ok(());
}
let lookup_type = self.lookup_type();
self.subtables()?.closure_lookups(c, lookup_type)
}
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
self.subtables()?.intersects(glyph_set)
}
}
impl LookupClosure for SubstitutionSubtables<'_> {
fn closure_lookups(&self, _c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
Ok(())
}
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
SubstitutionSubtables::Single(subtables) => subtables.intersects(glyph_set),
SubstitutionSubtables::Multiple(subtables) => subtables.intersects(glyph_set),
SubstitutionSubtables::Alternate(subtables) => subtables.intersects(glyph_set),
SubstitutionSubtables::Ligature(subtables) => subtables.intersects(glyph_set),
SubstitutionSubtables::Contextual(subtables) => subtables.intersects(glyph_set),
SubstitutionSubtables::ChainContextual(subtables) => subtables.intersects(glyph_set),
SubstitutionSubtables::Reverse(subtables) => subtables.intersects(glyph_set),
}
}
}
impl LookupClosure for SingleSubst<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
Self::Format1(item) => item.intersects(glyph_set),
Self::Format2(item) => item.intersects(glyph_set),
}
}
}
impl LookupClosure for SingleSubstFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for SingleSubstFormat2<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for MultipleSubstFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for AlternateSubstFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self.coverage()?.intersects(glyph_set))
}
}
impl LookupClosure for LigatureSubstFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let coverage = self.coverage()?;
let lig_sets = self.ligature_sets();
for lig_set in coverage
.iter()
.zip(lig_sets.iter())
.filter_map(|(g, lig_set)| glyph_set.contains(GlyphId::from(g)).then_some(lig_set))
{
if lig_set?.intersects(glyph_set)? {
return Ok(true);
}
}
Ok(false)
}
}
impl LookupClosure for LigatureSet<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let ligs = self.ligatures();
for lig in ligs.iter() {
if lig?.intersects(glyph_set)? {
return Ok(true);
}
}
Ok(false)
}
}
impl LookupClosure for Ligature<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let ret = self
.component_glyph_ids()
.iter()
.all(|g| glyph_set.contains(GlyphId::from(g.get())));
Ok(ret)
}
}
impl LookupClosure for ReverseChainSingleSubstFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
if !self.coverage()?.intersects(glyph_set) {
return Ok(false);
}
for coverage in self.backtrack_coverages().iter() {
if !coverage?.intersects(glyph_set) {
return Ok(false);
}
}
for coverage in self.lookahead_coverages().iter() {
if !coverage?.intersects(glyph_set) {
return Ok(false);
}
}
Ok(true)
}
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, HashSet};
use crate::{FontRef, TableProvider};
use super::*;
use font_test_data::closure as test_data;
struct GlyphMap {
to_gid: HashMap<&'static str, GlyphId16>,
from_gid: HashMap<GlyphId16, &'static str>,
}
impl GlyphMap {
fn new(raw_order: &'static str) -> GlyphMap {
let to_gid: HashMap<_, _> = raw_order
.split('\n')
.map(|line| line.trim())
.filter(|line| !(line.starts_with('#') || line.is_empty()))
.enumerate()
.map(|(gid, name)| (name, GlyphId16::new(gid.try_into().unwrap())))
.collect();
let from_gid = to_gid.iter().map(|(name, gid)| (*gid, *name)).collect();
GlyphMap { from_gid, to_gid }
}
fn get_gid(&self, name: &str) -> Option<GlyphId16> {
self.to_gid.get(name).copied()
}
fn get_name(&self, gid: GlyphId16) -> Option<&str> {
self.from_gid.get(&gid).copied()
}
}
fn get_gsub(test_data: &'static [u8]) -> Gsub<'static> {
let font = FontRef::new(test_data).unwrap();
font.gsub().unwrap()
}
fn compute_closure(gsub: &Gsub, glyph_map: &GlyphMap, input: &[&str]) -> IntSet<GlyphId16> {
let input_glyphs = input
.iter()
.map(|name| glyph_map.get_gid(name).unwrap())
.collect();
gsub.closure_glyphs(input_glyphs).unwrap()
}
/// assert a set of glyph ids matches a slice of names
macro_rules! assert_closure_result {
($glyph_map:expr, $result:expr, $expected:expr) => {
let result = $result
.iter()
.map(|gid| $glyph_map.get_name(gid).unwrap())
.collect::<HashSet<_>>();
let expected = $expected.iter().copied().collect::<HashSet<_>>();
if expected != result {
let in_output = result.difference(&expected).collect::<Vec<_>>();
let in_expected = expected.difference(&result).collect::<Vec<_>>();
let mut msg = format!("Closure output does not match\n");
if !in_expected.is_empty() {
msg.push_str(format!("missing {in_expected:?}\n").as_str());
}
if !in_output.is_empty() {
msg.push_str(format!("unexpected {in_output:?}").as_str());
}
panic!("{msg}")
}
};
}
#[test]
fn smoke_test() {
// tests various lookup types.
// test input is font-test-data/test_data/fea/simple_closure.fea
let gsub = get_gsub(test_data::SIMPLE);
let glyph_map = GlyphMap::new(test_data::SIMPLE_GLYPHS);
let result = compute_closure(&gsub, &glyph_map, &["a"]);
assert_closure_result!(
glyph_map,
result,
&["a", "A", "b", "c", "d", "a_a", "a.1", "a.2", "a.3"]
);
}
#[test]
fn recursive() {
// a scenario in which one substitution adds glyphs that trigger additional
// substitutions.
//
// test input is font-test-data/test_data/fea/recursive_closure.fea
let gsub = get_gsub(test_data::RECURSIVE);
let glyph_map = GlyphMap::new(test_data::RECURSIVE_GLYPHS);
let result = compute_closure(&gsub, &glyph_map, &["a"]);
assert_closure_result!(glyph_map, result, &["a", "b", "c", "d"]);
}
#[test]
fn contextual_lookups_nop() {
let gsub = get_gsub(test_data::CONTEXTUAL);
let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
// these match the lookups but not the context
let nop = compute_closure(&gsub, &glyph_map, &["three", "four", "e", "f"]);
assert_closure_result!(glyph_map, nop, &["three", "four", "e", "f"]);
}
#[test]
fn contextual_lookups_chained_f1() {
let gsub = get_gsub(test_data::CONTEXTUAL);
let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
let gsub6f1 = compute_closure(
&gsub,
&glyph_map,
&["one", "two", "three", "four", "five", "six", "seven"],
);
assert_closure_result!(
glyph_map,
gsub6f1,
&["one", "two", "three", "four", "five", "six", "seven", "X", "Y"]
);
}
#[test]
fn contextual_lookups_chained_f3() {
let gsub = get_gsub(test_data::CONTEXTUAL);
let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
let gsub6f3 = compute_closure(&gsub, &glyph_map, &["space", "e"]);
assert_closure_result!(glyph_map, gsub6f3, &["space", "e", "e.2"]);
let gsub5f3 = compute_closure(&gsub, &glyph_map, &["f", "g"]);
assert_closure_result!(glyph_map, gsub5f3, &["f", "g", "f.2"]);
}
#[test]
fn contextual_plain_f1() {
let gsub = get_gsub(test_data::CONTEXTUAL);
let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
let gsub5f1 = compute_closure(&gsub, &glyph_map, &["a", "b"]);
assert_closure_result!(glyph_map, gsub5f1, &["a", "b", "a_b"]);
}
#[test]
fn contextual_plain_f3() {
let gsub = get_gsub(test_data::CONTEXTUAL);
let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
let gsub5f3 = compute_closure(&gsub, &glyph_map, &["f", "g"]);
assert_closure_result!(glyph_map, gsub5f3, &["f", "g", "f.2"]);
}
#[test]
fn recursive_context() {
let gsub = get_gsub(test_data::RECURSIVE_CONTEXTUAL);
let glyph_map = GlyphMap::new(test_data::RECURSIVE_CONTEXTUAL_GLYPHS);
let nop = compute_closure(&gsub, &glyph_map, &["b", "B"]);
assert_closure_result!(glyph_map, nop, &["b", "B"]);
let full = compute_closure(&gsub, &glyph_map, &["a", "b", "c"]);
assert_closure_result!(glyph_map, full, &["a", "b", "c", "B", "B.2", "B.3"]);
let intermediate = compute_closure(&gsub, &glyph_map, &["a", "B.2"]);
assert_closure_result!(glyph_map, intermediate, &["a", "B.2", "B.3"]);
}
#[test]
fn feature_variations() {
let gsub = get_gsub(test_data::VARIATIONS_CLOSURE);
let glyph_map = GlyphMap::new(test_data::VARIATIONS_GLYPHS);
let input = compute_closure(&gsub, &glyph_map, &["a"]);
assert_closure_result!(glyph_map, input, &["a", "b", "c"]);
}
#[test]
fn context_with_unreachable_rules() {
let gsub = get_gsub(test_data::CONTEXT_WITH_UNREACHABLE_BITS);
let glyph_map = GlyphMap::new(test_data::CONTEXT_WITH_UNREACHABLE_BITS_GLYPHS);
let nop = compute_closure(&gsub, &glyph_map, &["c", "z"]);
assert_closure_result!(glyph_map, nop, &["c", "z"]);
let full = compute_closure(&gsub, &glyph_map, &["a", "b", "c", "z"]);
assert_closure_result!(glyph_map, full, &["a", "b", "c", "z", "A", "B"]);
}
#[test]
fn cyclical_context() {
let gsub = get_gsub(test_data::CYCLIC_CONTEXTUAL);
let glyph_map = GlyphMap::new(test_data::RECURSIVE_CONTEXTUAL_GLYPHS);
// we mostly care that this terminates
let nop = compute_closure(&gsub, &glyph_map, &["a", "b", "c"]);
assert_closure_result!(glyph_map, nop, &["a", "b", "c"]);
}
#[test]
fn collect_all_features() {
let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
let gsub = font.gsub().unwrap();
let ret = gsub
.collect_features(&IntSet::all(), &IntSet::all(), &IntSet::all())
.unwrap();
assert_eq!(ret.len(), 2);
assert!(ret.contains(0));
assert!(ret.contains(1));
}
#[test]
fn collect_all_features_with_feature_filter() {
let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
let gsub = font.gsub().unwrap();
let mut feature_tags = IntSet::empty();
feature_tags.insert(Tag::new(b"SUB5"));
let ret = gsub
.collect_features(&IntSet::all(), &IntSet::all(), &feature_tags)
.unwrap();
assert_eq!(ret.len(), 1);
assert!(ret.contains(0));
}
#[test]
fn collect_all_features_with_script_filter() {
let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
let gsub = font.gsub().unwrap();
let mut script_tags = IntSet::empty();
script_tags.insert(Tag::new(b"LATN"));
let ret = gsub
.collect_features(&script_tags, &IntSet::all(), &IntSet::all())
.unwrap();
assert!(ret.is_empty());
}
}

575
vendor/read-fonts/src/tables/gvar.rs vendored Normal file
View File

@@ -0,0 +1,575 @@
//! The [gvar (Glyph Variations)](https://learn.microsoft.com/en-us/typography/opentype/spec/gvar)
//! table
include!("../../generated/generated_gvar.rs");
use super::{
glyf::{CompositeGlyphFlags, Glyf, Glyph, PointCoord},
loca::Loca,
variations::{
PackedPointNumbers, Tuple, TupleDelta, TupleVariationCount, TupleVariationData,
TupleVariationHeader,
},
};
/// Variation data specialized for the glyph variations table.
pub type GlyphVariationData<'a> = TupleVariationData<'a, GlyphDelta>;
#[derive(Clone, Copy, Debug)]
pub struct U16Or32(u32);
impl ReadArgs for U16Or32 {
type Args = GvarFlags;
}
impl ComputeSize for U16Or32 {
fn compute_size(args: &GvarFlags) -> Result<usize, ReadError> {
Ok(if args.contains(GvarFlags::LONG_OFFSETS) {
4
} else {
2
})
}
}
impl FontReadWithArgs<'_> for U16Or32 {
fn read_with_args(data: FontData<'_>, args: &Self::Args) -> Result<Self, ReadError> {
if args.contains(GvarFlags::LONG_OFFSETS) {
data.read_at::<u32>(0).map(Self)
} else {
data.read_at::<u16>(0).map(|v| Self(v as u32 * 2))
}
}
}
impl U16Or32 {
#[inline]
pub fn get(self) -> u32 {
self.0
}
}
impl<'a> GlyphVariationDataHeader<'a> {
fn raw_tuple_header_data(&self) -> FontData<'a> {
let range = self.shape.tuple_variation_headers_byte_range();
self.data.split_off(range.start).unwrap()
}
}
impl<'a> Gvar<'a> {
/// Return the raw data for this gid.
///
/// If there is no variation data for the glyph, returns `Ok(None)`.
pub fn data_for_gid(&self, gid: GlyphId) -> Result<Option<FontData<'a>>, ReadError> {
let range = self.data_range_for_gid(gid)?;
if range.is_empty() {
return Ok(None);
}
match self.data.slice(range) {
Some(data) => Ok(Some(data)),
None => Err(ReadError::OutOfBounds),
}
}
pub fn glyph_variation_data_for_range(
&self,
offset_range: Range<usize>,
) -> Result<FontData<'a>, ReadError> {
let base = self.glyph_variation_data_array_offset() as usize;
let start = base
.checked_add(offset_range.start)
.ok_or(ReadError::OutOfBounds)?;
let end = base
.checked_add(offset_range.end)
.ok_or(ReadError::OutOfBounds)?;
self.data.slice(start..end).ok_or(ReadError::OutOfBounds)
}
pub fn as_bytes(&self) -> &[u8] {
self.data.as_bytes()
}
fn data_range_for_gid(&self, gid: GlyphId) -> Result<Range<usize>, ReadError> {
let start_idx = gid.to_u32() as usize;
let end_idx = start_idx + 1;
let data_start = self.glyph_variation_data_array_offset();
let start =
data_start.checked_add(self.glyph_variation_data_offsets().get(start_idx)?.get());
let end = data_start.checked_add(self.glyph_variation_data_offsets().get(end_idx)?.get());
let (Some(start), Some(end)) = (start, end) else {
return Err(ReadError::OutOfBounds);
};
Ok(start as usize..end as usize)
}
/// Get the variation data for a specific glyph.
///
/// Returns `Ok(None)` if there is no variation data for this glyph, and
/// returns an error if there is data but it is malformed.
pub fn glyph_variation_data(
&self,
gid: GlyphId,
) -> Result<Option<GlyphVariationData<'a>>, ReadError> {
let shared_tuples = self.shared_tuples()?;
let axis_count = self.axis_count();
let data = self.data_for_gid(gid)?;
data.map(|data| GlyphVariationData::new(data, axis_count, shared_tuples))
.transpose()
}
/// Returns the phantom point deltas for the given variation coordinates
/// and glyph identifier, if variation data exists for the glyph.
///
/// The resulting array will contain four deltas:
/// `[left, right, top, bottom]`.
pub fn phantom_point_deltas(
&self,
glyf: &Glyf,
loca: &Loca,
coords: &[F2Dot14],
glyph_id: GlyphId,
) -> Result<Option<[Point<Fixed>; 4]>, ReadError> {
// For any given glyph, there's only one outline that contributes to
// metrics deltas (via "phantom points"). For simple glyphs, that is
// the glyph itself. For composite glyphs, it is the first component
// in the tree that has the USE_MY_METRICS flag set or, if there are
// none, the composite glyph itself.
//
// This searches for the glyph that meets that criteria and also
// returns the point count (for composites, this is the component
// count), so that we know where the deltas for phantom points start
// in the variation data.
let (glyph_id, point_count) = find_glyph_and_point_count(glyf, loca, glyph_id, 0)?;
let mut phantom_deltas = [Point::default(); 4];
let phantom_range = point_count..point_count + 4;
let Some(var_data) = self.glyph_variation_data(glyph_id)? else {
return Ok(None);
};
// Note that phantom points can never belong to a contour so we don't have
// to handle the IUP case here.
for (tuple, scalar) in var_data.active_tuples_at(coords) {
for tuple_delta in tuple.deltas() {
let ix = tuple_delta.position as usize;
if phantom_range.contains(&ix) {
phantom_deltas[ix - phantom_range.start] += tuple_delta.apply_scalar(scalar);
}
}
}
Ok(Some(phantom_deltas))
}
}
impl<'a> GlyphVariationData<'a> {
pub(crate) fn new(
data: FontData<'a>,
axis_count: u16,
shared_tuples: SharedTuples<'a>,
) -> Result<Self, ReadError> {
let header = GlyphVariationDataHeader::read(data)?;
let header_data = header.raw_tuple_header_data();
let count = header.tuple_variation_count();
let data = header.serialized_data()?;
// if there are shared point numbers, get them now
let (shared_point_numbers, serialized_data) =
if header.tuple_variation_count().shared_point_numbers() {
let (packed, data) = PackedPointNumbers::split_off_front(data);
(Some(packed), data)
} else {
(None, data)
};
Ok(GlyphVariationData {
tuple_count: count,
axis_count,
shared_tuples: Some(shared_tuples.tuples()),
shared_point_numbers,
header_data,
serialized_data,
_marker: std::marker::PhantomData,
})
}
}
/// Delta information for a single point or component in a glyph.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GlyphDelta {
/// The point or component index.
pub position: u16,
/// The x delta.
pub x_delta: i32,
/// The y delta.
pub y_delta: i32,
}
impl GlyphDelta {
/// Applies a tuple scalar to this delta.
pub fn apply_scalar<D: PointCoord>(self, scalar: Fixed) -> Point<D> {
let scalar = D::from_fixed(scalar);
Point::new(self.x_delta, self.y_delta).map(D::from_i32) * scalar
}
}
impl TupleDelta for GlyphDelta {
fn is_point() -> bool {
true
}
fn new(position: u16, x: i32, y: i32) -> Self {
Self {
position,
x_delta: x,
y_delta: y,
}
}
}
/// Given a glyph identifier, searches for the glyph that contains the actual
/// metrics for rendering.
///
/// For simple glyphs, that is simply the requested glyph. For composites, it
/// depends on the USE_MY_METRICS flag.
///
/// Returns the resulting glyph identifier and the number of points (or
/// components) in that glyph. This count represents the start of the phantom
/// points.
fn find_glyph_and_point_count(
glyf: &Glyf,
loca: &Loca,
glyph_id: GlyphId,
recurse_depth: usize,
) -> Result<(GlyphId, usize), ReadError> {
// Matches HB's nesting limit
const RECURSION_LIMIT: usize = 64;
if recurse_depth > RECURSION_LIMIT {
return Err(ReadError::MalformedData(
"nesting too deep in composite glyph",
));
}
let glyph = loca.get_glyf(glyph_id, glyf)?;
let Some(glyph) = glyph else {
// Empty glyphs might still contain gvar data that
// only affects phantom points
return Ok((glyph_id, 0));
};
match glyph {
Glyph::Simple(simple) => {
// Simple glyphs always use their own metrics
Ok((glyph_id, simple.num_points()))
}
Glyph::Composite(composite) => {
// For composite glyphs, if one of the components has the
// USE_MY_METRICS flag set, recurse into the glyph referenced
// by that component. Otherwise, return the composite glyph
// itself and the number of components as the point count.
let mut count = 0;
for component in composite.components() {
count += 1;
if component
.flags
.contains(CompositeGlyphFlags::USE_MY_METRICS)
{
return find_glyph_and_point_count(
glyf,
loca,
component.glyph.into(),
recurse_depth + 1,
);
}
}
Ok((glyph_id, count))
}
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
use crate::{FontRef, TableProvider};
// Shared tuples in the 'gvar' table of the Skia font, as printed
// in Apple's TrueType specification.
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
static SKIA_GVAR_SHARED_TUPLES_DATA: FontData = FontData::new(&[
0x40, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xC0,
0x00, 0xC0, 0x00, 0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x00,
0x40, 0x00,
]);
static SKIA_GVAR_I_DATA: FontData = FontData::new(&[
0x00, 0x08, 0x00, 0x24, 0x00, 0x33, 0x20, 0x00, 0x00, 0x15, 0x20, 0x01, 0x00, 0x1B, 0x20,
0x02, 0x00, 0x24, 0x20, 0x03, 0x00, 0x15, 0x20, 0x04, 0x00, 0x26, 0x20, 0x07, 0x00, 0x0D,
0x20, 0x06, 0x00, 0x1A, 0x20, 0x05, 0x00, 0x40, 0x01, 0x01, 0x01, 0x81, 0x80, 0x43, 0xFF,
0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0x00, 0x81, 0x45, 0x01, 0x01, 0x01, 0x03, 0x01,
0x04, 0x01, 0x04, 0x01, 0x04, 0x01, 0x02, 0x80, 0x40, 0x00, 0x82, 0x81, 0x81, 0x04, 0x3A,
0x5A, 0x3E, 0x43, 0x20, 0x81, 0x04, 0x0E, 0x40, 0x15, 0x45, 0x7C, 0x83, 0x00, 0x0D, 0x9E,
0xF3, 0xF2, 0xF0, 0xF0, 0xF0, 0xF0, 0xF3, 0x9E, 0xA0, 0xA1, 0xA1, 0xA1, 0x9F, 0x80, 0x00,
0x91, 0x81, 0x91, 0x00, 0x0D, 0x0A, 0x0A, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,
0x0A, 0x0A, 0x0A, 0x0B, 0x80, 0x00, 0x15, 0x81, 0x81, 0x00, 0xC4, 0x89, 0x00, 0xC4, 0x83,
0x00, 0x0D, 0x80, 0x99, 0x98, 0x96, 0x96, 0x96, 0x96, 0x99, 0x80, 0x82, 0x83, 0x83, 0x83,
0x81, 0x80, 0x40, 0xFF, 0x18, 0x81, 0x81, 0x04, 0xE6, 0xF9, 0x10, 0x21, 0x02, 0x81, 0x04,
0xE8, 0xE5, 0xEB, 0x4D, 0xDA, 0x83, 0x00, 0x0D, 0xCE, 0xD3, 0xD4, 0xD3, 0xD3, 0xD3, 0xD5,
0xD2, 0xCE, 0xCC, 0xCD, 0xCD, 0xCD, 0xCD, 0x80, 0x00, 0xA1, 0x81, 0x91, 0x00, 0x0D, 0x07,
0x03, 0x04, 0x02, 0x02, 0x02, 0x03, 0x03, 0x07, 0x07, 0x08, 0x08, 0x08, 0x07, 0x80, 0x00,
0x09, 0x81, 0x81, 0x00, 0x28, 0x40, 0x00, 0xA4, 0x02, 0x24, 0x24, 0x66, 0x81, 0x04, 0x08,
0xFA, 0xFA, 0xFA, 0x28, 0x83, 0x00, 0x82, 0x02, 0xFF, 0xFF, 0xFF, 0x83, 0x02, 0x01, 0x01,
0x01, 0x84, 0x91, 0x00, 0x80, 0x06, 0x07, 0x08, 0x08, 0x08, 0x08, 0x0A, 0x07, 0x80, 0x03,
0xFE, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x08, 0x81, 0x82, 0x02, 0xEE, 0xEE, 0xEE, 0x8B, 0x6D,
0x00,
]);
#[test]
fn test_shared_tuples() {
#[allow(overflowing_literals)]
const MINUS_ONE: F2Dot14 = F2Dot14::from_bits(0xC000);
assert_eq!(MINUS_ONE, F2Dot14::from_f32(-1.0));
static EXPECTED: &[(F2Dot14, F2Dot14)] = &[
(F2Dot14::ONE, F2Dot14::ZERO),
(MINUS_ONE, F2Dot14::ZERO),
(F2Dot14::ZERO, F2Dot14::ONE),
(F2Dot14::ZERO, MINUS_ONE),
(MINUS_ONE, MINUS_ONE),
(F2Dot14::ONE, MINUS_ONE),
(F2Dot14::ONE, F2Dot14::ONE),
(MINUS_ONE, F2Dot14::ONE),
];
const N_AXES: u16 = 2;
let tuples =
SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, EXPECTED.len() as u16, N_AXES)
.unwrap();
let tuple_vec: Vec<_> = tuples
.tuples()
.iter()
.map(|tup| {
let values = tup.unwrap().values();
assert_eq!(values.len(), N_AXES as usize);
(values[0].get(), values[1].get())
})
.collect();
assert_eq!(tuple_vec, EXPECTED);
}
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
#[test]
fn smoke_test() {
let header = GlyphVariationDataHeader::read(SKIA_GVAR_I_DATA).unwrap();
assert_eq!(header.serialized_data_offset(), 36);
assert_eq!(header.tuple_variation_count().count(), 8);
let shared_tuples = SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, 8, 2).unwrap();
let vardata = GlyphVariationData::new(SKIA_GVAR_I_DATA, 2, shared_tuples).unwrap();
assert_eq!(vardata.tuple_count(), 8);
let deltas = vardata
.tuples()
.next()
.unwrap()
.deltas()
.collect::<Vec<_>>();
assert_eq!(deltas.len(), 18);
static EXPECTED: &[(i32, i32)] = &[
(257, 0),
(-127, 0),
(-128, 58),
(-130, 90),
(-130, 62),
(-130, 67),
(-130, 32),
(-127, 0),
(257, 0),
(259, 14),
(260, 64),
(260, 21),
(260, 69),
(258, 124),
(0, 0),
(130, 0),
(0, 0),
(0, 0),
];
let expected = EXPECTED
.iter()
.copied()
.enumerate()
.map(|(pos, (x_delta, y_delta))| GlyphDelta {
position: pos as _,
x_delta,
y_delta,
})
.collect::<Vec<_>>();
for (a, b) in deltas.iter().zip(expected.iter()) {
assert_eq!(a, b);
}
}
#[test]
fn vazirmatn_var_a() {
let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
.unwrap()
.gvar()
.unwrap();
let a_glyph_var = gvar.glyph_variation_data(GlyphId::new(1)).unwrap().unwrap();
assert_eq!(a_glyph_var.axis_count, 1);
let mut tuples = a_glyph_var.tuples();
let tup1 = tuples.next().unwrap();
assert_eq!(tup1.peak().values(), &[F2Dot14::from_f32(-1.0)]);
assert_eq!(tup1.deltas().count(), 18);
let x_vals = &[
-90, -134, 4, -6, -81, 18, -25, -33, -109, -121, -111, -111, -22, -22, 0, -113, 0, 0,
];
let y_vals = &[
83, 0, 0, 0, 0, 0, 83, 0, 0, 0, -50, 54, 54, -50, 0, 0, -21, 0,
];
assert_eq!(tup1.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
assert_eq!(tup1.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
let tup2 = tuples.next().unwrap();
assert_eq!(tup2.peak().values(), &[F2Dot14::from_f32(1.0)]);
let x_vals = &[
20, 147, -33, -53, 59, -90, 37, -6, 109, 90, -79, -79, -8, -8, 0, 59, 0, 0,
];
let y_vals = &[
-177, 0, 0, 0, 0, 0, -177, 0, 0, 0, 4, -109, -109, 4, 0, 0, 9, 0,
];
assert_eq!(tup2.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
assert_eq!(tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
assert!(tuples.next().is_none());
}
#[test]
fn vazirmatn_var_agrave() {
let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
.unwrap()
.gvar()
.unwrap();
let agrave_glyph_var = gvar.glyph_variation_data(GlyphId::new(2)).unwrap().unwrap();
let mut tuples = agrave_glyph_var.tuples();
let tup1 = tuples.next().unwrap();
assert_eq!(
tup1.deltas()
.map(|d| (d.position, d.x_delta, d.y_delta))
.collect::<Vec<_>>(),
&[(1, -51, 8), (3, -113, 0)]
);
let tup2 = tuples.next().unwrap();
assert_eq!(
tup2.deltas()
.map(|d| (d.position, d.x_delta, d.y_delta))
.collect::<Vec<_>>(),
&[(1, -54, -1), (3, 59, 0)]
);
}
#[test]
fn vazirmatn_var_grave() {
let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
.unwrap()
.gvar()
.unwrap();
let grave_glyph_var = gvar.glyph_variation_data(GlyphId::new(3)).unwrap().unwrap();
let mut tuples = grave_glyph_var.tuples();
let tup1 = tuples.next().unwrap();
let tup2 = tuples.next().unwrap();
assert!(tuples.next().is_none());
assert_eq!(tup1.deltas().count(), 8);
assert_eq!(
tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(),
&[0, -20, -20, 0, 0, 0, 0, 0]
);
}
#[test]
fn phantom_point_deltas() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
#[rustfmt::skip]
let a_cases = [
// (coords, deltas)
(&[0.0], [(0.0, 0.0); 4]),
(&[1.0], [(0.0, 0.0), (59.0, 0.0), (0.0, 9.0), (0.0, 0.0)]),
(&[-1.0], [(0.0, 0.0), (-113.0, 0.0), (0.0, -21.0), (0.0, 0.0)]),
(&[0.5], [(0.0, 0.0), (29.5, 0.0), (0.0, 4.5), (0.0, 0.0)]),
(&[-0.5], [(0.0, 0.0), (-56.5, 0.0), (0.0, -10.5), (0.0, 0.0)]),
];
for (coords, deltas) in a_cases {
// This is simple glyph "A"
assert_eq!(
compute_phantom_deltas(&font, coords, GlyphId::new(1)),
deltas
);
// This is composite glyph "Agrave" with USE_MY_METRICS set on "A" so
// the deltas are the same
assert_eq!(
compute_phantom_deltas(&font, coords, GlyphId::new(2)),
deltas
);
}
#[rustfmt::skip]
let grave_cases = [
// (coords, deltas)
(&[0.0], [(0.0, 0.0); 4]),
(&[1.0], [(0.0, 0.0), (63.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
(&[-1.0], [(0.0, 0.0), (-96.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
(&[0.5], [(0.0, 0.0), (31.5, 0.0), (0.0, 0.0), (0.0, 0.0)]),
(&[-0.5], [(0.0, 0.0), (-48.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
];
// This is simple glyph "grave"
for (coords, deltas) in grave_cases {
assert_eq!(
compute_phantom_deltas(&font, coords, GlyphId::new(3)),
deltas
);
}
}
fn compute_phantom_deltas(
font: &FontRef,
coords: &[f32],
glyph_id: GlyphId,
) -> [(f32, f32); 4] {
let loca = font.loca(None).unwrap();
let glyf = font.glyf().unwrap();
let gvar = font.gvar().unwrap();
let coords = coords
.iter()
.map(|coord| F2Dot14::from_f32(*coord))
.collect::<Vec<_>>();
gvar.phantom_point_deltas(&glyf, &loca, &coords, glyph_id)
.unwrap()
.unwrap()
.map(|delta| delta.map(Fixed::to_f32))
.map(|p| (p.x, p.y))
}
// fuzzer: add with overflow when computing glyph data range
// ref: <https://g-issues.oss-fuzz.com/issues/385918147>
#[test]
fn avoid_data_range_overflow() {
// Construct a gvar table with data offsets that overflow
// a u32
let mut buf = BeBuffer::new();
// major/minor version
buf = buf.push(1u16).push(0u16);
// axis count
buf = buf.push(0u16);
// shared tuple count and offset
buf = buf.push(0u16).push(0u32);
// glyph count = 1
buf = buf.push(1u16);
// flags, bit 1 = 32 bit offsets
buf = buf.push(1u16);
// variation data offset
buf = buf.push(u32::MAX - 10);
// two 32-bit entries that overflow when added to the above offset
buf = buf.push(0u32).push(11u32);
let gvar = Gvar::read(buf.data().into()).unwrap();
// don't panic with overflow!
let _ = gvar.data_range_for_gid(GlyphId::new(0));
}
}

180
vendor/read-fonts/src/tables/hdmx.rs vendored Normal file
View File

@@ -0,0 +1,180 @@
//! The [Horizontal Device Metrics](https://learn.microsoft.com/en-us/typography/opentype/spec/hdmx) table.
include!("../../generated/generated_hdmx.rs");
use std::cmp::Ordering;
impl<'a> Hdmx<'a> {
/// Returns for the device record that exactly matches the given
/// size (as ppem).
pub fn record_for_size(&self, size: u8) -> Option<DeviceRecord<'a>> {
let records = self.records();
// Need a custom binary search because we're working with
// ComputedArray
let mut lo = 0;
let mut hi = records.len();
while lo < hi {
let mid = (lo + hi) / 2;
let record = records.get(mid).ok()?;
match record.pixel_size.cmp(&size) {
Ordering::Less => lo = mid + 1,
Ordering::Greater => hi = mid,
Ordering::Equal => return Some(record),
}
}
None
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceRecord<'a> {
/// Pixel size for following widths (as ppem).
pub pixel_size: u8,
/// Maximum width.
pub max_width: u8,
/// Array of glyphs (numGlyphs is from the 'maxp' table).
pub widths: &'a [u8],
}
impl<'a> DeviceRecord<'a> {
/// Pixel size for following widths (as ppem).
pub fn pixel_size(&self) -> u8 {
self.pixel_size
}
/// Maximum width.
pub fn max_width(&self) -> u8 {
self.max_width
}
/// Array of widths, indexed by glyph id.
pub fn widths(&self) -> &'a [u8] {
self.widths
}
}
impl ReadArgs for DeviceRecord<'_> {
type Args = (u16, u32);
}
impl ComputeSize for DeviceRecord<'_> {
fn compute_size(args: &(u16, u32)) -> Result<usize, ReadError> {
let (_num_glyphs, size_device_record) = *args;
// Record size is explicitly defined in the parent hdmx table
Ok(size_device_record as usize)
}
}
impl<'a> FontReadWithArgs<'a> for DeviceRecord<'a> {
fn read_with_args(data: FontData<'a>, args: &(u16, u32)) -> Result<Self, ReadError> {
let mut cursor = data.cursor();
let (num_glyphs, _size_device_record) = *args;
Ok(Self {
pixel_size: cursor.read()?,
max_width: cursor.read()?,
widths: cursor.read_array(num_glyphs as usize)?,
})
}
}
#[allow(clippy::needless_lifetimes)]
impl<'a> DeviceRecord<'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,
size_device_record: u32,
) -> Result<Self, ReadError> {
let args = (num_glyphs, size_device_record);
Self::read_with_args(data, &args)
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeRecord<'a> for DeviceRecord<'a> {
fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
RecordResolver {
name: "DeviceRecord",
get_field: Box::new(move |idx, _data| match idx {
0usize => Some(Field::new("pixel_size", self.pixel_size())),
1usize => Some(Field::new("max_width", self.max_width())),
2usize => Some(Field::new("widths", self.widths())),
_ => None,
}),
data,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::{be_buffer, bebuffer::BeBuffer};
#[test]
fn read_hdmx() {
let buf = make_hdmx();
let hdmx = Hdmx::read(buf.data().into(), 3).unwrap();
assert_eq!(hdmx.version(), 0);
assert_eq!(hdmx.num_records(), 3);
// Note: this table has sizes for 3 glyphs making each device
// record 5 bytes in actual length, but each entry in the array
// should be aligned to 32-bits so the size is bumped to 8 bytes
//
// See the HdmxHeader::sizeDeviceRecord field at
// <https://learn.microsoft.com/en-us/typography/opentype/spec/hdmx>
// "Size of a device record, 32-bit aligned."
assert_eq!(hdmx.size_device_record(), 8);
let records = hdmx
.records()
.iter()
.map(|rec| rec.unwrap())
.collect::<Vec<_>>();
assert_eq!(records.len(), 3);
let expected_records = [
DeviceRecord {
pixel_size: 8,
max_width: 13,
widths: &[10, 12, 13],
},
DeviceRecord {
pixel_size: 16,
max_width: 21,
widths: &[18, 20, 21],
},
DeviceRecord {
pixel_size: 32,
max_width: 52,
widths: &[38, 40, 52],
},
];
assert_eq!(records, expected_records);
}
#[test]
fn find_by_size() {
let buf = make_hdmx();
let hdmx = Hdmx::read(buf.data().into(), 3).unwrap();
assert_eq!(hdmx.record_for_size(8).unwrap().pixel_size, 8);
assert_eq!(hdmx.record_for_size(16).unwrap().pixel_size, 16);
assert_eq!(hdmx.record_for_size(32).unwrap().pixel_size, 32);
assert!(hdmx.record_for_size(7).is_none());
assert!(hdmx.record_for_size(20).is_none());
assert!(hdmx.record_for_size(72).is_none());
}
fn make_hdmx() -> BeBuffer {
be_buffer! {
0u16, // version
3u16, // num_records
8u32, // size_device_record
// 3 records [pixel_size, max_width, width0, width1, ..padding]
[8u8, 13, 10, 12, 13, 0, 0, 0],
[16u8, 21, 18, 20, 21, 0, 0, 0],
[32u8, 52, 38, 40, 52, 0, 0, 0]
}
}
}

30
vendor/read-fonts/src/tables/head.rs vendored Normal file
View File

@@ -0,0 +1,30 @@
//! The [head](https://docs.microsoft.com/en-us/typography/opentype/spec/head) table
include!("../../generated/generated_head.rs");
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn smoke_text() {
let buf = BeBuffer::new()
.extend([1u16, 0u16])
.push(Fixed::from_f64(2.8))
.extend([42u32, 0x5f0f3cf5])
.extend([16u16, 4096]) // flags, upm
.extend([LongDateTime::new(-500), LongDateTime::new(101)])
.extend([-100i16, -50, 400, 711])
.extend([0u16, 12]) // mac_style / ppem
.extend([2i16, 1, 0]);
let head = super::Head::read(buf.data().into()).unwrap();
assert_eq!(head.version(), MajorMinor::VERSION_1_0);
assert_eq!(head.font_revision(), Fixed::from_f64(2.8));
assert_eq!(head.units_per_em(), 4096);
assert_eq!(head.created().as_secs(), -500);
assert_eq!(head.y_min(), -50);
}
}

10
vendor/read-fonts/src/tables/hhea.rs vendored Normal file
View File

@@ -0,0 +1,10 @@
//! the [hhea (Horizontal Header)](https://docs.microsoft.com/en-us/typography/opentype/spec/hhea) table
include!("../../generated/generated_hhea.rs");
impl Hhea<'_> {
#[deprecated(since = "0.26.0", note = "use number_of_h_metrics instead")]
pub fn number_of_long_metrics(&self) -> u16 {
self.number_of_h_metrics()
}
}

74
vendor/read-fonts/src/tables/hmtx.rs vendored Normal file
View File

@@ -0,0 +1,74 @@
//! The [hmtx (Horizontal Metrics)](https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx) table
include!("../../generated/generated_hmtx.rs");
impl Hmtx<'_> {
/// Returns the advance width for the given glyph identifier.
pub fn advance(&self, glyph_id: GlyphId) -> Option<u16> {
advance(self.h_metrics(), glyph_id)
}
/// Returns the left side bearing for the given glyph identifier.
pub fn side_bearing(&self, glyph_id: GlyphId) -> Option<i16> {
side_bearing(self.h_metrics(), self.left_side_bearings(), glyph_id)
}
}
pub(super) fn advance(metrics: &[LongMetric], glyph_id: GlyphId) -> Option<u16> {
metrics
.get(glyph_id.to_u32() as usize)
.or_else(|| metrics.last())
.map(|metric| metric.advance())
}
pub(super) fn side_bearing(
metrics: &[LongMetric],
side_bearings: &[BigEndian<i16>],
glyph_id: GlyphId,
) -> Option<i16> {
let ix = glyph_id.to_u32() as usize;
metrics
.get(ix)
.map(|metric| metric.side_bearing())
.or_else(|| {
side_bearings
.get(ix.saturating_sub(metrics.len()))
.map(|sb| sb.get())
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FontRef, TableProvider};
/// Test case where "long metric" array is short
#[test]
fn trimmed_advances() {
let font = FontRef::new(font_test_data::CBDT).unwrap();
let hmtx = font.hmtx().unwrap();
assert!(
!hmtx.left_side_bearings().is_empty(),
"if this fails then the test is no longer accurate"
);
let expected_lsbs = [100, 0, 100, 0];
for (i, lsb) in expected_lsbs.into_iter().enumerate() {
let gid = GlyphId::new(i as _);
// All glyphs have 800 advance width
assert_eq!(hmtx.advance(gid), Some(800));
assert_eq!(hmtx.side_bearing(gid), Some(lsb));
}
}
#[test]
fn metrics() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let hmtx = font.hmtx().unwrap();
let expected = [(908, 100), (1336, 29), (1336, 29), (633, 57)];
for (i, (advance, lsb)) in expected.into_iter().enumerate() {
let gid = GlyphId::new(i as _);
assert_eq!(hmtx.advance(gid), Some(advance));
assert_eq!(hmtx.side_bearing(gid), Some(lsb));
}
}
}

113
vendor/read-fonts/src/tables/hvar.rs vendored Normal file
View File

@@ -0,0 +1,113 @@
//! The [HVAR (Horizontal Metrics Variation)](https://docs.microsoft.com/en-us/typography/opentype/spec/hvar) table
use super::variations::{self, DeltaSetIndexMap, ItemVariationStore};
include!("../../generated/generated_hvar.rs");
impl Hvar<'_> {
/// Returns the advance width delta for the specified glyph identifier and
/// normalized variation coordinates.
pub fn advance_width_delta(
&self,
glyph_id: GlyphId,
coords: &[F2Dot14],
) -> Result<Fixed, ReadError> {
variations::advance_delta(
self.advance_width_mapping(),
self.item_variation_store(),
glyph_id,
coords,
)
}
/// Returns the left side bearing delta for the specified glyph identifier and
/// normalized variation coordinates.
pub fn lsb_delta(&self, glyph_id: GlyphId, coords: &[F2Dot14]) -> Result<Fixed, ReadError> {
variations::item_delta(
self.lsb_mapping(),
self.item_variation_store(),
glyph_id,
coords,
)
}
/// Returns the left side bearing delta for the specified glyph identifier and
/// normalized variation coordinates.
pub fn rsb_delta(&self, glyph_id: GlyphId, coords: &[F2Dot14]) -> Result<Fixed, ReadError> {
variations::item_delta(
self.rsb_mapping(),
self.item_variation_store(),
glyph_id,
coords,
)
}
}
#[cfg(test)]
mod tests {
use crate::{tables::variations::DeltaSetIndexMap, FontRef, TableProvider};
use types::{F2Dot14, Fixed, GlyphId};
#[test]
fn advance_deltas() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let hvar = font.hvar().unwrap();
let gid_a = GlyphId::new(1);
assert_eq!(
hvar.advance_width_delta(gid_a, &[F2Dot14::from_f32(-1.0)])
.unwrap(),
Fixed::from_f64(-113.0)
);
assert_eq!(
hvar.advance_width_delta(gid_a, &[F2Dot14::from_f32(-0.75)])
.unwrap(),
Fixed::from_f64(-85.0)
);
assert_eq!(
hvar.advance_width_delta(gid_a, &[F2Dot14::from_f32(-0.5)])
.unwrap(),
Fixed::from_f64(-56.0)
);
assert_eq!(
hvar.advance_width_delta(gid_a, &[F2Dot14::from_f32(0.0)])
.unwrap(),
Fixed::from_f64(0.0)
);
assert_eq!(
hvar.advance_width_delta(gid_a, &[F2Dot14::from_f32(0.5)])
.unwrap(),
Fixed::from_f64(30.0)
);
assert_eq!(
hvar.advance_width_delta(gid_a, &[F2Dot14::from_f32(1.0)])
.unwrap(),
Fixed::from_f64(59.0)
);
}
#[test]
fn advance_deltas_from_hvar_with_truncated_adv_index_map() {
let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
let maxp = font.maxp().unwrap();
let num_glyphs = maxp.num_glyphs();
let hvar = font.hvar().unwrap();
let Ok(DeltaSetIndexMap::Format0(adv_index_map)) = hvar.advance_width_mapping().unwrap()
else {
panic!("Expected DeltaSetIndexMap::Format0 for hvar.advance_width_mapping()");
};
assert!(adv_index_map.map_count() < num_glyphs);
assert_eq!(num_glyphs, 24);
assert_eq!(adv_index_map.map_count(), 15);
let last_mapped_gid = adv_index_map.map_count() - 1;
// We expect the last 10 glyphs to have the same advance width delta as the last mapped glyph.
// Crucially, hvar.advance_width_delta() should not return OutOfBounds for these glyphs.
for idx in last_mapped_gid..num_glyphs {
let gid = GlyphId::new(idx as _);
assert_eq!(
hvar.advance_width_delta(gid, &[F2Dot14::from_f32(1.0)])
.unwrap(),
Fixed::from_f64(100.0)
);
}
}
}

715
vendor/read-fonts/src/tables/ift.rs vendored Normal file
View File

@@ -0,0 +1,715 @@
//! Incremental Font Transfer [Patch Map](https://w3c.github.io/IFT/Overview.html#font-format-extensions)
include!("../../generated/generated_ift.rs");
use std::str;
pub const IFT_TAG: types::Tag = Tag::new(b"IFT ");
pub const IFTX_TAG: types::Tag = Tag::new(b"IFTX");
/// Wrapper for the packed childEntryMatchModeAndCount field in IFT format 2 mapping table.
///
/// Reference: <https://w3c.github.io/IFT/Overview.html#mapping-entry-childentrymatchmodeandcount>
///
/// The MSB is a flag which indicates conjunctive (bit set) or disjunctive (bit cleared) matching.
/// The remaining 7 bits are a count.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MatchModeAndCount(u8);
impl MatchModeAndCount {
/// Flag indicating that copy mode is append.
///
/// See: <https://w3c.github.io/IFT/Overview.html#mapping-entry-copymodeandcount>
pub const MATCH_MODE_MASK: u8 = 0b10000000;
/// Mask for the low 7 bits to give the copy index count.
pub const COUNT_MASK: u8 = 0b01111111;
pub fn bits(self) -> u8 {
self.0
}
pub fn from_bits(bits: u8) -> Self {
Self(bits)
}
/// If true matching mode is conjunctive (... AND ...) otherwise disjunctive (... OR ...)
pub fn conjunctive_match(self) -> bool {
(self.0 & Self::MATCH_MODE_MASK) != 0
}
pub fn count(self) -> u8 {
self.0 & Self::COUNT_MASK
}
}
impl TryFrom<MatchModeAndCount> for usize {
type Error = ReadError;
fn try_from(value: MatchModeAndCount) -> Result<Self, Self::Error> {
Ok(value.count() as usize)
}
}
impl types::Scalar for MatchModeAndCount {
type Raw = <u8 as types::Scalar>::Raw;
fn to_raw(self) -> Self::Raw {
self.0.to_raw()
}
fn from_raw(raw: Self::Raw) -> Self {
let t = <u8>::from_raw(raw);
Self(t)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, Default, Ord, PartialOrd, Hash)]
pub struct CompatibilityId([u8; 16]);
impl CompatibilityId {
pub fn new(value: [u8; 16]) -> Self {
CompatibilityId(value)
}
pub fn from_u32s(values: [u32; 4]) -> Self {
let mut data = [0u8; 16];
for i in 0..4 {
let be_bytes = values[i].to_be_bytes();
for j in 0..4 {
data[i * 4 + j] = be_bytes[j];
}
}
CompatibilityId(data)
}
pub fn as_slice(&self) -> &[u8] {
&self.0
}
}
impl Scalar for CompatibilityId {
type Raw = [u8; 16];
fn from_raw(raw: Self::Raw) -> Self {
CompatibilityId(raw)
}
fn to_raw(self) -> Self::Raw {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct U8Or16(u16);
impl ReadArgs for U8Or16 {
type Args = u16;
}
impl ComputeSize for U8Or16 {
fn compute_size(max_entry_index: &u16) -> Result<usize, ReadError> {
Ok(if *max_entry_index < 256 { 1 } else { 2 })
}
}
impl FontReadWithArgs<'_> for U8Or16 {
fn read_with_args(data: FontData<'_>, max_entry_index: &Self::Args) -> Result<Self, ReadError> {
if *max_entry_index < 256 {
data.read_at::<u8>(0).map(|v| Self(v as u16))
} else {
data.read_at::<u16>(0).map(Self)
}
}
}
impl U8Or16 {
#[inline]
pub fn get(self) -> u16 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct U16Or24(u32);
impl ReadArgs for U16Or24 {
type Args = GlyphKeyedFlags;
}
impl ComputeSize for U16Or24 {
fn compute_size(flags: &GlyphKeyedFlags) -> Result<usize, ReadError> {
// See: https://w3c.github.io/IFT/Overview.html#glyph-keyed-patch-flags
Ok(if flags.contains(GlyphKeyedFlags::WIDE_GLYPH_IDS) {
3
} else {
2
})
}
}
impl FontReadWithArgs<'_> for U16Or24 {
fn read_with_args(data: FontData<'_>, flags: &Self::Args) -> Result<Self, ReadError> {
if flags.contains(GlyphKeyedFlags::WIDE_GLYPH_IDS) {
data.read_at::<Uint24>(0).map(|v| Self(v.to_u32()))
} else {
data.read_at::<u16>(0).map(|v| Self(v as u32))
}
}
}
impl U16Or24 {
#[inline]
pub fn get(self) -> u32 {
self.0
}
}
impl<'a> PatchMapFormat1<'a> {
pub fn gid_to_entry_iter(&'a self) -> impl Iterator<Item = (GlyphId, u16)> + 'a {
GidToEntryIter {
glyph_map: self.glyph_map().ok(),
glyph_count: self.glyph_count().to_u32(),
gid: self
.glyph_map()
.map(|glyph_map| glyph_map.first_mapped_glyph() as u32)
.unwrap_or(0),
}
.filter(|(_, entry_index)| *entry_index > 0)
}
pub fn entry_count(&self) -> u32 {
self.max_entry_index() as u32 + 1
}
pub fn uri_template_as_string(&self) -> Result<&str, ReadError> {
str::from_utf8(self.uri_template())
.map_err(|_| ReadError::MalformedData("Invalid UTF8 encoding for uri template."))
}
pub fn is_entry_applied(&self, entry_index: u16) -> bool {
let byte_index = entry_index / 8;
let bit_mask = 1 << (entry_index % 8);
self.applied_entries_bitmap()
.get(byte_index as usize)
.map(|byte| byte & bit_mask != 0)
.unwrap_or(false)
}
}
impl PatchMapFormat2<'_> {
pub fn uri_template_as_string(&self) -> Result<&str, ReadError> {
str::from_utf8(self.uri_template())
.map_err(|_| ReadError::MalformedData("Invalid UTF8 encoding for uri template."))
}
}
impl FeatureMap<'_> {
pub fn entry_records_size(&self, max_entry_index: u16) -> Result<usize, ReadError> {
let field_width = if max_entry_index < 256 { 1 } else { 2 };
let mut num_bytes = 0usize;
for record in self.feature_records().iter() {
num_bytes += record?.entry_map_count().get() as usize * field_width * 2;
}
Ok(num_bytes)
}
}
struct GidToEntryIter<'a> {
glyph_map: Option<GlyphMap<'a>>,
glyph_count: u32,
gid: u32,
}
impl Iterator for GidToEntryIter<'_> {
type Item = (GlyphId, u16);
fn next(&mut self) -> Option<Self::Item> {
let glyph_map = self.glyph_map.as_ref()?;
let cur_gid = self.gid;
self.gid += 1;
if cur_gid >= self.glyph_count {
return None;
}
let index = cur_gid as usize - glyph_map.first_mapped_glyph() as usize;
glyph_map
.entry_index()
.get(index)
.ok()
.map(|entry_index| (cur_gid.into(), entry_index.0))
}
}
impl<'a> GlyphPatches<'a> {
/// Returns an iterator over the per glyph data for the table with the given index.
pub fn glyph_data_for_table(
&'a self,
table_index: usize,
) -> impl Iterator<Item = Result<(GlyphId, &'a [u8]), ReadError>> {
let glyph_count = self.glyph_count() as usize;
let start_index = table_index * glyph_count;
let start_it = self.glyph_data_offsets().iter().skip(start_index);
let end_it = self.glyph_data_offsets().iter().skip(start_index + 1);
let glyphs = self.glyph_ids().iter().take(glyph_count);
let it = glyphs.zip(start_it.zip(end_it)).map(|(gid, (start, end))| {
let start = start.get();
let end = end.get();
(gid, start, end)
});
GlyphDataIterator {
patches: self,
offset_iterator: it,
previous_gid: None,
failed: false,
}
}
}
/// Custom iterator for glyph keyed glyph data which allows us to terminate the iterator on the first error.
struct GlyphDataIterator<'a, T>
where
T: Iterator<Item = (Result<U16Or24, ReadError>, Offset32, Offset32)>,
{
patches: &'a GlyphPatches<'a>,
offset_iterator: T,
previous_gid: Option<GlyphId>,
failed: bool,
}
impl<'a, T> Iterator for GlyphDataIterator<'a, T>
where
T: Iterator<Item = (Result<U16Or24, ReadError>, Offset32, Offset32)>,
{
type Item = Result<(GlyphId, &'a [u8]), ReadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed {
return None;
}
let (gid, start, end) = self.offset_iterator.next()?;
let gid = match gid {
Ok(gid) => GlyphId::new(gid.get()),
Err(err) => {
self.failed = true;
return Some(Err(err));
}
};
if let Some(previous_gid) = self.previous_gid {
if gid <= previous_gid {
self.failed = true;
return Some(Err(ReadError::MalformedData(
"Glyph IDs are unsorted or duplicated.",
)));
}
}
self.previous_gid = Some(gid);
let len = match end
.to_u32()
.checked_sub(start.to_u32())
.ok_or(ReadError::MalformedData(
"glyph data offsets are not ascending.",
)) {
Ok(len) => len as usize,
Err(err) => {
self.failed = true;
return Some(Err(err));
}
};
let data: Result<GlyphData<'a>, ReadError> = self.patches.resolve_offset(start);
let data = match data {
Ok(data) => data.data,
Err(err) => {
self.failed = true;
return Some(Err(err));
}
};
let Some(data) = data.as_bytes().get(..len) else {
self.failed = true;
return Some(Err(ReadError::OutOfBounds));
};
Some(Ok((gid, data)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::ift as test_data;
// TODO(garretrieger) - more tests (as functionality is implemented):
// - Test where entryIndex array has len 0 (eg. all glyphs map to 0)
// - Test which appliedEntriesBitmap > 1 byte
// - Test w/ feature map populated.
// - Test enforced minimum entry count of > 0.
// - Test where entryIndex is a u16.
// - Invalid table (too short).
// - Invalid UTF8 sequence in uri template.
// - Compat ID is to short.
// - invalid entry map array (too short)
// - feature map with short entry indices.
#[test]
fn format_1_gid_to_u8_entry_iter() {
let data = test_data::simple_format1();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
let entries: Vec<(GlyphId, u16)> = map.gid_to_entry_iter().collect();
assert_eq!(
entries,
vec![(1u32.into(), 2), (2u32.into(), 1), (4u32.into(), 1)]
);
}
#[test]
fn format_1_gid_to_u16_entry_iter() {
let data = test_data::u16_entries_format1();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
let entries: Vec<(GlyphId, u16)> = map.gid_to_entry_iter().collect();
assert_eq!(
entries,
vec![
(2u32.into(), 0x50),
(3u32.into(), 0x51),
(4u32.into(), 0x12c),
(5u32.into(), 0x12c),
(6u32.into(), 0x50)
]
);
}
#[test]
fn format_1_feature_map() {
let data = test_data::feature_map_format1();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
let Some(feature_map_result) = map.feature_map() else {
panic!("should have a non null feature map.");
};
let Ok(feature_map) = feature_map_result else {
panic!("should have a valid feature map.");
};
assert_eq!(feature_map.feature_records().len(), 3);
let fr0 = feature_map.feature_records().get(0).unwrap();
assert_eq!(fr0.feature_tag(), Tag::new(b"dlig"));
assert_eq!(*fr0.first_new_entry_index(), U8Or16(0x190));
assert_eq!(*fr0.entry_map_count(), U8Or16(0x01));
let fr1 = feature_map.feature_records().get(1).unwrap();
assert_eq!(fr1.feature_tag(), Tag::new(b"liga"));
assert_eq!(*fr1.first_new_entry_index(), U8Or16(0x180));
assert_eq!(*fr1.entry_map_count(), U8Or16(0x02));
}
#[test]
fn format_1_get_charstrings_offset() {
// No offsets
let data = test_data::simple_format1();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
assert_eq!(map.cff_charstrings_offset(), None);
assert_eq!(map.cff2_charstrings_offset(), None);
// One offset
let data = test_data::simple_format1_with_one_charstrings_offset();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
assert_eq!(map.cff_charstrings_offset(), Some(456));
assert_eq!(map.cff2_charstrings_offset(), None);
// Two offsets
let data = test_data::simple_format1_with_two_charstrings_offsets();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
assert_eq!(map.cff_charstrings_offset(), Some(456));
assert_eq!(map.cff2_charstrings_offset(), Some(789));
}
#[test]
fn format_2_get_charstrings_offset() {
// No offsets
let data = test_data::codepoints_only_format2();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format2(map) = table else {
panic!("Not format 2.");
};
assert_eq!(map.cff_charstrings_offset(), None);
assert_eq!(map.cff2_charstrings_offset(), None);
// One offset
let data = test_data::format2_with_one_charstrings_offset();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format2(map) = table else {
panic!("Not format 2.");
};
assert_eq!(map.cff_charstrings_offset(), Some(456));
assert_eq!(map.cff2_charstrings_offset(), None);
// Two offsets
let data = test_data::format2_with_two_charstrings_offset();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format2(map) = table else {
panic!("Not format 2.");
};
assert_eq!(map.cff_charstrings_offset(), Some(456));
assert_eq!(map.cff2_charstrings_offset(), Some(789));
}
#[test]
fn compatibility_id() {
let data = test_data::simple_format1();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
assert_eq!(
map.compatibility_id(),
CompatibilityId::from_u32s([1, 2, 3, 4])
);
}
#[test]
fn is_entry_applied() {
let data = test_data::simple_format1();
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
assert!(!map.is_entry_applied(0));
assert!(map.is_entry_applied(1));
assert!(!map.is_entry_applied(2));
}
#[test]
fn uri_template_as_string() {
let mut data = test_data::simple_format1();
data.write_at("uri_template[0]", 0xc9u8);
data.write_at("uri_template[1]", 0xa4u8);
let table = Ift::read(FontData::new(&data)).unwrap();
let Ift::Format1(map) = table else {
panic!("Not format 1.");
};
assert_eq!(Ok("ɤo/{id}"), map.uri_template_as_string());
}
#[test]
fn glyph_keyed_glyph_data_for_one_table() {
let data = test_data::glyf_u16_glyph_patches();
let table = GlyphPatches::read(FontData::new(&data), GlyphKeyedFlags::NONE).unwrap();
let it = table.glyph_data_for_table(0);
assert_eq!(
it.collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"abc".as_slice())),
Ok((GlyphId::new(7), b"defg".as_slice())),
Ok((GlyphId::new(8), b"".as_slice())),
Ok((GlyphId::new(9), b"hijkl".as_slice())),
Ok((GlyphId::new(13), b"mn".as_slice())),
]
);
assert_eq!(table.glyph_data_for_table(1).collect::<Vec<_>>(), vec![]);
}
#[test]
fn glyph_keyed_glyph_data_for_one_table_u24_ids() {
let data = test_data::glyf_u24_glyph_patches();
let table =
GlyphPatches::read(FontData::new(&data), GlyphKeyedFlags::WIDE_GLYPH_IDS).unwrap();
let it = table.glyph_data_for_table(0);
assert_eq!(
it.collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"abc".as_slice())),
Ok((GlyphId::new(7), b"defg".as_slice())),
Ok((GlyphId::new(8), b"".as_slice())),
Ok((GlyphId::new(9), b"hijkl".as_slice())),
Ok((GlyphId::new(13), b"mn".as_slice())),
]
);
assert_eq!(table.glyph_data_for_table(1).collect::<Vec<_>>(), vec![]);
}
#[test]
fn glyph_keyed_glyph_data_for_multiple_tables() {
let data = test_data::glyf_and_gvar_u16_glyph_patches();
let table = GlyphPatches::read(FontData::new(&data), GlyphKeyedFlags::NONE).unwrap();
assert_eq!(
table.glyph_data_for_table(0).collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"abc".as_slice())),
Ok((GlyphId::new(7), b"defg".as_slice())),
Ok((GlyphId::new(8), b"hijkl".as_slice())),
]
);
assert_eq!(
table.glyph_data_for_table(1).collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"mn".as_slice())),
Ok((GlyphId::new(7), b"opq".as_slice())),
Ok((GlyphId::new(8), b"r".as_slice())),
]
);
assert_eq!(table.glyph_data_for_table(2).collect::<Vec<_>>(), vec![]);
}
#[test]
fn glyph_keyed_glyph_data_non_ascending_gids() {
let mut builder = test_data::glyf_u16_glyph_patches();
builder.write_at("gid_8", 6);
let table =
GlyphPatches::read(FontData::new(builder.as_slice()), GlyphKeyedFlags::NONE).unwrap();
let it = table.glyph_data_for_table(0);
assert_eq!(
it.collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"abc".as_slice())),
Ok((GlyphId::new(7), b"defg".as_slice())),
Err(ReadError::MalformedData(
"Glyph IDs are unsorted or duplicated."
)),
]
);
}
#[test]
fn glyph_keyed_glyph_data_duplicate_gids() {
let mut builder = test_data::glyf_u16_glyph_patches();
builder.write_at("gid_8", 7);
let table =
GlyphPatches::read(FontData::new(builder.as_slice()), GlyphKeyedFlags::NONE).unwrap();
let it = table.glyph_data_for_table(0);
assert_eq!(
it.collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"abc".as_slice())),
Ok((GlyphId::new(7), b"defg".as_slice())),
Err(ReadError::MalformedData(
"Glyph IDs are unsorted or duplicated."
)),
]
);
}
#[test]
fn glyph_keyed_glyph_data_for_one_table_non_ascending_offsets() {
let mut builder = test_data::glyf_u16_glyph_patches();
let gid_13 = builder.offset_for("gid_13_data") as u32;
let gid_9 = builder.offset_for("gid_8_and_9_data") as u32;
builder.write_at("gid_13_offset", gid_9);
builder.write_at("gid_9_offset", gid_13);
let table =
GlyphPatches::read(FontData::new(builder.as_slice()), GlyphKeyedFlags::NONE).unwrap();
let it = table.glyph_data_for_table(0);
assert_eq!(
it.collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"abc".as_slice())),
Ok((GlyphId::new(7), b"defg".as_slice())),
Ok((GlyphId::new(8), b"hijkl".as_slice())),
Err(ReadError::MalformedData(
"glyph data offsets are not ascending."
)),
]
);
assert_eq!(table.glyph_data_for_table(1).collect::<Vec<_>>(), vec![]);
}
#[test]
fn glyph_keyed_glyph_data_for_one_table_gids_truncated() {
let builder = test_data::glyf_u16_glyph_patches();
let len = builder.offset_for("table_count");
let data = &builder.as_slice()[..len];
let Err(err) = GlyphPatches::read(FontData::new(data), GlyphKeyedFlags::NONE) else {
panic!("Expected to fail.");
};
assert_eq!(ReadError::OutOfBounds, err);
}
#[test]
fn glyph_keyed_glyph_data_for_one_table_data_truncated() {
let builder = test_data::glyf_u16_glyph_patches();
let len = builder.offset_for("gid_8_and_9_data");
let data = &builder.as_slice()[..len];
let table = GlyphPatches::read(FontData::new(data), GlyphKeyedFlags::NONE).unwrap();
let it = table.glyph_data_for_table(0);
assert_eq!(
it.collect::<Vec<_>>(),
vec![
Ok((GlyphId::new(2), b"abc".as_slice())),
Ok((GlyphId::new(7), b"defg".as_slice())),
Ok((GlyphId::new(8), b"".as_slice())),
Err(ReadError::OutOfBounds),
]
);
}
#[test]
fn glyph_keyed_glyph_data_for_one_table_offset_array_truncated() {
let builder = test_data::glyf_u16_glyph_patches();
let len = builder.offset_for("gid_9_offset");
let data = &builder.as_slice()[..len];
let Err(err) = GlyphPatches::read(FontData::new(data), GlyphKeyedFlags::NONE) else {
panic!("Expected to fail.");
};
assert_eq!(ReadError::OutOfBounds, err);
}
}

View File

@@ -0,0 +1,98 @@
//! An fvar InstanceRecord
use types::{BigEndian, Fixed, FixedSize, NameId};
#[cfg(feature = "experimental_traverse")]
use crate::traversal::{Field, RecordResolver, SomeRecord};
use crate::{ComputeSize, FontData, FontReadWithArgs, ReadArgs, ReadError};
/// The [InstanceRecord](https://learn.microsoft.com/en-us/typography/opentype/spec/fvar#instancerecord)
#[derive(Clone, Debug)]
pub struct InstanceRecord<'a> {
/// The name ID for entries in the 'name' table that provide subfamily names for this instance.
pub subfamily_name_id: NameId,
/// Reserved for future use — set to 0.
pub flags: u16,
/// The coordinates array for this instance.
pub coordinates: &'a [BigEndian<Fixed>],
/// Optional. The name ID for entries in the 'name' table that provide PostScript names for this instance.
pub post_script_name_id: Option<NameId>,
}
impl ReadArgs for InstanceRecord<'_> {
type Args = (u16, u16);
}
impl<'a> InstanceRecord<'a> {
/// Parse an instance record with a known axis_count and instance_size
pub fn read(
data: FontData<'a>,
axis_count: u16,
instance_size: u16,
) -> Result<Self, ReadError> {
let args = (axis_count, instance_size);
Self::read_with_args(data, &args)
}
}
impl<'a> FontReadWithArgs<'a> for InstanceRecord<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
let axis_count = args.0 as usize;
let instance_size = args.1 as usize;
let mut cursor = data.cursor();
let subfamily_name_id = cursor.read()?;
let flags = cursor.read()?;
let coordinates = cursor.read_array(axis_count)?;
// Size of common fields (subfamily_name_id and flags) plus axis coordinates.
let common_byte_len = u16::RAW_BYTE_LEN * 2 + (axis_count * Fixed::RAW_BYTE_LEN);
// The instance contains a post_script_name_id field if the instance size is greater than
// or equal to the common size plus the 2 bytes for the optional field.
let has_post_script_name_id = instance_size >= common_byte_len + u16::RAW_BYTE_LEN;
let post_script_name_id = if has_post_script_name_id {
let id: NameId = cursor.read()?;
// From <https://learn.microsoft.com/en-us/typography/opentype/spec/fvar#instancerecord>:
// "If the value is 0xFFFF, then the value is ignored, and no
// PostScript name equivalent is provided for the instance."
(id.to_u16() != 0xFFFF).then_some(id)
} else {
None
};
Ok(InstanceRecord {
subfamily_name_id,
flags,
coordinates,
post_script_name_id,
})
}
}
impl ComputeSize for InstanceRecord<'_> {
#[inline]
fn compute_size(args: &(u16, u16)) -> Result<usize, ReadError> {
Ok(args.1 as usize)
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> InstanceRecord<'a> {
pub(crate) fn get_field(&self, idx: usize, _data: FontData<'a>) -> Option<Field<'a>> {
match idx {
0 => Some(Field::new("subfamily_name_id", self.subfamily_name_id)),
1 => Some(Field::new("flags", self.flags)),
2 => Some(Field::new("coordinates", self.coordinates)),
3 => Some(Field::new("post_script_name_id", self.post_script_name_id?)),
_ => None,
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeRecord<'a> for InstanceRecord<'a> {
fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
RecordResolver {
name: "InstanceRecord",
data,
get_field: Box::new(move |idx, data| self.get_field(idx, data)),
}
}
}

628
vendor/read-fonts/src/tables/kern.rs vendored Normal file
View File

@@ -0,0 +1,628 @@
//! The kerning table.
use super::aat::StateTable;
pub use super::kerx::Subtable0Pair;
include!("../../generated/generated_kern.rs");
/// The kerning table.
#[derive(Clone)]
pub enum Kern<'a> {
Ot(OtKern<'a>),
Aat(AatKern<'a>),
}
impl TopLevelTable for Kern<'_> {
const TAG: Tag = Tag::new(b"kern");
}
impl<'a> FontRead<'a> for Kern<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
// The Apple kern table has a 32-bit fixed version field set to
// 1.0 while the OpenType kern table has a 16-bit version field
// set to 0. Check the first 16-bit word to determine which
// version of the table we should parse.
if data.read_at::<u16>(0)? == 0 {
OtKern::read(data).map(Self::Ot)
} else {
AatKern::read(data).map(Self::Aat)
}
}
}
impl<'a> Kern<'a> {
/// Returns an iterator over all of the subtables in this `kern` table.
pub fn subtables(&self) -> impl Iterator<Item = Result<Subtable<'a>, ReadError>> + 'a + Clone {
let (data, is_aat, n_tables) = match self {
Self::Ot(table) => (table.subtable_data(), false, table.n_tables() as u32),
Self::Aat(table) => (table.subtable_data(), true, table.n_tables()),
};
let data = FontData::new(data);
Subtables {
data,
is_aat,
n_tables,
}
}
}
/// Iterator over the subtables of a `kern` table.
#[derive(Clone)]
struct Subtables<'a> {
data: FontData<'a>,
is_aat: bool,
n_tables: u32,
}
impl<'a> Iterator for Subtables<'a> {
type Item = Result<Subtable<'a>, ReadError>;
fn next(&mut self) -> Option<Self::Item> {
let len = if self.is_aat {
self.data.read_at::<u32>(0).ok()? as usize
} else if self.n_tables == 1 {
// For OT kern tables with a single subtable, ignore the length
// and allow the single subtable to extend to the end of the full
// table. Some fonts abuse this to bypass the 16-bit limit of the
// length field.
//
// This is why we don't use VarLenArray for this type.
self.data.len()
} else {
self.data.read_at::<u16>(2).ok()? as usize
};
if len == 0 {
return None;
}
let data = self.data.take_up_to(len)?;
Some(Subtable::read_with_args(data, &self.is_aat))
}
}
impl OtSubtable<'_> {
// version, length and coverage: all u16
const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 3;
}
impl AatSubtable<'_> {
// length: u32, coverage and tuple_index: u16
const HEADER_LEN: usize = u32::RAW_BYTE_LEN + u16::RAW_BYTE_LEN * 2;
}
/// A subtable in the `kern` table.
#[derive(Clone)]
pub enum Subtable<'a> {
Ot(OtSubtable<'a>),
Aat(AatSubtable<'a>),
}
impl ReadArgs for Subtable<'_> {
// is_aat
type Args = bool;
}
impl<'a> FontReadWithArgs<'a> for Subtable<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
let is_aat = *args;
if is_aat {
Ok(Self::Aat(AatSubtable::read(data)?))
} else {
Ok(Self::Ot(OtSubtable::read(data)?))
}
}
}
impl<'a> Subtable<'a> {
/// True if the table has vertical kerning values.
#[inline]
pub fn is_vertical(&self) -> bool {
match self {
Self::Ot(subtable) => subtable.coverage() & (1 << 0) == 0,
Self::Aat(subtable) => subtable.coverage() & 0x8000 != 0,
}
}
/// True if the table has horizontal kerning values.
#[inline]
pub fn is_horizontal(&self) -> bool {
!self.is_vertical()
}
/// True if the table has cross-stream kerning values.
///
/// If text is normally written horizontally, adjustments will be
/// vertical. If adjustment values are positive, the text will be
/// moved up. If they are negative, the text will be moved down.
/// If text is normally written vertically, adjustments will be
/// horizontal. If adjustment values are positive, the text will be
/// moved to the right. If they are negative, the text will be moved
/// to the left.
#[inline]
pub fn is_cross_stream(&self) -> bool {
match self {
Self::Ot(subtable) => subtable.coverage() & (1 << 2) != 0,
Self::Aat(subtable) => subtable.coverage() & 0x4000 != 0,
}
}
/// True if the table has variation kerning values.
#[inline]
pub fn is_variable(&self) -> bool {
match self {
Self::Ot(_) => false,
Self::Aat(subtable) => subtable.coverage() & 0x2000 != 0,
}
}
/// True if the table is represented by a state machine.
#[inline]
pub fn is_state_machine(&self) -> bool {
// Only format 1 is a state machine
self.data_and_format().1 == 1
}
/// Returns an enum representing the actual subtable data.
pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
let (data, format) = self.data_and_format();
let is_aat = matches!(self, Self::Aat(_));
SubtableKind::read_with_args(FontData::new(data), &(format, is_aat))
}
fn data_and_format(&self) -> (&'a [u8], u8) {
match self {
Self::Ot(subtable) => (subtable.data(), ((subtable.coverage() & 0xFF00) >> 8) as u8),
Self::Aat(subtable) => (subtable.data(), subtable.coverage() as u8),
}
}
}
/// The various `kern` subtable formats.
#[derive(Clone)]
pub enum SubtableKind<'a> {
Format0(Subtable0<'a>),
Format1(StateTable<'a>),
Format2(Subtable2<'a>),
Format3(Subtable3<'a>),
}
impl ReadArgs for SubtableKind<'_> {
type Args = (u8, bool);
}
impl<'a> FontReadWithArgs<'a> for SubtableKind<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
let (format, is_aat) = *args;
match format {
0 => Ok(Self::Format0(Subtable0::read(data)?)),
1 => Ok(Self::Format1(StateTable::read(data)?)),
2 => {
let header_len = if is_aat {
AatSubtable::HEADER_LEN
} else {
OtSubtable::HEADER_LEN
};
Ok(Self::Format2(Subtable2::read_with_args(data, &header_len)?))
}
3 => Ok(Self::Format3(Subtable3::read(data)?)),
_ => Err(ReadError::InvalidFormat(format as _)),
}
}
}
impl Subtable0<'_> {
/// Returns the kerning adjustment for the given pair.
pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
super::kerx::pair_kerning(self.pairs(), left, right)
}
}
/// The type 2 `kern` subtable.
#[derive(Clone)]
pub struct Subtable2<'a> {
pub data: FontData<'a>,
/// Size of the header of the containing subtable.
pub header_len: usize,
/// Left-hand offset table.
pub left_offset_table: Subtable2ClassTable<'a>,
/// Right-hand offset table.
pub right_offset_table: Subtable2ClassTable<'a>,
/// Offset to kerning value array.
pub array_offset: usize,
}
impl ReadArgs for Subtable2<'_> {
type Args = usize;
}
impl<'a> FontReadWithArgs<'a> for Subtable2<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
let mut cursor = data.cursor();
let header_len = *args;
// Skip rowWidth field
cursor.advance_by(u16::RAW_BYTE_LEN);
// The offsets here are from the beginning of the subtable and not
// from the "data" section, so we need to hand parse and subtract
// the header size.
let left_offset = (cursor.read::<u16>()? as usize)
.checked_sub(header_len)
.ok_or(ReadError::OutOfBounds)?;
let right_offset = (cursor.read::<u16>()? as usize)
.checked_sub(header_len)
.ok_or(ReadError::OutOfBounds)?;
let array_offset = (cursor.read::<u16>()? as usize)
.checked_sub(header_len)
.ok_or(ReadError::OutOfBounds)?;
let left_offset_table =
Subtable2ClassTable::read(data.slice(left_offset..).ok_or(ReadError::OutOfBounds)?)?;
let right_offset_table =
Subtable2ClassTable::read(data.slice(right_offset..).ok_or(ReadError::OutOfBounds)?)?;
Ok(Self {
data,
header_len,
left_offset_table,
right_offset_table,
array_offset,
})
}
}
impl Subtable2<'_> {
/// Returns the kerning adjustment for the given pair.
pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
let left_offset = self.left_offset_table.value(left).unwrap_or(0) as usize;
let right_offset = self.right_offset_table.value(right).unwrap_or(0) as usize;
// "The left-hand class values are stored pre-multiplied by the number
// of bytes in one row and offset by the offset of the array from the
// start of the subtable."
let left_offset = left_offset.checked_sub(self.header_len)?;
// Make sure that the left offset is greater than the array base
// See <https://github.com/harfbuzz/harfbuzz/blob/6fb10ded54e4640f75f829acb754b05da5c26362/src/hb-aat-layout-common.hh#L1121>
if left_offset < self.array_offset {
return None;
}
// "The right-hand class values are stored pre-multiplied by the number
// of bytes in a single kerning value (i.e., two)"
let offset = left_offset.checked_add(right_offset)?;
self.data
.read_at::<i16>(offset)
.ok()
.map(|value| value as i32)
}
}
impl Subtable2ClassTable<'_> {
fn value(&self, glyph_id: GlyphId) -> Option<u16> {
let glyph_id: u16 = glyph_id.to_u32().try_into().ok()?;
let index = glyph_id.checked_sub(self.first_glyph().to_u16())?;
self.offsets()
.get(index as usize)
.map(|offset| offset.get())
}
}
impl Subtable3<'_> {
/// Returns the kerning adjustment for the given pair.
pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
let left_class = self.left_class().get(left.to_u32() as usize).copied()? as usize;
let right_class = self.right_class().get(right.to_u32() as usize).copied()? as usize;
let index = self
.kern_index()
.get(left_class * self.right_class_count() as usize + right_class)
.copied()? as usize;
self.kern_value().get(index).map(|value| value.get() as i32)
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn ot_format_0() {
// from https://github.com/fonttools/fonttools/blob/729b3d2960efd3/Tests/ttLib/tables/_k_e_r_n_test.py#L9
#[rustfmt::skip]
const 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
];
let kern = Kern::read(FontData::new(KERN_VER_0_FMT_0_DATA)).unwrap();
let Kern::Ot(ot_kern) = &kern else {
panic!("Should be an OpenType kerning table");
};
assert_eq!(ot_kern.version(), 0);
assert_eq!(ot_kern.n_tables(), 1);
let subtables = kern.subtables().collect::<Vec<_>>();
assert_eq!(subtables.len(), 1);
let subtable = subtables.first().unwrap().as_ref().unwrap();
assert!(subtable.is_horizontal());
let Subtable::Ot(ot_subtable) = subtable else {
panic!("Should be an OpenType subtable");
};
assert_eq!(ot_subtable.coverage(), 1);
assert_eq!(ot_subtable.length(), 32);
check_format_0(subtable);
}
#[test]
fn aat_format_0() {
// As above, but modified for AAT
#[rustfmt::skip]
const KERN_VER_1_FMT_0_DATA: &[u8] = &[
0x00, 0x01, 0x00, 0x00, // "0001 0000" # 0: version=1.0
0x00, 0x00, 0x00, 0x01, // "0000 0001 " # 4: nTables=1
0x00, 0x00, 0x00, 0x22, // "0000 0020 " # 8: length=34
0x00, // "00 " # 12: coverage=0
0x00, // "00 " # 13: format=0
0x00, 0x00, // "0000" # 14: tupleIndex=0
0x00, 0x03, // "0003 " # 16: nPairs=3
0x00, 0x0C, // "000C " # 18: searchRange=12
0x00, 0x01, // "0001 " # 20: entrySelector=1
0x00, 0x06, // "0006 " # 22: rangeShift=6
0x00, 0x04, 0x00, 0x0C, 0xFF, 0xD8, // "0004 000C FFD8 " # 24: l=4, r=12, v=-40
0x00, 0x04, 0x00, 0x1C, 0x00, 0x28, // "0004 001C 0028 " # 30: l=4, r=28, v=40
0x00, 0x05, 0x00, 0x28, 0xFF, 0xCE, // "0005 0028 FFCE " # 36: l=5, r=40, v=-50
];
let kern = Kern::read(FontData::new(KERN_VER_1_FMT_0_DATA)).unwrap();
let Kern::Aat(aat_kern) = &kern else {
panic!("Should be an AAT kerning table");
};
assert_eq!(aat_kern.version(), MajorMinor::VERSION_1_0);
assert_eq!(aat_kern.n_tables(), 1);
let subtables = kern.subtables().collect::<Vec<_>>();
assert_eq!(subtables.len(), 1);
let subtable = subtables.first().unwrap().as_ref().unwrap();
assert!(subtable.is_horizontal());
let Subtable::Aat(aat_subtable) = subtable else {
panic!("Should be an AAT subtable");
};
assert_eq!(aat_subtable.coverage(), 0);
assert_eq!(aat_subtable.length(), 34);
check_format_0(subtable);
}
fn check_format_0(subtable: &Subtable) {
let SubtableKind::Format0(format0) = subtable.kind().unwrap() else {
panic!("Should be a format 0 subtable");
};
const EXPECTED: &[(u32, u32, i32)] = &[(4, 12, -40), (4, 28, 40), (5, 40, -50)];
let pairs = format0
.pairs()
.iter()
.map(|pair| {
(
pair.left().to_u32(),
pair.right().to_u32(),
pair.value() as i32,
)
})
.collect::<Vec<_>>();
assert_eq!(pairs, EXPECTED);
for (left, right, value) in EXPECTED.iter().copied() {
assert_eq!(
format0.kerning(left.into(), right.into()),
Some(value),
"left = {left}, right = {right}"
);
}
}
#[test]
fn format_2() {
let kern = Kern::read(FontData::new(KERN_FORMAT_2)).unwrap();
let subtables = kern.subtables().filter_map(|t| t.ok()).collect::<Vec<_>>();
assert_eq!(subtables.len(), 3);
// First subtable is format 0 so ignore it
check_format_2(
&subtables[1],
&[
(68, 60, -100),
(68, 61, -20),
(68, 88, -20),
(69, 67, -30),
(69, 69, -30),
(69, 70, -30),
(69, 71, -30),
(69, 73, -30),
(69, 81, -30),
(69, 83, -30),
(72, 67, -20),
(72, 69, -20),
(72, 70, -20),
(72, 71, -20),
(72, 73, -20),
(72, 81, -20),
(72, 83, -20),
(81, 60, -100),
(81, 61, -20),
(81, 88, -20),
(82, 60, -100),
(82, 61, -20),
(82, 88, -20),
(84, 67, -50),
(84, 69, -50),
(84, 70, -50),
(84, 71, -50),
(84, 73, -50),
(84, 81, -50),
(84, 83, -50),
(88, 67, -20),
(88, 69, -20),
(88, 70, -20),
(88, 71, -20),
(88, 73, -20),
(88, 81, -20),
(88, 83, -20),
],
);
check_format_2(
&subtables[2],
&[
(60, 67, -100),
(60, 69, -100),
(60, 70, -100),
(60, 71, -100),
(60, 73, -100),
(60, 81, -100),
(60, 83, -100),
],
);
}
fn check_format_2(subtable: &Subtable, expected: &[(u32, u32, i32)]) {
let SubtableKind::Format2(format2) = subtable.kind().unwrap() else {
panic!("Should be a format 2 subtable");
};
for (left, right, value) in expected.iter().copied() {
assert_eq!(
format2.kerning(left.into(), right.into()),
Some(value),
"left = {left}, right = {right}"
);
}
}
// Kern version 1, format 2 kern table taken from
// HarfRuzz test font
// <https://github.com/harfbuzz/harfruzz/blob/b3704a4b51ec045a1acf531a3c4600db4aa55446/tests/fonts/in-house/e39391c77a6321c2ac7a2d644de0396470cd4bfe.ttf>
const KERN_FORMAT_2: &[u8] = &[
0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x3C, 0xFF, 0x7E, 0x0, 0x0, 0x1, 0x74, 0x0,
0x2, 0x0, 0x0, 0x0, 0x12, 0x0, 0x7C, 0x0, 0xB0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC,
0xFF, 0xEC, 0xFF, 0xBA, 0xFF, 0x9C, 0xFF, 0xD8, 0xFF, 0xE2, 0xFF, 0x7E, 0x0, 0x0, 0xFF,
0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
0xE2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x0,
0x18, 0x0, 0x34, 0x0, 0x58, 0x0, 0x10, 0x0, 0x10, 0x0, 0x22, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x34, 0x0, 0x34, 0x0,
0x10, 0x0, 0x6A, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x46, 0x0, 0x10, 0x0, 0x10, 0x0,
0x46, 0x0, 0x37, 0x0, 0x60, 0x0, 0x10, 0x0, 0x0, 0x0, 0x8, 0x0, 0xE, 0x0, 0xC, 0x0, 0xA,
0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2,
0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
0x0, 0x0, 0x3, 0x84, 0x0, 0x2, 0x0, 0x0, 0x0, 0x16, 0x1, 0x44, 0x2, 0x64, 0x0, 0x10, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xC4, 0xFF, 0xCE, 0xFF, 0xB0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x9C, 0xFF, 0xC4, 0xFF, 0x7E, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8,
0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xFF, 0xE2, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x7E, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x60, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xFF, 0xE2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xFF, 0x6A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x7E, 0xFF, 0xE2, 0xFF,
0x9C, 0xFF, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0xFF, 0xD8, 0xFF,
0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x9C,
0xFF, 0xB0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x8E, 0x1,
0x18, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0x10, 0x0, 0x94, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x1, 0x2E, 0x0, 0x10, 0x0, 0xD6, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0xAA, 0x0,
0x10, 0x0, 0xC0, 0x0, 0x10, 0x0, 0xEC, 0x1, 0x2E, 0x0, 0x26, 0x0, 0x68, 0x0, 0x52, 0x0,
0x3C, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0,
0x10, 0x1, 0x2, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x1, 0x18, 0x0, 0x10, 0x0,
0x10, 0x0, 0x7E, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x7E, 0x0,
0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0x24, 0x0, 0x8E, 0x0, 0x6,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xC, 0x0, 0x14, 0x0, 0xE, 0x0, 0x10, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0,
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0xA, 0x0,
0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0,
0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4,
];
#[test]
fn format_3() {
// Build a simple NxM kerning array with 5 glyphs
let mut buf = BeBuffer::new();
buf = buf.push(5u16); // glyphCount
buf = buf.push(4u8); // kernValueCount
buf = buf.push(3u8); // leftClassCount
buf = buf.push(2u8); // rightClassCount
buf = buf.push(0u8); // unused flags
buf = buf.extend([0i16, -10, -20, 12]); // kernValues
buf = buf.extend([0u8, 2, 1, 1, 2]); // leftClass
buf = buf.extend([0u8, 1, 1, 0, 1]); // rightClass
buf = buf.extend([0u8, 1, 2, 3, 2, 1]); // kernIndex
let format3 = Subtable3::read(FontData::new(buf.as_slice())).unwrap();
const EXPECTED: [(u32, u32, i32); 25] = [
(0, 0, 0),
(0, 1, -10),
(0, 2, -10),
(0, 3, 0),
(0, 4, -10),
(1, 0, -20),
(1, 1, -10),
(1, 2, -10),
(1, 3, -20),
(1, 4, -10),
(2, 0, -20),
(2, 1, 12),
(2, 2, 12),
(2, 3, -20),
(2, 4, 12),
(3, 0, -20),
(3, 1, 12),
(3, 2, 12),
(3, 3, -20),
(3, 4, 12),
(4, 0, -20),
(4, 1, -10),
(4, 2, -10),
(4, 3, -20),
(4, 4, -10),
];
for (left, right, value) in EXPECTED {
assert_eq!(
format3.kerning(left.into(), right.into()),
Some(value),
"left = {left}, right = {right}"
);
}
}
}

670
vendor/read-fonts/src/tables/kerx.rs vendored Normal file
View File

@@ -0,0 +1,670 @@
//! The [Extended Kerning (kerx)](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html) table.
use super::aat::{safe_read_array_to_end, ExtendedStateTable, LookupU16, LookupU32};
include!("../../generated/generated_kerx.rs");
impl VarSize for Subtable<'_> {
type Size = u32;
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
// The default implementation assumes that the length field itself
// is not included in the total size which is not true of this
// table.
data.read_at::<u32>(pos).ok().map(|size| size as usize)
}
}
impl<'a> Subtable<'a> {
// length, coverage, tuple_count: all u32
pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 3;
/// True if the table has vertical kerning values.
#[inline]
pub fn is_vertical(&self) -> bool {
self.coverage() & 0x80000000 != 0
}
/// True if the table has horizontal kerning values.
#[inline]
pub fn is_horizontal(&self) -> bool {
!self.is_vertical()
}
/// True if the table has cross-stream kerning values.
///
/// If text is normally written horizontally, adjustments will be
/// vertical. If adjustment values are positive, the text will be
/// moved up. If they are negative, the text will be moved down.
/// If text is normally written vertically, adjustments will be
/// horizontal. If adjustment values are positive, the text will be
/// moved to the right. If they are negative, the text will be moved
/// to the left.
#[inline]
pub fn is_cross_stream(&self) -> bool {
self.coverage() & 0x40000000 != 0
}
/// True if the table has variation kerning values.
#[inline]
pub fn is_variable(&self) -> bool {
self.coverage() & 0x20000000 != 0
}
/// Process direction flag. If clear, process the glyphs forwards,
/// that is, from first to last in the glyph stream. If we, process
/// them from last to first. This flag only applies to state-table
/// based 'kerx' subtables (types 1 and 4).
#[inline]
pub fn process_direction(&self) -> bool {
self.coverage() & 0x10000000 != 0
}
/// Returns an enum representing the actual subtable data.
pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
SubtableKind::read_with_args(FontData::new(self.data()), &self.coverage())
}
}
/// The various `kerx` subtable formats.
#[derive(Clone)]
pub enum SubtableKind<'a> {
Format0(Subtable0<'a>),
Format1(Subtable1<'a>),
Format2(Subtable2<'a>),
Format4(Subtable4<'a>),
Format6(Subtable6<'a>),
}
impl ReadArgs for SubtableKind<'_> {
type Args = u32;
}
impl<'a> FontReadWithArgs<'a> for SubtableKind<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
// Format is low byte of coverage
let format = *args & 0xFF;
match format {
0 => Ok(Self::Format0(Subtable0::read(data)?)),
1 => Ok(Self::Format1(Subtable1::read(data)?)),
2 => Ok(Self::Format2(Subtable2::read(data)?)),
// No format 3
4 => Ok(Self::Format4(Subtable4::read(data)?)),
// No format 5
6 => Ok(Self::Format6(Subtable6::read(data)?)),
_ => Err(ReadError::InvalidFormat(format as _)),
}
}
}
impl Subtable0<'_> {
/// Returns the kerning adjustment for the given pair.
pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
pair_kerning(self.pairs(), left, right)
}
}
pub(crate) fn pair_kerning(pairs: &[Subtable0Pair], left: GlyphId, right: GlyphId) -> Option<i32> {
let left: GlyphId16 = left.try_into().ok()?;
let right: GlyphId16 = right.try_into().ok()?;
fn make_key(left: GlyphId16, right: GlyphId16) -> u32 {
(left.to_u32() << 16) | right.to_u32()
}
let idx = pairs
.binary_search_by_key(&make_key(left, right), |pair| {
make_key(pair.left(), pair.right())
})
.ok()?;
pairs.get(idx).map(|pair| pair.value() as i32)
}
/// The type 1 `kerx` subtable.
#[derive(Clone)]
pub struct Subtable1<'a> {
pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
/// Contains the set of kerning values, one for each state.
pub values: &'a [BigEndian<i16>],
}
impl<'a> FontRead<'a> for Subtable1<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let state_table = ExtendedStateTable::read(data)?;
let mut cursor = data.cursor();
cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
let values_offset = cursor.read::<u32>()? as usize;
let values = super::aat::safe_read_array_to_end(&data, values_offset)?;
Ok(Self {
state_table,
values,
})
}
}
/// The type 2 `kerx` subtable.
#[derive(Clone)]
pub struct Subtable2<'a> {
pub data: FontData<'a>,
/// Left-hand offset table.
pub left_offset_table: LookupU16<'a>,
/// Right-hand offset table.
pub right_offset_table: LookupU16<'a>,
/// Kerning values.
pub array: &'a [BigEndian<i16>],
}
impl<'a> FontRead<'a> for Subtable2<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let mut cursor = data.cursor();
// Skip rowWidth field
cursor.advance_by(u32::RAW_BYTE_LEN);
// The offsets here are from the beginning of the subtable and not
// from the "data" section, so we need to hand parse and subtract
// the header size.
let left_offset = (cursor.read::<u32>()? as usize)
.checked_sub(Subtable::HEADER_LEN)
.ok_or(ReadError::OutOfBounds)?;
let right_offset = (cursor.read::<u32>()? as usize)
.checked_sub(Subtable::HEADER_LEN)
.ok_or(ReadError::OutOfBounds)?;
let array_offset = (cursor.read::<u32>()? as usize)
.checked_sub(Subtable::HEADER_LEN)
.ok_or(ReadError::OutOfBounds)?;
let left_offset_table =
LookupU16::read(data.slice(left_offset..).ok_or(ReadError::OutOfBounds)?)?;
let right_offset_table =
LookupU16::read(data.slice(right_offset..).ok_or(ReadError::OutOfBounds)?)?;
let array = safe_read_array_to_end(&data, array_offset)?;
Ok(Self {
data,
left_offset_table,
right_offset_table,
array,
})
}
}
impl Subtable2<'_> {
/// Returns the kerning adjustment for the given pair.
pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
let left: u16 = left.to_u32().try_into().ok()?;
let right: u16 = right.to_u32().try_into().ok()?;
let left_idx = self.left_offset_table.value(left).unwrap_or(0) as usize;
let right_idx = self.right_offset_table.value(right).unwrap_or(0) as usize;
self.array
.get(left_idx + right_idx)
.map(|value| value.get() as i32)
}
}
/// The type 4 `kerx` subtable.
#[derive(Clone)]
pub struct Subtable4<'a> {
pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
/// Flags for control point positioning.
pub flags: u32,
pub actions: Subtable4Actions<'a>,
}
impl<'a> FontRead<'a> for Subtable4<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let state_table = ExtendedStateTable::read(data)?;
let mut cursor = data.cursor();
cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
let flags = cursor.read::<u32>()?;
let action_type = (flags & 0xC0000000) >> 30;
let offset = (flags & 0x00FFFFFF) as usize;
let actions = match action_type {
0 => Subtable4Actions::ControlPoints(safe_read_array_to_end(&data, offset)?),
1 => Subtable4Actions::AnchorPoints(safe_read_array_to_end(&data, offset)?),
2 => Subtable4Actions::ControlPointCoords(safe_read_array_to_end(&data, offset)?),
_ => {
return Err(ReadError::MalformedData(
"invalid action type in kerx subtable 4",
))
}
};
Ok(Self {
state_table,
flags,
actions,
})
}
}
/// Actions for the type 4 `kerx` subtable.
#[derive(Clone)]
pub enum Subtable4Actions<'a> {
/// Sequence of glyph outline point indices.
ControlPoints(&'a [BigEndian<u16>]),
/// Sequence of indices into the `ankr` table.
AnchorPoints(&'a [BigEndian<u16>]),
/// Sequence of coordinate values.
ControlPointCoords(&'a [BigEndian<i16>]),
}
/// The type 6 `kerx` subtable.
#[derive(Clone)]
pub enum Subtable6<'a> {
ShortValues(LookupU16<'a>, LookupU16<'a>, &'a [BigEndian<i16>]),
LongValues(LookupU32<'a>, LookupU32<'a>, &'a [BigEndian<i32>]),
}
impl<'a> FontRead<'a> for Subtable6<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let mut cursor = data.cursor();
let flags = cursor.read::<u32>()?;
// Skip rowCount and columnCount
cursor.advance_by(u16::RAW_BYTE_LEN * 2);
// All offsets are relative to the parent subtable
let row_index_table_offset = (cursor.read::<u32>()? as usize)
.checked_sub(Subtable::HEADER_LEN)
.ok_or(ReadError::OutOfBounds)?;
let column_index_table_offset = (cursor.read::<u32>()? as usize)
.checked_sub(Subtable::HEADER_LEN)
.ok_or(ReadError::OutOfBounds)?;
let kerning_array_offset = (cursor.read::<u32>()? as usize)
.checked_sub(Subtable::HEADER_LEN)
.ok_or(ReadError::OutOfBounds)?;
let row_data = data
.slice(row_index_table_offset..)
.ok_or(ReadError::OutOfBounds)?;
let column_data = data
.slice(column_index_table_offset..)
.ok_or(ReadError::OutOfBounds)?;
if flags & 1 == 0 {
let rows = LookupU16::read(row_data)?;
let columns = LookupU16::read(column_data)?;
let kerning_array = safe_read_array_to_end(&data, kerning_array_offset)?;
Ok(Self::ShortValues(rows, columns, kerning_array))
} else {
let rows = LookupU32::read(row_data)?;
let columns = LookupU32::read(column_data)?;
let kerning_array = safe_read_array_to_end(&data, kerning_array_offset)?;
Ok(Self::LongValues(rows, columns, kerning_array))
}
}
}
impl Subtable6<'_> {
/// Returns the kerning adjustment for the given pair.
pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
let left: u16 = left.to_u32().try_into().ok()?;
let right: u16 = right.to_u32().try_into().ok()?;
match self {
Self::ShortValues(rows, columns, array) => {
let left_idx = rows.value(left).unwrap_or_default();
let right_idx = columns.value(right).unwrap_or_default();
let idx = left_idx as usize + right_idx as usize;
array.get(idx).map(|value| value.get() as i32)
}
Self::LongValues(rows, columns, array) => {
let left_idx = rows.value(left).unwrap_or_default();
let right_idx = columns.value(right).unwrap_or_default();
let idx = (left_idx as usize).checked_add(right_idx as usize)?;
array.get(idx).map(|value| value.get())
}
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeRecord<'a> for Subtable<'a> {
fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
RecordResolver {
name: "Subtable",
get_field: Box::new(move |idx, _data| match idx {
0usize => Some(Field::new("coverage", self.coverage())),
1usize => Some(Field::new("tuple_count", self.tuple_count())),
_ => None,
}),
data,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::bebuffer::BeBuffer;
#[test]
fn parse_subtable0() {
let mut buf = BeBuffer::new();
// n_pairs, bsearch params
buf = buf.extend([6u32, 0, 0, 0]);
// just some randomly generated pairs (left, right, kerning adjustment)
let mut pairs = [
(0u32, 1u32, -10i32),
(2, 4, 22),
(0, 3, -6),
(8, 2, 500),
(10, 1, 42),
(9, 12, -1000),
];
// pairs must be sorted by left and right packed into a u32
pairs.sort_by_key(|pair| (pair.0 << 16) | pair.1);
for pair in &pairs {
buf = buf
.push(pair.0 as u16)
.push(pair.1 as u16)
.push(pair.2 as i16);
}
let data = buf.to_vec();
let subtable0 = Subtable0::read(FontData::new(&data)).unwrap();
for pair in pairs {
assert_eq!(
subtable0.kerning(pair.0.into(), pair.1.into()),
Some(pair.2)
);
}
}
#[test]
fn parse_subtable1() {
let data = FormatOneFour::One.build_subtable();
let subtable1 = Subtable1::read(FontData::new(&data)).unwrap();
let values = subtable1
.values
.iter()
// The values array is unsized in the format so we need
// to limit it for comparison
.take(ONE_EXPECTED.len())
.map(|value| value.get())
.collect::<Vec<_>>();
assert_eq!(values, &ONE_EXPECTED);
}
#[test]
fn parse_subtable2() {
let data = FormatTwoSix::Two.build_subtable();
let subtable = Subtable2::read(FontData::new(&data)).unwrap();
let mut values = vec![];
for left in 0u32..4 {
for right in 0u32..4 {
let Some(kerning) = subtable.kerning(left.into(), right.into()) else {
panic!("expected kerning value for {left} and {right}");
};
values.push(kerning);
}
}
assert_eq!(values, &TWO_SIX_EXPECTED);
}
#[test]
fn parse_subtable4_control_points() {
let data = FormatOneFour::FourControlPoints.build_subtable();
let subtable4 = Subtable4::read(FontData::new(&data)).unwrap();
let Subtable4Actions::ControlPoints(action) = &subtable4.actions else {
panic!("expected subtable 4 control points action");
};
let values = action
.chunks_exact(2)
.take(FOUR_OUTLINE_ANKR_EXPECTED.len())
.map(|values| (values[0].get(), values[1].get()))
.collect::<Vec<_>>();
assert_eq!(values, &FOUR_OUTLINE_ANKR_EXPECTED);
}
#[test]
fn parse_subtable4_anchor_points() {
let data = FormatOneFour::FourAnchorPoints.build_subtable();
let subtable4 = Subtable4::read(FontData::new(&data)).unwrap();
let Subtable4Actions::AnchorPoints(action) = &subtable4.actions else {
panic!("expected subtable 4 anchor points action");
};
let values = action
.chunks_exact(2)
.take(FOUR_OUTLINE_ANKR_EXPECTED.len())
.map(|values| (values[0].get(), values[1].get()))
.collect::<Vec<_>>();
assert_eq!(values, &FOUR_OUTLINE_ANKR_EXPECTED);
}
#[test]
fn parse_subtable4_coords() {
let data = FormatOneFour::FourCoords.build_subtable();
let subtable4 = Subtable4::read(FontData::new(&data)).unwrap();
let Subtable4Actions::ControlPointCoords(action) = &subtable4.actions else {
panic!("expected subtable 4 coords action");
};
let values = action
.chunks_exact(4)
.take(FOUR_COORDS_EXPECTED.len())
.map(|values| {
[
values[0].get(),
values[1].get(),
values[2].get(),
values[3].get(),
]
})
.collect::<Vec<_>>();
assert_eq!(values, &FOUR_COORDS_EXPECTED);
}
#[test]
fn parse_subtable6_short() {
let data = FormatTwoSix::SixShort.build_subtable();
let subtable = Subtable6::read(FontData::new(&data)).unwrap();
let Subtable6::ShortValues(..) = &subtable else {
panic!("expected short values in subtable 6");
};
check_subtable6(subtable);
}
#[test]
fn parse_subtable6_long() {
let data = FormatTwoSix::SixLong.build_subtable();
let subtable = Subtable6::read(FontData::new(&data)).unwrap();
let Subtable6::LongValues(..) = &subtable else {
panic!("expected long values in subtable 6");
};
check_subtable6(subtable);
}
fn check_subtable6(subtable: Subtable6) {
let mut values = vec![];
for left in 0u32..4 {
for right in 0u32..4 {
let Some(kerning) = subtable.kerning(left.into(), right.into()) else {
panic!("expected kerning value for {left} and {right}");
};
values.push(kerning);
}
}
assert_eq!(values, &TWO_SIX_EXPECTED);
}
// Just kerning adjustment values
const ONE_EXPECTED: [i16; 8] = [-40, -20, -10, 0, 10, 20, 40, 80];
// Mark/Current glyph indices. Either outline points or indices into the ankr
// table depending on format 4 action type.
const FOUR_OUTLINE_ANKR_EXPECTED: [(u16, u16); 4] = [(0, 2), (2, 4), (4, 8), (8, 16)];
// Mark/Current xy coordinates
const FOUR_COORDS_EXPECTED: [[i16; 4]; 4] = [
[-10, 10, -20, 20],
[1, 2, 3, 4],
[-1, -2, -3, -4],
[10, -10, 20, -20],
];
enum FormatOneFour {
One,
FourControlPoints,
FourAnchorPoints,
FourCoords,
}
impl FormatOneFour {
fn build_subtable(&self) -> Vec<u8> {
let mut flags_offset = ExtendedStateTable::<()>::HEADER_LEN + u32::RAW_BYTE_LEN;
// Low bits are offset. Set the action type for format 4.
match self {
Self::FourAnchorPoints => {
flags_offset |= 1 << 30;
}
Self::FourCoords => {
flags_offset |= 2 << 30;
}
_ => {}
}
let mut buf = BeBuffer::new();
buf = buf.push(flags_offset as u32);
// Now add some data depending on the format
match self {
Self::One => {
buf = buf.extend(ONE_EXPECTED);
}
Self::FourControlPoints | Self::FourAnchorPoints => {
for indices in FOUR_OUTLINE_ANKR_EXPECTED {
buf = buf.push(indices.0).push(indices.1);
}
}
Self::FourCoords => {
for coords in FOUR_COORDS_EXPECTED {
buf = buf.extend(coords);
}
}
}
let payload = buf.to_vec();
let payload_len = payload.len() as u32;
#[rustfmt::skip]
let header = [
6_u32, // number of classes
payload_len + 16, // byte offset to class table
payload_len + 52, // byte offset to state array
payload_len + 88, // byte offset to entry array
];
#[rustfmt::skip]
let class_table = [
6_u16, // format
4, // unit size (4 bytes)
5, // number of units
16, // search range
2, // entry selector
0, // range shift
50, 4, // Input glyph 50 maps to class 4
51, 4, // Input glyph 51 maps to class 4
80, 5, // Input glyph 80 maps to class 5
201, 4, // Input glyph 201 maps to class 4
202, 4, // Input glyph 202 maps to class 4
!0, !0
];
#[rustfmt::skip]
let state_array: [u16; 18] = [
0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 2, 1,
];
#[rustfmt::skip]
let entry_table: [u16; 9] = [
0, 0, 1,
2, 0, 2,
0, 0, 3,
];
BeBuffer::new()
.extend(header)
.extend(payload)
.extend(class_table)
.extend(state_array)
.extend(entry_table)
.to_vec()
}
}
const TWO_SIX_EXPECTED: [i32; 16] =
[0i32, 10, 20, 0, 8, 4, -2, 8, 30, -10, -20, 30, 8, 4, -2, 8];
enum FormatTwoSix {
Two,
SixShort,
SixLong,
}
impl FormatTwoSix {
fn is_long(&self) -> bool {
matches!(self, Self::SixLong)
}
fn is_six(&self) -> bool {
!matches!(self, Self::Two)
}
// Common helper for building format 2/6 subtables
fn build_subtable(&self) -> Vec<u8> {
let mut buf = BeBuffer::new();
let row_count = 3u32;
let column_count = 3u32;
let is_long = self.is_long();
if self.is_six() {
// flags, rowCount, columnCount
buf = buf
.push(if is_long { 1u32 } else { 0u32 })
.push(row_count as u16)
.push(column_count as u16);
} else {
// rowWidth
buf = buf.push(row_count);
}
// Map 4 glyphs
// 0 => row 0, column 0
// 1 => row 2, column 1
// 2 => row 1, column 2
// 3 => row 2, column 0
// values in the row table are pre-multiplied by column count
#[allow(clippy::erasing_op, clippy::identity_op)]
let row_table = build_lookup(
&[
0 * column_count,
2 * column_count,
1 * column_count,
2 * column_count,
],
is_long,
);
let column_table = build_lookup(&[0, 1, 2, 0], is_long);
// 3x3 kerning matrix
let kerning_array = [0i32, 10, 20, 30, -10, -20, 8, 4, -2];
let mut offset =
Subtable::HEADER_LEN + u32::RAW_BYTE_LEN * if self.is_six() { 5 } else { 4 };
// row table offset
buf = buf.push(offset as u32);
offset += row_table.len();
// column table offset
buf = buf.push(offset as u32);
offset += column_table.len();
// kerning array offset
buf = buf.push(offset as u32);
buf = buf.extend(row_table);
buf = buf.extend(column_table);
if is_long {
buf = buf.extend(kerning_array);
} else {
for value in &kerning_array {
buf = buf.push(*value as i16);
}
}
buf.to_vec()
}
}
// Builds a simple lookup table mapping the specified slice from
// index -> value.
// If `is_long` is true, builds a 32-bit lookup table, otherwise
// builds a 16-bit table.
fn build_lookup(values: &[u32], is_long: bool) -> Vec<u8> {
let mut buf = BeBuffer::new();
// format
buf = buf.push(0u16);
for value in values {
if is_long {
buf = buf.push(*value);
} else {
buf = buf.push(*value as u16);
}
}
buf.to_vec()
}
}

760
vendor/read-fonts/src/tables/layout.rs vendored Normal file
View File

@@ -0,0 +1,760 @@
//! OpenType Layout common table formats
#[cfg(feature = "std")]
mod closure;
mod feature;
mod lookup_flag;
mod script;
use core::cmp::Ordering;
pub use lookup_flag::LookupFlag;
pub use script::{ScriptTags, SelectedScript, UNICODE_TO_NEW_OPENTYPE_SCRIPT_TAGS};
use super::variations::DeltaSetIndex;
#[cfg(feature = "std")]
use crate::collections::IntSet;
#[cfg(feature = "std")]
pub(crate) use closure::{
ContextFormat1, ContextFormat2, ContextFormat3, LookupClosure, LookupClosureCtx,
};
#[cfg(test)]
mod spec_tests;
include!("../../generated/generated_layout.rs");
impl<'a, T: FontRead<'a>> Lookup<'a, T> {
pub fn get_subtable(&self, offset: Offset16) -> Result<T, ReadError> {
self.resolve_offset(offset)
}
#[cfg(feature = "experimental_traverse")]
fn traverse_lookup_flag(&self) -> traversal::FieldType<'a> {
self.lookup_flag().to_bits().into()
}
}
/// A trait that abstracts the behaviour of an extension subtable
///
/// This is necessary because GPOS and GSUB have different concrete types
/// for their extension lookups.
pub trait ExtensionLookup<'a, T: FontRead<'a>>: FontRead<'a> {
fn extension(&self) -> Result<T, ReadError>;
}
/// an array of subtables, maybe behind extension lookups
///
/// This is used to implement more ergonomic access to lookup subtables for
/// GPOS & GSUB lookup tables.
pub enum Subtables<'a, T: FontRead<'a>, Ext: ExtensionLookup<'a, T>> {
Subtable(ArrayOfOffsets<'a, T>),
Extension(ArrayOfOffsets<'a, Ext>),
}
impl<'a, T: FontRead<'a> + 'a, Ext: ExtensionLookup<'a, T> + 'a> Subtables<'a, T, Ext> {
/// create a new subtables array given offsets to non-extension subtables
pub(crate) fn new(offsets: &'a [BigEndian<Offset16>], data: FontData<'a>) -> Self {
Subtables::Subtable(ArrayOfOffsets::new(offsets, data, ()))
}
/// create a new subtables array given offsets to extension subtables
pub(crate) fn new_ext(offsets: &'a [BigEndian<Offset16>], data: FontData<'a>) -> Self {
Subtables::Extension(ArrayOfOffsets::new(offsets, data, ()))
}
/// The number of subtables in this collection
pub fn len(&self) -> usize {
match self {
Subtables::Subtable(inner) => inner.len(),
Subtables::Extension(inner) => inner.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Return the subtable at the given index
pub fn get(&self, idx: usize) -> Result<T, ReadError> {
match self {
Subtables::Subtable(inner) => inner.get(idx),
Subtables::Extension(inner) => inner.get(idx).and_then(|ext| ext.extension()),
}
}
/// Return an iterator over all the subtables in the collection
pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
let (left, right) = match self {
Subtables::Subtable(inner) => (Some(inner.iter()), None),
Subtables::Extension(inner) => (
None,
Some(inner.iter().map(|ext| ext.and_then(|ext| ext.extension()))),
),
};
left.into_iter()
.flatten()
.chain(right.into_iter().flatten())
}
}
/// An enum for different possible tables referenced by [Feature::feature_params_offset]
pub enum FeatureParams<'a> {
StylisticSet(StylisticSetParams<'a>),
Size(SizeParams<'a>),
CharacterVariant(CharacterVariantParams<'a>),
}
impl ReadArgs for FeatureParams<'_> {
type Args = Tag;
}
impl<'a> FontReadWithArgs<'a> for FeatureParams<'a> {
fn read_with_args(bytes: FontData<'a>, args: &Tag) -> Result<FeatureParams<'a>, ReadError> {
match *args {
t if t == Tag::new(b"size") => SizeParams::read(bytes).map(Self::Size),
// to whoever is debugging this dumb bug I wrote: I'm sorry.
t if &t.to_raw()[..2] == b"ss" => {
StylisticSetParams::read(bytes).map(Self::StylisticSet)
}
t if &t.to_raw()[..2] == b"cv" => {
CharacterVariantParams::read(bytes).map(Self::CharacterVariant)
}
// NOTE: what even is our error condition here? an offset exists but
// we don't know the tag?
_ => Err(ReadError::InvalidFormat(0xdead)),
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeTable<'a> for FeatureParams<'a> {
fn type_name(&self) -> &str {
match self {
FeatureParams::StylisticSet(table) => table.type_name(),
FeatureParams::Size(table) => table.type_name(),
FeatureParams::CharacterVariant(table) => table.type_name(),
}
}
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
match self {
FeatureParams::StylisticSet(table) => table.get_field(idx),
FeatureParams::Size(table) => table.get_field(idx),
FeatureParams::CharacterVariant(table) => table.get_field(idx),
}
}
}
impl FeatureTableSubstitutionRecord {
pub fn alternate_feature<'a>(&self, data: FontData<'a>) -> Result<Feature<'a>, ReadError> {
self.alternate_feature_offset()
.resolve_with_args(data, &Tag::new(b"NULL"))
}
}
impl<'a> CoverageTable<'a> {
pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + 'a {
// all one expression so that we have a single return type
let (iter1, iter2) = match self {
CoverageTable::Format1(t) => (Some(t.glyph_array().iter().map(|g| g.get())), None),
CoverageTable::Format2(t) => {
let iter = t.range_records().iter().flat_map(RangeRecord::iter);
(None, Some(iter))
}
};
iter1
.into_iter()
.flatten()
.chain(iter2.into_iter().flatten())
}
/// If this glyph is in the coverage table, returns its index
pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
match self {
CoverageTable::Format1(sub) => sub.get(gid),
CoverageTable::Format2(sub) => sub.get(gid),
}
}
/// Returns if this table contains at least one glyph in the 'glyphs' set.
#[cfg(feature = "std")]
pub fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
match self {
CoverageTable::Format1(sub) => sub.intersects(glyphs),
CoverageTable::Format2(sub) => sub.intersects(glyphs),
}
}
/// Returns the intersection of this table and input 'glyphs' set.
#[cfg(feature = "std")]
pub fn intersect_set(&self, glyphs: &IntSet<GlyphId>) -> IntSet<GlyphId> {
match self {
CoverageTable::Format1(sub) => sub.intersect_set(glyphs),
CoverageTable::Format2(sub) => sub.intersect_set(glyphs),
}
}
}
impl CoverageFormat1<'_> {
/// If this glyph is in the coverage table, returns its index
pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
let gid16: GlyphId16 = gid.into().try_into().ok()?;
let be_glyph: BigEndian<GlyphId16> = gid16.into();
self.glyph_array()
.binary_search(&be_glyph)
.ok()
.map(|idx| idx as _)
}
/// Returns if this table contains at least one glyph in the 'glyphs' set.
#[cfg(feature = "std")]
fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
let glyph_count = self.glyph_count() as u32;
let num_bits = 32 - glyph_count.leading_zeros();
if glyph_count > (glyphs.len() as u32) * num_bits {
glyphs.iter().any(|g| self.get(g).is_some())
} else {
self.glyph_array()
.iter()
.any(|g| glyphs.contains(GlyphId::from(g.get())))
}
}
/// Returns the intersection of this table and input 'glyphs' set.
#[cfg(feature = "std")]
fn intersect_set(&self, glyphs: &IntSet<GlyphId>) -> IntSet<GlyphId> {
let glyph_count = self.glyph_count() as u32;
let num_bits = 32 - glyph_count.leading_zeros();
if glyph_count > (glyphs.len() as u32) * num_bits {
glyphs
.iter()
.filter_map(|g| self.get(g).map(|_| g))
.collect()
} else {
self.glyph_array()
.iter()
.filter(|g| glyphs.contains(GlyphId::from(g.get())))
.map(|g| GlyphId::from(g.get()))
.collect()
}
}
/// Return the number of glyphs in this table
pub fn population(&self) -> usize {
self.glyph_count() as usize
}
}
impl CoverageFormat2<'_> {
/// If this glyph is in the coverage table, returns its index
pub fn get(&self, gid: impl Into<GlyphId>) -> Option<u16> {
let gid: GlyphId16 = gid.into().try_into().ok()?;
self.range_records()
.binary_search_by(|rec| {
if rec.end_glyph_id() < gid {
Ordering::Less
} else if rec.start_glyph_id() > gid {
Ordering::Greater
} else {
Ordering::Equal
}
})
.ok()
.map(|idx| {
let rec = &self.range_records()[idx];
rec.start_coverage_index() + gid.to_u16() - rec.start_glyph_id().to_u16()
})
}
/// Returns if this table contains at least one glyph in the 'glyphs' set.
#[cfg(feature = "std")]
fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
let range_count = self.range_count() as u32;
let num_bits = 32 - range_count.leading_zeros();
if range_count > (glyphs.len() as u32) * num_bits {
glyphs.iter().any(|g| self.get(g).is_some())
} else {
self.range_records()
.iter()
.any(|record| record.intersects(glyphs))
}
}
/// Returns the intersection of this table and input 'glyphs' set.
#[cfg(feature = "std")]
fn intersect_set(&self, glyphs: &IntSet<GlyphId>) -> IntSet<GlyphId> {
let range_count = self.range_count() as u32;
let num_bits = 32 - range_count.leading_zeros();
if range_count > (glyphs.len() as u32) * num_bits {
glyphs
.iter()
.filter_map(|g| self.get(g).map(|_| g))
.collect()
} else {
let mut out = IntSet::empty();
let mut last = GlyphId16::from(0);
for record in self.range_records() {
// break out of loop for overlapping/broken tables
let start_glyph = record.start_glyph_id();
if start_glyph < last {
break;
}
let end = record.end_glyph_id();
last = end;
let start = GlyphId::from(start_glyph);
if glyphs.contains(start) {
out.insert(start);
}
for g in glyphs.iter_after(start) {
if g.to_u32() > end.to_u32() {
break;
}
out.insert(g);
}
}
out
}
}
/// Return the number of glyphs in this table
pub fn population(&self) -> usize {
self.range_records()
.iter()
.fold(0, |acc, record| acc + record.population())
}
}
impl RangeRecord {
pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
(self.start_glyph_id().to_u16()..=self.end_glyph_id().to_u16()).map(GlyphId16::new)
}
/// Returns if this table contains at least one glyph in the 'glyphs' set.
#[cfg(feature = "std")]
pub fn intersects(&self, glyphs: &IntSet<GlyphId>) -> bool {
glyphs.intersects_range(
GlyphId::from(self.start_glyph_id())..=GlyphId::from(self.end_glyph_id()),
)
}
/// Return the number of glyphs in this record
pub fn population(&self) -> usize {
let start = self.start_glyph_id().to_u32() as usize;
let end = self.end_glyph_id().to_u32() as usize;
if start > end {
0
} else {
end - start + 1
}
}
}
impl DeltaFormat {
pub(crate) fn value_count(self, start_size: u16, end_size: u16) -> usize {
let range_len = end_size.saturating_add(1).saturating_sub(start_size) as usize;
let val_per_word = match self {
DeltaFormat::Local2BitDeltas => 8,
DeltaFormat::Local4BitDeltas => 4,
DeltaFormat::Local8BitDeltas => 2,
_ => return 0,
};
let count = range_len / val_per_word;
let extra = (range_len % val_per_word).min(1);
count + extra
}
}
// we as a 'format' in codegen, and the generic error type for an invalid format
// stores the value as an i64, so we need this conversion.
impl From<DeltaFormat> for i64 {
fn from(value: DeltaFormat) -> Self {
value as u16 as _
}
}
impl<'a> ClassDefFormat1<'a> {
/// Get the class for this glyph id
pub fn get(&self, gid: GlyphId16) -> u16 {
if gid < self.start_glyph_id() {
return 0;
}
let idx = gid.to_u16() - self.start_glyph_id().to_u16();
self.class_value_array()
.get(idx as usize)
.map(|x| x.get())
.unwrap_or(0)
}
/// Iterate over each glyph and its class.
pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + 'a {
let start = self.start_glyph_id();
self.class_value_array()
.iter()
.enumerate()
.map(move |(i, val)| {
let gid = start.to_u16().saturating_add(i as u16);
(GlyphId16::new(gid), val.get())
})
}
/// Return the number of glyphs explicitly assigned to a class in this table
pub fn population(&self) -> usize {
self.glyph_count() as usize
}
/// Returns class values for the intersected glyphs of this table and input 'glyphs' set.
#[cfg(feature = "std")]
fn intersect_classes(&self, glyphs: &IntSet<GlyphId>) -> IntSet<u16> {
let mut out = IntSet::empty();
if glyphs.is_empty() {
return out;
}
let start_glyph = self.start_glyph_id().to_u32();
let glyph_count = self.glyph_count();
let end_glyph = start_glyph + glyph_count as u32 - 1;
if glyphs.first().unwrap().to_u32() < start_glyph
|| glyphs.last().unwrap().to_u32() > end_glyph
{
out.insert(0);
}
let class_values = self.class_value_array();
if glyphs.contains(GlyphId::from(start_glyph)) {
let Some(start_glyph_class) = class_values.first() else {
return out;
};
out.insert(start_glyph_class.get());
}
for g in glyphs.iter_after(GlyphId::from(start_glyph)) {
let g = g.to_u32();
if g > end_glyph {
break;
}
let idx = g - start_glyph;
let Some(class) = class_values.get(idx as usize) else {
break;
};
out.insert(class.get());
}
out
}
}
impl<'a> ClassDefFormat2<'a> {
/// Get the class for this glyph id
pub fn get(&self, gid: GlyphId16) -> u16 {
let records = self.class_range_records();
let ix = match records.binary_search_by(|rec| rec.start_glyph_id().cmp(&gid)) {
Ok(ix) => ix,
Err(ix) => ix.saturating_sub(1),
};
if let Some(record) = records.get(ix) {
if (record.start_glyph_id()..=record.end_glyph_id()).contains(&gid) {
return record.class();
}
}
0
}
/// Iterate over each glyph and its class.
pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + 'a {
self.class_range_records().iter().flat_map(|range| {
let start = range.start_glyph_id().to_u16();
let end = range.end_glyph_id().to_u16();
(start..=end).map(|gid| (GlyphId16::new(gid), range.class()))
})
}
/// Return the number of glyphs explicitly assigned to a class in this table
pub fn population(&self) -> usize {
self.class_range_records()
.iter()
.fold(0, |acc, record| acc + record.population())
}
/// Returns class values for the intersected glyphs of this table and input 'glyphs' set.
#[cfg(feature = "std")]
fn intersect_classes(&self, glyphs: &IntSet<GlyphId>) -> IntSet<u16> {
let mut out = IntSet::empty();
if glyphs.is_empty() {
return out;
}
if self.class_range_count() == 0 {
out.insert(0);
return out;
}
let range_records = self.class_range_records();
let first_record = range_records[0];
if glyphs.first().unwrap() < first_record.start_glyph_id() {
out.insert(0);
} else {
let mut glyph = GlyphId::from(first_record.end_glyph_id());
for record in range_records.iter().skip(1) {
let Some(g) = glyphs.iter_after(glyph).next() else {
break;
};
if g < record.start_glyph_id() {
out.insert(0);
break;
}
glyph = GlyphId::from(record.end_glyph_id());
}
if glyphs.iter_after(glyph).next().is_some() {
out.insert(0);
}
}
let num_ranges = self.class_range_count();
let num_bits = 16 - num_ranges.leading_zeros();
if num_ranges as u64 > glyphs.len() * num_bits as u64 {
for g in glyphs.iter() {
let class = self.get(GlyphId16::from(g.to_u32() as u16));
if class != 0 {
out.insert(class);
}
}
} else {
for record in range_records {
if glyphs.intersects_range(
GlyphId::from(record.start_glyph_id())..=GlyphId::from(record.end_glyph_id()),
) {
out.insert(record.class());
}
}
}
out
}
}
impl ClassRangeRecord {
/// Return the number of glyphs explicitly assigned to a class in this table
pub fn population(&self) -> usize {
let start = self.start_glyph_id().to_u32() as usize;
let end = self.end_glyph_id().to_u32() as usize;
if start > end {
0
} else {
end - start + 1
}
}
}
impl ClassDef<'_> {
/// Get the class for this glyph id
pub fn get(&self, gid: GlyphId16) -> u16 {
match self {
ClassDef::Format1(table) => table.get(gid),
ClassDef::Format2(table) => table.get(gid),
}
}
/// Iterate over each glyph and its class.
///
/// This will not include class 0 unless it has been explicitly assigned.
pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
let (one, two) = match self {
ClassDef::Format1(inner) => (Some(inner.iter()), None),
ClassDef::Format2(inner) => (None, Some(inner.iter())),
};
one.into_iter().flatten().chain(two.into_iter().flatten())
}
/// Return the number of glyphs explicitly assigned to a class in this table
pub fn population(&self) -> usize {
match self {
ClassDef::Format1(table) => table.population(),
ClassDef::Format2(table) => table.population(),
}
}
/// Returns class values for the intersected glyphs of this table and input 'glyphs' set.
#[cfg(feature = "std")]
pub fn intersect_classes(&self, glyphs: &IntSet<GlyphId>) -> IntSet<u16> {
match self {
ClassDef::Format1(table) => table.intersect_classes(glyphs),
ClassDef::Format2(table) => table.intersect_classes(glyphs),
}
}
}
impl<'a> Device<'a> {
/// Iterate over the decoded values for this device
pub fn iter(&self) -> impl Iterator<Item = i8> + 'a {
let format = self.delta_format();
let mut n = (self.end_size() - self.start_size()) as usize + 1;
let deltas_per_word = match format {
DeltaFormat::Local2BitDeltas => 8,
DeltaFormat::Local4BitDeltas => 4,
DeltaFormat::Local8BitDeltas => 2,
_ => 0,
};
self.delta_value().iter().flat_map(move |val| {
let iter = iter_packed_values(val.get(), format, n);
n = n.saturating_sub(deltas_per_word);
iter
})
}
}
fn iter_packed_values(raw: u16, format: DeltaFormat, n: usize) -> impl Iterator<Item = i8> {
let mut decoded = [None; 8];
let (mask, sign_mask, bits) = match format {
DeltaFormat::Local2BitDeltas => (0b11, 0b10, 2usize),
DeltaFormat::Local4BitDeltas => (0b1111, 0b1000, 4),
DeltaFormat::Local8BitDeltas => (0b1111_1111, 0b1000_0000, 8),
_ => (0, 0, 0),
};
let max_per_word = 16 / bits;
#[allow(clippy::needless_range_loop)] // enumerate() feels weird here
for i in 0..n.min(max_per_word) {
let mask = mask << ((16 - bits) - i * bits);
let val = (raw & mask) >> ((16 - bits) - i * bits);
let sign = val & sign_mask != 0;
let val = if sign {
// it is 2023 and I am googling to remember how twos compliment works
-((((!val) & mask) + 1) as i8)
} else {
val as i8
};
decoded[i] = Some(val)
}
decoded.into_iter().flatten()
}
impl From<VariationIndex<'_>> for DeltaSetIndex {
fn from(src: VariationIndex) -> DeltaSetIndex {
DeltaSetIndex {
outer: src.delta_set_outer_index(),
inner: src.delta_set_inner_index(),
}
}
}
/// Combination of a tag and a child table.
///
/// Used in script and feature lists where a data structure has an array
/// of records with each containing a tag and an offset to a table. This
/// allows us to provide convenience methods that return both values.
#[derive(Clone)]
pub struct TaggedElement<T> {
pub tag: Tag,
pub element: T,
}
impl<T> TaggedElement<T> {
pub fn new(tag: Tag, element: T) -> Self {
Self { tag, element }
}
}
impl<T> std::ops::Deref for TaggedElement<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.element
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coverage_get_format1() {
// manually generated, corresponding to the glyphs (1, 7, 13, 27, 44);
const COV1_DATA: FontData = FontData::new(&[0, 1, 0, 5, 0, 1, 0, 7, 0, 13, 0, 27, 0, 44]);
let coverage = CoverageFormat1::read(COV1_DATA).unwrap();
assert_eq!(coverage.get(GlyphId::new(1)), Some(0));
assert_eq!(coverage.get(GlyphId::new(2)), None);
assert_eq!(coverage.get(GlyphId::new(7)), Some(1));
assert_eq!(coverage.get(GlyphId::new(27)), Some(3));
assert_eq!(coverage.get(GlyphId::new(45)), None);
}
#[test]
fn coverage_get_format2() {
// manually generated, corresponding to glyphs (5..10) and (30..40).
const COV2_DATA: FontData =
FontData::new(&[0, 2, 0, 2, 0, 5, 0, 9, 0, 0, 0, 30, 0, 39, 0, 5]);
let coverage = CoverageFormat2::read(COV2_DATA).unwrap();
assert_eq!(coverage.get(GlyphId::new(2)), None);
assert_eq!(coverage.get(GlyphId::new(7)), Some(2));
assert_eq!(coverage.get(GlyphId::new(9)), Some(4));
assert_eq!(coverage.get(GlyphId::new(10)), None);
assert_eq!(coverage.get(GlyphId::new(32)), Some(7));
assert_eq!(coverage.get(GlyphId::new(39)), Some(14));
assert_eq!(coverage.get(GlyphId::new(40)), None);
}
#[test]
fn classdef_get_format2() {
let classdef = ClassDef::read(FontData::new(
font_test_data::gdef::MARKATTACHCLASSDEF_TABLE,
))
.unwrap();
assert!(matches!(classdef, ClassDef::Format2(..)));
let gid_class_pairs = [
(616, 1),
(617, 1),
(618, 1),
(624, 1),
(625, 1),
(626, 1),
(652, 2),
(653, 2),
(654, 2),
(655, 2),
(661, 2),
];
for (gid, class) in gid_class_pairs {
assert_eq!(classdef.get(GlyphId16::new(gid)), class);
}
for (gid, class) in classdef.iter() {
assert_eq!(classdef.get(gid), class);
}
}
#[test]
fn delta_decode() {
// these examples come from the spec
assert_eq!(
iter_packed_values(0x123f, DeltaFormat::Local4BitDeltas, 4).collect::<Vec<_>>(),
&[1, 2, 3, -1]
);
assert_eq!(
iter_packed_values(0x5540, DeltaFormat::Local2BitDeltas, 5).collect::<Vec<_>>(),
&[1, 1, 1, 1, 1]
);
}
#[test]
fn delta_decode_all() {
// manually generated with write-fonts
let bytes: &[u8] = &[0, 7, 0, 13, 0, 3, 1, 244, 30, 245, 101, 8, 42, 0];
let device = Device::read(bytes.into()).unwrap();
assert_eq!(
device.iter().collect::<Vec<_>>(),
&[1i8, -12, 30, -11, 101, 8, 42]
);
}
}

View File

@@ -0,0 +1,756 @@
//! Support Layout Closure
use types::{BigEndian, GlyphId16};
use super::{
ArrayOfOffsets, ChainedClassSequenceRule, ChainedClassSequenceRuleSet, ChainedSequenceContext,
ChainedSequenceContextFormat1, ChainedSequenceContextFormat2, ChainedSequenceContextFormat3,
ChainedSequenceRule, ChainedSequenceRuleSet, ClassDef, ClassDefFormat1, ClassDefFormat2,
ClassSequenceRule, ClassSequenceRuleSet, CoverageTable, ExtensionLookup, FeatureList, FontRead,
GlyphId, LangSys, ReadError, Script, ScriptList, SequenceContext, SequenceContextFormat1,
SequenceContextFormat2, SequenceContextFormat3, SequenceLookupRecord, SequenceRule,
SequenceRuleSet, Subtables, Tag,
};
use crate::collections::IntSet;
const MAX_SCRIPTS: u16 = 500;
const MAX_LANGSYS: u16 = 2000;
const MAX_FEATURE_INDICES: u16 = 1500;
const MAX_NESTING_LEVEL: u8 = 64;
const MAX_LOOKUP_VISIT_COUNT: u16 = 35000;
struct CollectFeaturesContext<'a> {
script_count: u16,
langsys_count: u16,
feature_index_count: u16,
visited_script: IntSet<u32>,
visited_langsys: IntSet<u32>,
feature_indices: &'a mut IntSet<u16>,
feature_indices_filter: IntSet<u16>,
table_head: usize,
}
impl<'a> CollectFeaturesContext<'a> {
pub(crate) fn new(
features: &IntSet<Tag>,
table_head: usize,
feature_list: &'a FeatureList<'a>,
feature_indices: &'a mut IntSet<u16>,
) -> Self {
Self {
script_count: 0,
langsys_count: 0,
feature_index_count: 0,
visited_script: IntSet::empty(),
visited_langsys: IntSet::empty(),
feature_indices,
feature_indices_filter: feature_list
.feature_records()
.iter()
.enumerate()
.filter(|(_i, record)| features.contains(record.feature_tag()))
.map(|(idx, _)| idx as u16)
.collect(),
table_head,
}
}
/// Return true if the script limit has been exceeded or the script is visited before
pub(crate) fn script_visited(&mut self, s: &Script) -> bool {
if self.script_count > MAX_SCRIPTS {
return true;
}
self.script_count += 1;
let delta = (s.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
!self.visited_script.insert(delta)
}
/// Return true if the Langsys limit has been exceeded or the Langsys is visited before
pub(crate) fn langsys_visited(&mut self, langsys: &LangSys) -> bool {
if self.langsys_count > MAX_LANGSYS {
return true;
}
self.langsys_count += 1;
let delta = (langsys.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
!self.visited_langsys.insert(delta)
}
/// Returns true if the feature limit has been exceeded
pub(crate) fn feature_indices_limit_exceeded(&mut self, count: u16) -> bool {
let (new_count, overflow) = self.feature_index_count.overflowing_add(count);
if overflow {
self.feature_index_count = MAX_FEATURE_INDICES;
return true;
}
self.feature_index_count = new_count;
new_count > MAX_FEATURE_INDICES
}
}
impl ScriptList<'_> {
/// Return a set of all feature indices underneath the specified scripts, languages and features
pub(crate) fn collect_features(
&self,
layout_table_head: usize,
feature_list: &FeatureList,
scripts: &IntSet<Tag>,
languages: &IntSet<Tag>,
features: &IntSet<Tag>,
) -> Result<IntSet<u16>, ReadError> {
let mut out = IntSet::empty();
let mut c =
CollectFeaturesContext::new(features, layout_table_head, feature_list, &mut out);
let script_records = self.script_records();
let font_data = self.offset_data();
if scripts.is_inverted() {
for record in script_records {
let tag = record.script_tag();
if !scripts.contains(tag) {
continue;
}
let script = record.script(font_data)?;
script.collect_features(&mut c, languages)?;
}
} else {
for idx in scripts.iter().filter_map(|tag| self.index_for_tag(tag)) {
let script = script_records[idx as usize].script(font_data)?;
script.collect_features(&mut c, languages)?;
}
}
Ok(out)
}
}
impl Script<'_> {
fn collect_features(
&self,
c: &mut CollectFeaturesContext,
languages: &IntSet<Tag>,
) -> Result<(), ReadError> {
if c.script_visited(self) {
return Ok(());
}
let lang_sys_records = self.lang_sys_records();
let font_data = self.offset_data();
if let Some(default_lang_sys) = self.default_lang_sys().transpose()? {
default_lang_sys.collect_features(c);
}
if languages.is_inverted() {
for record in lang_sys_records {
let tag = record.lang_sys_tag();
if !languages.contains(tag) {
continue;
}
let lang_sys = record.lang_sys(font_data)?;
lang_sys.collect_features(c);
}
} else {
for idx in languages
.iter()
.filter_map(|tag| self.lang_sys_index_for_tag(tag))
{
let lang_sys = lang_sys_records[idx as usize].lang_sys(font_data)?;
lang_sys.collect_features(c);
}
}
Ok(())
}
}
impl LangSys<'_> {
fn collect_features(&self, c: &mut CollectFeaturesContext) {
if c.langsys_visited(self) {
return;
}
if c.feature_indices_filter.is_empty() {
return;
}
let required_feature_idx = self.required_feature_index();
if required_feature_idx != 0xFFFF
&& !c.feature_indices_limit_exceeded(1)
&& c.feature_indices_filter.contains(required_feature_idx)
{
c.feature_indices.insert(required_feature_idx);
}
if c.feature_indices_limit_exceeded(self.feature_index_count()) {
return;
}
for feature_index in self.feature_indices() {
let idx = feature_index.get();
if !c.feature_indices_filter.contains(idx) {
continue;
}
c.feature_indices.insert(idx);
c.feature_indices_filter.remove(idx);
}
}
}
#[allow(dead_code)]
pub(crate) struct LookupClosureCtx<'a> {
visited_lookups: IntSet<u16>,
inactive_lookups: IntSet<u16>,
glyph_set: &'a IntSet<GlyphId>,
lookup_count: u16,
nesting_level_left: u8,
}
impl<'a> LookupClosureCtx<'a> {
pub(crate) fn new(glyph_set: &'a IntSet<GlyphId>) -> Self {
Self {
visited_lookups: IntSet::empty(),
inactive_lookups: IntSet::empty(),
glyph_set,
lookup_count: 0,
nesting_level_left: MAX_NESTING_LEVEL,
}
}
pub(crate) fn visited_lookups(&self) -> &IntSet<u16> {
&self.visited_lookups
}
pub(crate) fn inactive_lookups(&self) -> &IntSet<u16> {
&self.inactive_lookups
}
pub(crate) fn glyphs(&self) -> &IntSet<GlyphId> {
self.glyph_set
}
pub(crate) fn set_lookup_inactive(&mut self, lookup_index: u16) {
self.inactive_lookups.insert(lookup_index);
}
#[allow(dead_code)]
pub(crate) fn lookup_limit_exceed(&self) -> bool {
self.lookup_count > MAX_LOOKUP_VISIT_COUNT
}
// return false if lookup limit exceeded or lookup visited,and visited set is not modified
// Otherwise return true and insert lookup index into the visited set
pub(crate) fn should_visit_lookup(&mut self, lookup_index: u16) -> bool {
if self.lookup_count > MAX_LOOKUP_VISIT_COUNT {
return false;
}
self.lookup_count += 1;
self.visited_lookups.insert(lookup_index)
}
}
/// Compute the transitive closure of lookups
pub(crate) trait LookupClosure {
fn closure_lookups(&self, _c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
Ok(())
}
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError>;
}
impl LookupClosure for ClassDef<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
ClassDef::Format1(table) => table.intersects(glyph_set),
ClassDef::Format2(table) => table.intersects(glyph_set),
}
}
}
impl LookupClosure for ClassDefFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let glyph_count = self.glyph_count();
if glyph_count == 0 {
return Ok(false);
}
let start = self.start_glyph_id().to_u32();
let end = start + glyph_count as u32;
let start_glyph = GlyphId::from(start);
let class_values = self.class_value_array();
if glyph_set.contains(start_glyph) && class_values[0] != 0 {
return Ok(true);
}
while let Some(g) = glyph_set.iter_after(start_glyph).next() {
let g = g.to_u32();
if g >= end {
break;
}
let Some(class) = class_values.get((g - start) as usize) else {
break;
};
if class.get() != 0 {
return Ok(true);
}
}
Ok(false)
}
}
impl LookupClosure for ClassDefFormat2<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let num_ranges = self.class_range_count();
let num_bits = 16 - num_ranges.leading_zeros();
if num_ranges as u64 > glyph_set.len() * num_bits as u64 {
for g in glyph_set.iter().map(|g| GlyphId16::from(g.to_u32() as u16)) {
if self.get(g) != 0 {
return Ok(true);
}
}
} else {
for record in self.class_range_records() {
let first = GlyphId::from(record.start_glyph_id());
let last = GlyphId::from(record.end_glyph_id());
if glyph_set.intersects_range(first..=last) && record.class() != 0 {
return Ok(true);
}
}
}
Ok(false)
}
}
impl<'a, T, Ext> LookupClosure for Subtables<'a, T, Ext>
where
T: LookupClosure + FontRead<'a> + 'a,
Ext: ExtensionLookup<'a, T> + 'a,
{
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
for sub in self.iter() {
if sub?.intersects(glyph_set)? {
return Ok(true);
}
}
Ok(false)
}
}
// these are basically the same; but we need to jump through some hoops
// to get the fields to line up
pub(crate) enum ContextFormat1<'a> {
Plain(SequenceContextFormat1<'a>),
Chain(ChainedSequenceContextFormat1<'a>),
}
pub(crate) enum Format1RuleSet<'a> {
Plain(SequenceRuleSet<'a>),
Chain(ChainedSequenceRuleSet<'a>),
}
pub(crate) enum Format1Rule<'a> {
Plain(SequenceRule<'a>),
Chain(ChainedSequenceRule<'a>),
}
impl ContextFormat1<'_> {
pub(crate) fn coverage(&self) -> Result<CoverageTable, ReadError> {
match self {
ContextFormat1::Plain(table) => table.coverage(),
ContextFormat1::Chain(table) => table.coverage(),
}
}
pub(crate) fn rule_sets(
&self,
) -> impl Iterator<Item = Option<Result<Format1RuleSet, ReadError>>> {
let (left, right) = match self {
ContextFormat1::Plain(table) => (
Some(
table
.seq_rule_sets()
.iter()
.map(|rs| rs.map(|rs| rs.map(Format1RuleSet::Plain))),
),
None,
),
ContextFormat1::Chain(table) => (
None,
Some(
table
.chained_seq_rule_sets()
.iter()
.map(|rs| rs.map(|rs| rs.map(Format1RuleSet::Chain))),
),
),
};
left.into_iter()
.flatten()
.chain(right.into_iter().flatten())
}
}
impl Format1RuleSet<'_> {
pub(crate) fn rules(&self) -> impl Iterator<Item = Result<Format1Rule, ReadError>> {
let (left, right) = match self {
Self::Plain(table) => (
Some(
table
.seq_rules()
.iter()
.map(|rule| rule.map(Format1Rule::Plain)),
),
None,
),
Self::Chain(table) => (
None,
Some(
table
.chained_seq_rules()
.iter()
.map(|rule| rule.map(Format1Rule::Chain)),
),
),
};
left.into_iter()
.flatten()
.chain(right.into_iter().flatten())
}
}
impl Format1Rule<'_> {
pub(crate) fn input_sequence(&self) -> &[BigEndian<GlyphId16>] {
match self {
Self::Plain(table) => table.input_sequence(),
Self::Chain(table) => table.input_sequence(),
}
}
pub(crate) fn matches_glyphs(&self, glyphs: &IntSet<GlyphId16>) -> bool {
let (backtrack, lookahead) = match self {
Format1Rule::Plain(_) => (None, None),
Format1Rule::Chain(table) => (
Some(table.backtrack_sequence()),
Some(table.lookahead_sequence()),
),
};
self.input_sequence()
.iter()
.chain(backtrack.into_iter().flatten())
.chain(lookahead.into_iter().flatten())
.all(|gid| glyphs.contains(gid.get()))
}
pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
match self {
Self::Plain(table) => table.seq_lookup_records(),
Self::Chain(table) => table.seq_lookup_records(),
}
}
}
impl LookupClosure for &[BigEndian<GlyphId16>] {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
Ok(self
.iter()
.all(|g| glyph_set.contains(GlyphId::from(g.get()))))
}
}
impl LookupClosure for Format1Rule<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
Self::Plain(table) => table.input_sequence().intersects(glyph_set),
Self::Chain(table) => Ok(table.backtrack_sequence().intersects(glyph_set)?
&& table.input_sequence().intersects(glyph_set)?
&& table.lookahead_sequence().intersects(glyph_set)?),
}
}
}
pub(crate) enum ContextFormat2<'a> {
Plain(SequenceContextFormat2<'a>),
Chain(ChainedSequenceContextFormat2<'a>),
}
pub(crate) enum Format2RuleSet<'a> {
Plain(ClassSequenceRuleSet<'a>),
Chain(ChainedClassSequenceRuleSet<'a>),
}
pub(crate) enum Format2Rule<'a> {
Plain(ClassSequenceRule<'a>),
Chain(ChainedClassSequenceRule<'a>),
}
impl ContextFormat2<'_> {
pub(crate) fn coverage(&self) -> Result<CoverageTable<'_>, ReadError> {
match self {
ContextFormat2::Plain(table) => table.coverage(),
ContextFormat2::Chain(table) => table.coverage(),
}
}
pub(crate) fn input_class_def(&self) -> Result<ClassDef<'_>, ReadError> {
match self {
ContextFormat2::Plain(table_ref) => table_ref.class_def(),
ContextFormat2::Chain(table_ref) => table_ref.input_class_def(),
}
}
pub(crate) fn rule_sets(
&self,
) -> impl Iterator<Item = Option<Result<Format2RuleSet, ReadError>>> {
let (left, right) = match self {
ContextFormat2::Plain(table) => (
Some(
table
.class_seq_rule_sets()
.iter()
.map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Plain))),
),
None,
),
ContextFormat2::Chain(table) => (
None,
Some(
table
.chained_class_seq_rule_sets()
.iter()
.map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Chain))),
),
),
};
left.into_iter()
.flatten()
.chain(right.into_iter().flatten())
}
}
impl Format2RuleSet<'_> {
pub(crate) fn rules(&self) -> impl Iterator<Item = Result<Format2Rule, ReadError>> {
let (left, right) = match self {
Format2RuleSet::Plain(table) => (
Some(
table
.class_seq_rules()
.iter()
.map(|rule| rule.map(Format2Rule::Plain)),
),
None,
),
Format2RuleSet::Chain(table) => (
None,
Some(
table
.chained_class_seq_rules()
.iter()
.map(|rule| rule.map(Format2Rule::Chain)),
),
),
};
left.into_iter()
.flatten()
.chain(right.into_iter().flatten())
}
}
impl Format2Rule<'_> {
pub(crate) fn input_sequence(&self) -> &[BigEndian<u16>] {
match self {
Self::Plain(table) => table.input_sequence(),
Self::Chain(table) => table.input_sequence(),
}
}
pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
match self {
Self::Plain(table) => table.seq_lookup_records(),
Self::Chain(table) => table.seq_lookup_records(),
}
}
//TODO: Fix glyph closure: this one is incorrect in case of ChainedContext, replace it with intersects()
pub(crate) fn matches_classes(&self, classes: &IntSet<u16>) -> bool {
let (backtrack, lookahead) = match self {
Self::Plain(_) => (None, None),
Self::Chain(table) => (
Some(table.backtrack_sequence()),
Some(table.lookahead_sequence()),
),
};
self.input_sequence()
.iter()
.chain(backtrack.into_iter().flatten())
.chain(lookahead.into_iter().flatten())
.all(|gid| classes.contains(gid.get()))
}
pub(crate) fn intersects(
&self,
input_classes: &IntSet<u16>,
backtrack_classes: &IntSet<u16>,
lookahead_classes: &IntSet<u16>,
) -> bool {
match self {
Self::Plain(table) => table.intersects(input_classes),
Self::Chain(table) => {
table.intersects(input_classes, backtrack_classes, lookahead_classes)
}
}
}
}
impl ClassSequenceRule<'_> {
fn intersects(&self, input_classes: &IntSet<u16>) -> bool {
self.input_sequence()
.iter()
.all(|c| input_classes.contains(c.get()))
}
}
impl ChainedClassSequenceRule<'_> {
fn intersects(
&self,
input_classes: &IntSet<u16>,
backtrack_classes: &IntSet<u16>,
lookahead_classes: &IntSet<u16>,
) -> bool {
self.input_sequence()
.iter()
.all(|c| input_classes.contains(c.get()))
&& self
.backtrack_sequence()
.iter()
.all(|c| backtrack_classes.contains(c.get()))
&& self
.lookahead_sequence()
.iter()
.all(|c| lookahead_classes.contains(c.get()))
}
}
pub(crate) enum ContextFormat3<'a> {
Plain(SequenceContextFormat3<'a>),
Chain(ChainedSequenceContextFormat3<'a>),
}
impl ContextFormat3<'_> {
pub(crate) fn coverages(&self) -> ArrayOfOffsets<CoverageTable> {
match self {
ContextFormat3::Plain(table) => table.coverages(),
ContextFormat3::Chain(table) => table.input_coverages(),
}
}
pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
match self {
ContextFormat3::Plain(table) => table.seq_lookup_records(),
ContextFormat3::Chain(table) => table.seq_lookup_records(),
}
}
pub(crate) fn matches_glyphs(&self, glyphs: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let (backtrack, lookahead) = match self {
Self::Plain(_) => (None, None),
Self::Chain(table) => (
Some(table.backtrack_coverages()),
Some(table.lookahead_coverages()),
),
};
for coverage in self
.coverages()
.iter()
.chain(backtrack.into_iter().flat_map(|x| x.iter()))
.chain(lookahead.into_iter().flat_map(|x| x.iter()))
{
if !coverage?.intersects(glyphs) {
return Ok(false);
}
}
Ok(true)
}
}
impl LookupClosure for ContextFormat1<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let coverage = self.coverage()?;
for rule_set in coverage
.iter()
.zip(self.rule_sets())
.filter_map(|(g, rule_set)| rule_set.filter(|_| glyph_set.contains(GlyphId::from(g))))
{
for rule in rule_set?.rules() {
if rule?.intersects(glyph_set)? {
return Ok(true);
}
}
}
Ok(false)
}
}
impl LookupClosure for ContextFormat2<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
let coverage = self.coverage()?;
let retained_coverage_glyphs = coverage.intersect_set(glyph_set);
if retained_coverage_glyphs.is_empty() {
return Ok(false);
}
let input_class_def = self.input_class_def()?;
let coverage_glyph_classes = input_class_def.intersect_classes(&retained_coverage_glyphs);
let input_glyph_classes = input_class_def.intersect_classes(glyph_set);
let backtrack_classes = match self {
Self::Plain(_) => IntSet::empty(),
Self::Chain(table) => table.backtrack_class_def()?.intersect_classes(glyph_set),
};
let lookahead_classes = match self {
Self::Plain(_) => IntSet::empty(),
Self::Chain(table) => table.lookahead_class_def()?.intersect_classes(glyph_set),
};
for rule_set in self.rule_sets().enumerate().filter_map(|(c, rule_set)| {
coverage_glyph_classes
.contains(c as u16)
.then_some(rule_set)
.flatten()
}) {
for rule in rule_set?.rules() {
if rule?.intersects(&input_glyph_classes, &backtrack_classes, &lookahead_classes) {
return Ok(true);
}
}
}
Ok(false)
}
}
impl LookupClosure for ContextFormat3<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
self.matches_glyphs(glyph_set)
}
}
impl LookupClosure for SequenceContext<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
Self::Format1(table) => ContextFormat1::Plain(table.clone()).intersects(glyph_set),
Self::Format2(table) => ContextFormat2::Plain(table.clone()).intersects(glyph_set),
Self::Format3(table) => ContextFormat3::Plain(table.clone()).intersects(glyph_set),
}
}
}
impl LookupClosure for ChainedSequenceContext<'_> {
fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
match self {
Self::Format1(table) => ContextFormat1::Chain(table.clone()).intersects(glyph_set),
Self::Format2(table) => ContextFormat2::Chain(table.clone()).intersects(glyph_set),
Self::Format3(table) => ContextFormat3::Chain(table.clone()).intersects(glyph_set),
}
}
}

View File

@@ -0,0 +1,33 @@
//! Additional support for working with OpenType features.
use super::{Feature, FeatureList, ReadError, TaggedElement};
impl<'a> FeatureList<'a> {
/// Returns the tag and feature at the given index.
pub fn get(&self, index: u16) -> Result<TaggedElement<Feature<'a>>, ReadError> {
self.feature_records()
.get(index as usize)
.ok_or(ReadError::OutOfBounds)
.and_then(|rec| {
Ok(TaggedElement::new(
rec.feature_tag(),
rec.feature(self.offset_data())?,
))
})
}
}
#[cfg(test)]
mod tests {
use crate::{FontRef, TableProvider, Tag};
#[test]
fn feature_list_get() {
let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
let gsub = font.gsub().unwrap();
let feature_list = gsub.feature_list().unwrap();
assert_eq!(feature_list.get(0).unwrap().tag, Tag::new(b"c2sc"));
assert_eq!(feature_list.get(1).unwrap().tag, Tag::new(b"liga"));
assert!(feature_list.get(2).is_err());
}
}

View File

@@ -0,0 +1,101 @@
//! The lookup flag type.
//!
//! This is kind-of-but-not-quite-exactly a bit enumeration, and so we implement
//! it manually.
use core::ops::{BitOr, BitOrAssign};
/// The [LookupFlag](https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#lookupFlag) bit enumeration.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LookupFlag(u16);
//NOTE: this impl has the potential to make garbage if used on two lookupflag
//instances which have different mark attachment masks set, but as that field
//is not really used in compilation, which is where this impl will be helpful,
//the risk that this is the source of an actual bug seems very low,
impl BitOr for LookupFlag {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for LookupFlag {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0
}
}
impl LookupFlag {
/// This bit relates only to the correct processing of GPOS type 3 (cursive attachment) lookups
///
/// When this bit is set, the last glyph in a given sequence to which the cursive
/// attachment lookup is applied, will be positioned on the baseline.
pub const RIGHT_TO_LEFT: Self = LookupFlag(0x0001);
/// If set, skips over base glyphs
pub const IGNORE_BASE_GLYPHS: Self = LookupFlag(0x002);
/// If set, skips over ligatures
pub const IGNORE_LIGATURES: Self = LookupFlag(0x004);
/// If set, skips over all combining marks
pub const IGNORE_MARKS: Self = LookupFlag(0x008);
/// If set, indicates that the lookup table structure is followed by a MarkFilteringSet field.
///
/// The layout engine skips over all mark glyphs not in the mark filtering set indicated.
pub const USE_MARK_FILTERING_SET: Self = LookupFlag(0x010);
// union of all flags, above
const FLAG_MASK: Self = LookupFlag(0x1F);
/// Return new, empty flags
pub fn empty() -> Self {
Self(0)
}
/// Construct a LookupFlag from a raw value, discarding invalid bits
pub fn from_bits_truncate(bits: u16) -> Self {
const VALID_BITS: u16 = !0x00E0;
Self(bits & VALID_BITS)
}
/// Raw transmutation to u16.
pub fn to_bits(self) -> u16 {
self.0
}
/// Returns `true` if all of the flags in `other` are contained within `self`.
#[inline]
pub const fn contains(&self, other: Self) -> bool {
// only count flag bits
let other = other.0 & Self::FLAG_MASK.0;
(self.0 & other) == other
}
/// If not zero, skips over all marks of attachment type different from specified.
pub fn mark_attachment_class(self) -> Option<u16> {
let val = self.0 & 0xff00;
if val == 0 {
None
} else {
Some(val >> 8)
}
}
/// If not zero, skips over all marks of attachment type different from specified.
pub fn set_mark_attachment_class(&mut self, val: u16) {
let val = (val & 0xff) << 8;
self.0 = (self.0 & 0xff) | val;
}
}
impl types::Scalar for LookupFlag {
type Raw = <u16 as types::Scalar>::Raw;
fn to_raw(self) -> Self::Raw {
self.0.to_raw()
}
fn from_raw(raw: Self::Raw) -> Self {
let t = <u16>::from_raw(raw);
Self(t)
}
}

View File

@@ -0,0 +1,385 @@
//! Additional support for working with OpenType scripts and language systems.
use super::{FeatureList, LangSys, ReadError, Script, ScriptList, Tag, TaggedElement};
use std::ops::Deref;
/// A script chosen from a set of candidate tags.
///
/// Returned by the [`ScriptList::select`] method.
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct SelectedScript {
/// The actual OpenType tag of the chosen script.
pub tag: Tag,
/// Index of the script in the [`ScriptList`].
pub index: u16,
/// True if a script was chosen that wasn't in the requested list.
pub is_fallback: bool,
}
impl<'a> ScriptList<'a> {
/// Returns the index of the script with the given tag.
pub fn index_for_tag(&self, tag: Tag) -> Option<u16> {
self.script_records()
.binary_search_by_key(&tag, |rec| rec.script_tag())
.map(|index| index as u16)
.ok()
}
/// Returns the tag and script at the given index.
pub fn get(&self, index: u16) -> Result<TaggedElement<Script<'a>>, ReadError> {
self.script_records()
.get(index as usize)
.ok_or(ReadError::OutOfBounds)
.and_then(|rec| {
Ok(TaggedElement::new(
rec.script_tag(),
rec.script(self.offset_data())?,
))
})
}
/// Finds the first available script that matches one of the given tags.
///
/// When none of the requested scripts are available, then `DFLT`, `dflt`
/// and `latn` tags are tried in that order.
///
/// If you're starting from a Unicode script code, use the
/// [`ScriptTags::from_unicode`] function to generate the appropriate set
/// of tags to pass to this method.
///
/// See [`hb_ot_layout_table_select_script`](https://github.com/harfbuzz/harfbuzz/blob/2edc371e97d6d2c5ad0e085b26e9af0123501647/src/hb-ot-layout.cc#L547)
/// for the corresponding HarfBuzz function.
pub fn select(&self, tags: &[Tag]) -> Option<SelectedScript> {
for &tag in tags {
if let Some(index) = self.index_for_tag(tag) {
return Some(SelectedScript {
tag,
index,
is_fallback: false,
});
}
}
for tag in [
// Try finding 'DFLT'
Tag::new(b"DFLT"),
// Try with 'dflt'; MS site has had typos and many fonts use it now :(
Tag::new(b"dflt"),
// try with 'latn'; some old fonts put their features there even though
// they're really trying to support Thai, for example :(
Tag::new(b"latn"),
] {
if let Some(index) = self.index_for_tag(tag) {
return Some(SelectedScript {
tag,
index,
is_fallback: true,
});
}
}
None
}
}
impl<'a> Script<'a> {
/// If the script contains a language system with the given tag, returns
/// the index.
pub fn lang_sys_index_for_tag(&self, tag: Tag) -> Option<u16> {
self.lang_sys_records()
.binary_search_by_key(&tag, |rec| rec.lang_sys_tag())
.map(|index| index as u16)
.ok()
}
/// Returns the language system with the given index.
pub fn lang_sys(&self, index: u16) -> Result<TaggedElement<LangSys<'a>>, ReadError> {
self.lang_sys_records()
.get(index as usize)
.ok_or(ReadError::OutOfBounds)
.and_then(|rec| {
Ok(TaggedElement::new(
rec.lang_sys_tag(),
rec.lang_sys(self.offset_data())?,
))
})
}
}
impl LangSys<'_> {
/// If the language system references a feature with the given tag,
/// returns the index of that feature in the specified feature list.
///
/// The feature list can be obtained from the `feature_list` method on
/// the parent [Gsub](crate::tables::gsub::Gsub) or
/// [Gpos](crate::tables::gpos::Gpos) tables.
pub fn feature_index_for_tag(&self, list: &FeatureList, tag: Tag) -> Option<u16> {
let records = list.feature_records();
self.feature_indices()
.iter()
.map(|ix| ix.get())
.find(|&feature_ix| {
records
.get(feature_ix as usize)
.map(|rec| rec.feature_tag())
== Some(tag)
})
}
}
/// A prioritized list of OpenType script tags mapped from a Unicode script
/// tag.
///
/// This is useful as input to [`ScriptList::select`] when you have a Unicode
/// script and would like to find the appropriate OpenType script for shaping.
#[derive(Copy, Clone, PartialEq, Eq, Default)]
pub struct ScriptTags {
tags: [Tag; 3],
len: usize,
}
impl ScriptTags {
/// Given a [Unicode script code](https://unicode.org/iso15924/iso15924-codes.html),
/// returns a prioritized list of matching
/// [OpenType script tags](https://learn.microsoft.com/en-us/typography/opentype/spec/scripttags).
///
/// See [hb_ot_all_tags_from_script](https://github.com/harfbuzz/harfbuzz/blob/63d09dbefcf7ad9f794ca96445d37b6d8c3c9124/src/hb-ot-tag.cc#L155C1-L155C27)
/// for the equivalent HarfBuzz function.
pub fn from_unicode(unicode_script: Tag) -> Self {
let mut tags = [Tag::default(); 3];
let mut len = 0;
if let Some(new_tag) = new_tag_from_unicode(unicode_script) {
// Myanmar maps to mym2 but there is no mym3
if new_tag != Tag::new(b"mym2") {
let mut bytes = new_tag.to_be_bytes();
bytes[3] = b'3';
tags[len] = Tag::new(&bytes);
len += 1;
}
tags[len] = new_tag;
len += 1;
}
tags[len] = old_tag_from_unicode(unicode_script);
len += 1;
Self { tags, len }
}
/// Returns a slice containing the mapped script tags.
pub fn as_slice(&self) -> &[Tag] {
&self.tags[..self.len]
}
}
impl Deref for ScriptTags {
type Target = [Tag];
fn deref(&self) -> &Self::Target {
&self.tags[..self.len]
}
}
impl std::fmt::Debug for ScriptTags {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self.as_slice())
}
}
// See <https://github.com/harfbuzz/harfbuzz/blob/63d09dbefcf7ad9f794ca96445d37b6d8c3c9124/src/hb-ot-tag.cc#L37>
fn old_tag_from_unicode(unicode_script: Tag) -> Tag {
let mut bytes = unicode_script.to_be_bytes();
let tag_bytes = match &bytes {
b"Zmth" => b"math",
// Katakana and Hiragana both map to 'kana'
b"Hira" => b"kana",
// Spaces at the end are preserved, unlike ISO 15924
b"Laoo" => b"lao ",
b"Yiii" => b"yi ",
// Unicode 5.0 additions
b"Nkoo" => b"nko ",
// Unicode 5.1 additions
b"Vaii" => b"vai ",
_ => {
// Else, just change the first char to lowercase
bytes[0] = bytes[0].to_ascii_lowercase();
&bytes
}
};
Tag::new(tag_bytes)
}
/// Mapping from Unicode script code to "new" OpenType script
/// tags.
#[doc(hidden)]
pub const UNICODE_TO_NEW_OPENTYPE_SCRIPT_TAGS: &[(&[u8; 4], Tag)] = &[
(b"Beng", Tag::new(b"bng2")),
(b"Deva", Tag::new(b"dev2")),
(b"Gujr", Tag::new(b"gjr2")),
(b"Guru", Tag::new(b"gur2")),
(b"Knda", Tag::new(b"knd2")),
(b"Mlym", Tag::new(b"mlm2")),
(b"Mymr", Tag::new(b"mym2")),
(b"Orya", Tag::new(b"ory2")),
(b"Taml", Tag::new(b"tml2")),
(b"Telu", Tag::new(b"tel2")),
];
// See <https://github.com/harfbuzz/harfbuzz/blob/63d09dbefcf7ad9f794ca96445d37b6d8c3c9124/src/hb-ot-tag.cc#L84>
fn new_tag_from_unicode(unicode_script: Tag) -> Option<Tag> {
let ix = UNICODE_TO_NEW_OPENTYPE_SCRIPT_TAGS
.binary_search_by_key(&unicode_script.to_be_bytes(), |entry| *entry.0)
.ok()?;
UNICODE_TO_NEW_OPENTYPE_SCRIPT_TAGS
.get(ix)
.map(|entry| entry.1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FontRef, TableProvider};
#[test]
fn script_index_for_tag() {
let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
let gsub_scripts = font.gsub().unwrap().script_list().unwrap();
let ordered_scripts = [b"DFLT", b"cyrl", b"grek", b"hebr", b"latn"];
for (index, tag) in ordered_scripts.into_iter().enumerate() {
let tag = Tag::new(tag);
assert_eq!(gsub_scripts.index_for_tag(tag), Some(index as u16));
}
}
#[test]
fn simple_script_tag_from_unicode() {
let unicode_tags = [b"Cyrl", b"Grek", b"Hebr", b"Latn"];
for unicode_tag in unicode_tags {
// These should all return a single tag that is simply
// the lowercase version of the Unicode tag
let mut bytes = *unicode_tag;
bytes[0] = bytes[0].to_ascii_lowercase();
let expected_tag = Tag::new(&bytes);
let result = ScriptTags::from_unicode(Tag::new(unicode_tag));
assert_eq!(&*result, &[expected_tag]);
}
}
#[test]
fn exception_script_tag_from_unicode() {
let cases = [
// (Unicode, OpenType)
(b"Kana", b"kana"),
// Hiragana maps to kana
(b"Hira", b"kana"),
// Unicode extends last char but OpenType pads with spaces
// for tags < 4 bytes
(b"Nkoo", b"nko "),
(b"Yiii", b"yi "),
(b"Vaii", b"vai "),
];
for (unicode_tag, ot_tag) in cases {
let result = ScriptTags::from_unicode(Tag::new(unicode_tag));
assert_eq!(&*result, &[Tag::new(ot_tag)]);
}
}
#[test]
fn multi_script_tags_from_unicode() {
let cases = [
// (Unicode, OpenType)
(b"Beng", &[b"bng3", b"bng2", b"beng"][..]),
(b"Orya", &[b"ory3", b"ory2", b"orya"]),
(b"Mlym", &[b"mlm3", b"mlm2", b"mlym"]),
// There's no version 3 tag for Myanmar
(b"Mymr", &[b"mym2", b"mymr"]),
];
for (unicode_tag, ot_tags) in cases {
let result = ScriptTags::from_unicode(Tag::new(unicode_tag));
let ot_tags = ot_tags
.iter()
.map(|bytes| Tag::new(bytes))
.collect::<Vec<_>>();
assert_eq!(&*result, &ot_tags);
}
}
#[test]
fn select_scripts_from_unicode() {
let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
let gsub_scripts = font.gsub().unwrap().script_list().unwrap();
// We know Hebrew is available
let hebr = gsub_scripts
.select(&ScriptTags::from_unicode(Tag::new(b"Hebr")))
.unwrap();
assert_eq!(
hebr,
SelectedScript {
tag: Tag::new(b"hebr"),
index: 3,
is_fallback: false,
}
);
// But this font doesn't contain any Indic scripts so we'll
// select a fallback for Bengali
let beng = gsub_scripts
.select(&ScriptTags::from_unicode(Tag::new(b"Beng")))
.unwrap();
assert_eq!(
beng,
SelectedScript {
tag: Tag::new(b"DFLT"),
index: 0,
is_fallback: true,
}
);
}
#[test]
fn script_list_get() {
const LATN: Tag = Tag::new(b"latn");
let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
let gsub = font.gsub().unwrap();
let script_list = gsub.script_list().unwrap();
let latn_script_index = script_list.index_for_tag(LATN).unwrap();
assert_eq!(latn_script_index, 1);
let script = script_list.get(latn_script_index).unwrap();
assert_eq!(script.tag, LATN);
}
#[test]
fn script_lang_sys_helpers() {
const TRK: Tag = Tag::new(b"TRK ");
let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
let gsub = font.gsub().unwrap();
let script_list = gsub.script_list().unwrap();
let script = script_list.get(1).unwrap();
let lang_sys_index = script.lang_sys_index_for_tag(TRK).unwrap();
assert_eq!(lang_sys_index, 0);
assert_eq!(script.lang_sys(lang_sys_index).unwrap().tag, TRK);
}
#[test]
fn feature_index_for_tag() {
let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
let gsub = font.gsub().unwrap();
let script_list = gsub.script_list().unwrap();
let feature_list = gsub.feature_list().unwrap();
let lang_sys = script_list
.get(1)
.unwrap()
.default_lang_sys()
.unwrap()
.unwrap();
assert_eq!(
lang_sys.feature_index_for_tag(&feature_list, Tag::new(b"rclt")),
Some(0)
);
assert_eq!(
lang_sys.feature_index_for_tag(&feature_list, Tag::new(b"rlig")),
Some(1)
);
for tag in [b"locl", b"abvs", b"liga"] {
assert_eq!(
lang_sys.feature_index_for_tag(&feature_list, Tag::new(tag)),
None
);
}
}
}

View File

@@ -0,0 +1,41 @@
use super::*;
use font_test_data::layout as test_data;
#[test]
fn example_1_scripts() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#example-1-scriptlist-table-and-scriptrecords
let table = ScriptList::read(test_data::SCRIPTS.into()).unwrap();
assert_eq!(table.script_count(), 3);
assert_eq!(table.script_records()[0].script_tag(), Tag::new(b"hani"));
assert_eq!(table.script_records()[1].script_tag(), Tag::new(b"kana"));
assert_eq!(table.script_records()[2].script_tag(), Tag::new(b"latn"));
}
#[test]
fn example_2_scripts_and_langs() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#example-2-script-table-langsysrecord-and-langsys-table
let table = Script::read(test_data::SCRIPTS_AND_LANGUAGES.into()).unwrap();
let def_sys = table.default_lang_sys().unwrap().unwrap();
assert_eq!(def_sys.required_feature_index(), 0xffff);
assert_eq!(def_sys.feature_index_count(), 3);
assert_eq!(table.lang_sys_count(), 1);
let urdu_record = &table.lang_sys_records()[0];
assert_eq!(urdu_record.lang_sys_tag(), Tag::new(b"URD "));
let urdu_sys = urdu_record.lang_sys(table.offset_data()).unwrap();
assert_eq!(urdu_sys.required_feature_index(), 3);
assert_eq!(urdu_sys.feature_index_count(), 3);
}
#[test]
fn example_3_featurelist_and_feature() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#example-3-featurelist-table-and-feature-table
let table = FeatureList::read(test_data::FEATURELIST_AND_FEATURE.into()).unwrap();
assert_eq!(table.feature_count(), 3);
let turkish_liga_record = &table.feature_records()[0];
let feature = turkish_liga_record.feature(table.offset_data()).unwrap();
assert!(feature.feature_params_offset().is_null());
assert_eq!(feature.lookup_list_indices().len(), 1);
}

190
vendor/read-fonts/src/tables/loca.rs vendored Normal file
View File

@@ -0,0 +1,190 @@
//! The [loca (Index to Location)][loca] table
//!
//! [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca
use crate::{
read::{FontRead, FontReadWithArgs, ReadArgs, ReadError},
table_provider::TopLevelTable,
FontData,
};
use types::{BigEndian, GlyphId, Tag};
#[cfg(feature = "experimental_traverse")]
use crate::traversal;
/// The [loca] table.
///
/// [loca]: https://docs.microsoft.com/en-us/typography/opentype/spec/loca
#[derive(Clone)]
pub enum Loca<'a> {
Short(&'a [BigEndian<u16>]),
Long(&'a [BigEndian<u32>]),
}
impl TopLevelTable for Loca<'_> {
const TAG: Tag = Tag::new(b"loca");
}
impl<'a> Loca<'a> {
pub fn read(data: FontData<'a>, is_long: bool) -> Result<Self, crate::ReadError> {
Self::read_with_args(data, &is_long)
}
pub fn len(&self) -> usize {
match self {
Loca::Short(data) => data.len().saturating_sub(1),
Loca::Long(data) => data.len().saturating_sub(1),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn all_offsets_are_ascending(&self) -> bool {
match self {
Loca::Short(data) => !data
.iter()
.zip(data.iter().skip(1))
.any(|(start, end)| start > end),
Loca::Long(data) => !data
.iter()
.zip(data.iter().skip(1))
.any(|(start, end)| start > end),
}
}
/// Attempt to return the offset for a given glyph id.
pub fn get_raw(&self, idx: usize) -> Option<u32> {
match self {
Loca::Short(data) => data.get(idx).map(|x| x.get() as u32 * 2),
Loca::Long(data) => data.get(idx).map(|x| x.get()),
}
}
pub fn get_glyf(
&self,
gid: GlyphId,
glyf: &super::glyf::Glyf<'a>,
) -> Result<Option<super::glyf::Glyph<'a>>, ReadError> {
let idx = gid.to_u32() as usize;
let start = self.get_raw(idx).ok_or(ReadError::OutOfBounds)?;
let end = self.get_raw(idx + 1).ok_or(ReadError::OutOfBounds)?;
if start == end {
return Ok(None);
}
let data = glyf
.offset_data()
.slice(start as usize..end as usize)
.ok_or(ReadError::OutOfBounds)?;
match super::glyf::Glyph::read(data) {
Ok(glyph) => Ok(Some(glyph)),
Err(e) => Err(e),
}
}
}
impl ReadArgs for Loca<'_> {
type Args = bool;
}
impl<'a> FontReadWithArgs<'a> for Loca<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, crate::ReadError> {
let is_long = *args;
if is_long {
data.read_array(0..data.len()).map(Loca::Long)
} else {
data.read_array(0..data.len()).map(Loca::Short)
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> traversal::SomeTable<'a> for Loca<'a> {
fn type_name(&self) -> &str {
"loca"
}
fn get_field(&self, idx: usize) -> Option<traversal::Field<'a>> {
match idx {
0usize => Some(traversal::Field::new("offsets", self.clone())),
_ => None,
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> traversal::SomeArray<'a> for Loca<'a> {
fn len(&self) -> usize {
self.len()
}
fn get(&self, idx: usize) -> Option<traversal::FieldType<'a>> {
self.get_raw(idx).map(|off| off.into())
}
fn type_name(&self) -> &str {
"Offset32"
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> std::fmt::Debug for Loca<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(self as &dyn traversal::SomeTable<'a>).fmt(f)
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use types::Scalar;
use super::Loca;
fn to_loca_bytes<T: Scalar + Copy>(values: &[T]) -> (BeBuffer, bool) {
let value_num_bytes = std::mem::size_of::<T>();
let is_long = if value_num_bytes == 2 {
false
} else if value_num_bytes == 4 {
true
} else {
panic!("invalid integer type must be u32 or u16")
};
let mut buffer = BeBuffer::default();
for v in values {
buffer = buffer.push(*v);
}
(buffer, is_long)
}
fn check_loca_sorting(values: &[u16], is_sorted: bool) {
let (bytes, is_long) = to_loca_bytes(values);
let loca = Loca::read(bytes.data().into(), is_long).unwrap();
assert_eq!(loca.all_offsets_are_ascending(), is_sorted);
let u32_values: Vec<u32> = values.iter().map(|v| *v as u32).collect();
let (bytes, is_long) = to_loca_bytes(&u32_values);
let loca = Loca::read(bytes.data().into(), is_long).unwrap();
assert_eq!(loca.all_offsets_are_ascending(), is_sorted);
}
#[test]
fn all_offsets_are_ascending() {
// Sorted
let empty: &[u16] = &[];
check_loca_sorting(empty, true);
check_loca_sorting(&[0], true);
check_loca_sorting(&[0, 0], true);
check_loca_sorting(&[0, 1], true);
check_loca_sorting(&[1, 2, 2, 3, 7], true);
// Unsorted
check_loca_sorting(&[1, 0], false);
check_loca_sorting(&[1, 3, 2], false);
check_loca_sorting(&[2, 1, 3], false);
check_loca_sorting(&[1, 2, 3, 2, 7], false);
}
}

55
vendor/read-fonts/src/tables/ltag.rs vendored Normal file
View File

@@ -0,0 +1,55 @@
//! The [language tag](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ltag.html) table.
include!("../../generated/generated_ltag.rs");
impl<'a> Ltag<'a> {
/// Returns an iterator yielding the index and string value of each
/// tag in the table.
pub fn tag_indices(&self) -> impl Iterator<Item = (u32, &'a str)> {
let table_data = self.offset_data().as_bytes();
self.tag_ranges()
.iter()
.enumerate()
.filter_map(move |(index, range)| {
let start = range.offset() as usize;
// These are u16 so can't overflow even in 32-bit
let range = start..start + range.length() as usize;
let string_bytes = table_data.get(range)?;
let s = core::str::from_utf8(string_bytes).ok()?;
Some((index as u32, s))
})
}
/// Returns the index of the given language tag.
pub fn index_for_tag(&self, tag: &str) -> Option<u32> {
self.tag_indices().find(|x| x.1 == tag).map(|x| x.0)
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn tags() {
// Second sample at <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ltag.html>
let mut buf = BeBuffer::new();
// header
buf = buf.extend([1u32, 0, 3]);
// tag records
buf = buf.extend([24u16, 2, 26, 2, 28, 2]);
// string data
buf = buf.extend("enspsr".as_bytes().iter().copied());
let expected_tags = [(0, "en"), (1, "sp"), (2, "sr")];
let ltag = Ltag::read(buf.data().into()).unwrap();
let tags = ltag.tag_indices().collect::<Vec<_>>();
assert_eq!(tags, expected_tags);
assert_eq!(ltag.index_for_tag("en"), Some(0));
assert_eq!(ltag.index_for_tag("sp"), Some(1));
assert_eq!(ltag.index_for_tag("sr"), Some(2));
assert_eq!(ltag.index_for_tag("ar"), None);
assert_eq!(ltag.index_for_tag("hi"), None);
}
}

3
vendor/read-fonts/src/tables/maxp.rs vendored Normal file
View File

@@ -0,0 +1,3 @@
//! The [maxp](https://docs.microsoft.com/en-us/typography/opentype/spec/maxp) table
include!("../../generated/generated_maxp.rs");

116
vendor/read-fonts/src/tables/meta.rs vendored Normal file
View File

@@ -0,0 +1,116 @@
//! The [meta (Metadata)](https://docs.microsoft.com/en-us/typography/opentype/spec/meta) table
include!("../../generated/generated_meta.rs");
pub const DLNG: Tag = Tag::new(b"dlng");
pub const SLNG: Tag = Tag::new(b"slng");
/// Data stored in the 'meta' table.
pub enum Metadata<'a> {
/// Used for the 'dlng' and 'slng' metadata
ScriptLangTags(VarLenArray<'a, ScriptLangTag<'a>>),
/// Other metadata, which may exist in certain apple fonts
Other(&'a [u8]),
}
impl ReadArgs for Metadata<'_> {
type Args = (Tag, u32);
}
impl<'a> FontReadWithArgs<'a> for Metadata<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
let (tag, len) = *args;
let data = data.slice(0..len as usize).ok_or(ReadError::OutOfBounds)?;
if [DLNG, SLNG].contains(&tag) {
VarLenArray::read(data).map(Metadata::ScriptLangTags)
} else {
Ok(Metadata::Other(data.as_bytes()))
}
}
}
#[derive(Clone, Debug)]
pub struct ScriptLangTag<'a>(&'a str);
impl<'a> ScriptLangTag<'a> {
pub fn as_str(&self) -> &'a str {
self.0
}
}
impl AsRef<str> for ScriptLangTag<'_> {
fn as_ref(&self) -> &str {
self.0
}
}
#[cfg(feature = "std")]
impl From<ScriptLangTag<'_>> for String {
fn from(value: ScriptLangTag<'_>) -> Self {
value.0.into()
}
}
impl VarSize for ScriptLangTag<'_> {
type Size = u32;
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
let bytes = data.split_off(pos)?.as_bytes();
if bytes.is_empty() {
return None;
}
let end = data
.as_bytes()
.iter()
.position(|b| *b == b',')
.map(|pos| pos + 1) // include comma
.unwrap_or(bytes.len());
Some(end)
}
}
impl<'a> FontRead<'a> for ScriptLangTag<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
std::str::from_utf8(data.as_bytes())
.map_err(|_| ReadError::MalformedData("LangScriptTag must be utf8"))
.map(|s| ScriptLangTag(s.trim_matches(|c| c == ' ' || c == ',')))
}
}
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::meta as test_data;
impl PartialEq<&str> for ScriptLangTag<'_> {
fn eq(&self, other: &&str) -> bool {
self.as_ref() == *other
}
}
fn expect_script_lang_tags(table: Metadata, expected: &[&str]) -> bool {
let Metadata::ScriptLangTags(langs) = table else {
panic!("wrong metadata");
};
let result = langs.iter().map(|x| x.unwrap()).collect::<Vec<_>>();
result == expected
}
#[test]
fn parse_simple() {
let table = Meta::read(test_data::SIMPLE_META_TABLE.into()).unwrap();
let rec1 = table.data_maps()[0];
let rec2 = table.data_maps()[1];
assert_eq!(rec1.tag(), Tag::new(b"dlng"));
assert_eq!(rec2.tag(), Tag::new(b"slng"));
assert!(expect_script_lang_tags(
rec1.data(table.offset_data()).unwrap(),
&["en-latn", "latn"]
));
assert!(expect_script_lang_tags(
rec2.data(table.offset_data()).unwrap(),
&["latn"]
));
}
}

329
vendor/read-fonts/src/tables/morx.rs vendored Normal file
View File

@@ -0,0 +1,329 @@
//! The [morx (Extended Glyph Metamorphosis)](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html) table.
use super::aat::{safe_read_array_to_end, ExtendedStateTable, LookupU16};
include!("../../generated/generated_morx.rs");
impl VarSize for Chain<'_> {
type Size = u32;
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
// Size in a chain is second field beyond 4 byte `defaultFlags`
data.read_at::<u32>(pos.checked_add(u32::RAW_BYTE_LEN)?)
.ok()
.map(|size| size as usize)
}
}
impl VarSize for Subtable<'_> {
type Size = u32;
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
// The default implementation assumes that the length field itself
// is not included in the total size which is not true of this
// table.
data.read_at::<u32>(pos).ok().map(|size| size as usize)
}
}
impl<'a> Subtable<'a> {
/// If true, this subtable will process glyphs in logical order (or reverse
/// logical order, depending on the value of bit 0x80000000).
#[inline]
pub fn is_logical(&self) -> bool {
self.coverage() & 0x10000000 != 0
}
/// If true, this subtable will be applied to both horizontal and vertical
/// text (i.e. the state of bit 0x80000000 is ignored).
#[inline]
pub fn is_all_directions(&self) -> bool {
self.coverage() & 0x20000000 != 0
}
/// If true, this subtable will process glyphs in descending order.
/// Otherwise, it will process the glyphs in ascending order.
#[inline]
pub fn is_backwards(&self) -> bool {
self.coverage() & 0x40000000 != 0
}
/// If true, this subtable will only be applied to vertical text.
/// Otherwise, this subtable will only be applied to horizontal
/// text.
#[inline]
pub fn is_vertical(&self) -> bool {
self.coverage() & 0x80000000 != 0
}
/// Returns an enum representing the actual subtable data.
pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
SubtableKind::read_with_args(FontData::new(self.data()), &self.coverage())
}
}
/// The various `morx` subtable formats.
#[derive(Clone)]
pub enum SubtableKind<'a> {
Rearrangement(ExtendedStateTable<'a>),
Contextual(ContextualSubtable<'a>),
Ligature(LigatureSubtable<'a>),
NonContextual(LookupU16<'a>),
Insertion(InsertionSubtable<'a>),
}
impl ReadArgs for SubtableKind<'_> {
type Args = u32;
}
impl<'a> FontReadWithArgs<'a> for SubtableKind<'a> {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
// Format is low byte of coverage
let format = *args & 0xFF;
match format {
0 => Ok(Self::Rearrangement(ExtendedStateTable::read(data)?)),
1 => Ok(Self::Contextual(ContextualSubtable::read(data)?)),
2 => Ok(Self::Ligature(LigatureSubtable::read(data)?)),
// 3 is reserved
4 => Ok(Self::NonContextual(LookupU16::read(data)?)),
5 => Ok(Self::Insertion(InsertionSubtable::read(data)?)),
_ => Err(ReadError::InvalidFormat(format as _)),
}
}
}
/// Contextual glyph substitution subtable.
#[derive(Clone)]
pub struct ContextualSubtable<'a> {
pub state_table: ExtendedStateTable<'a, ContextualEntryData>,
/// List of lookups specifying substitutions. The index into this array
/// is specified by the action in the state table.
pub lookups: ArrayOfOffsets<'a, LookupU16<'a>, Offset32>,
}
impl<'a> FontRead<'a> for ContextualSubtable<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let state_table = ExtendedStateTable::read(data)?;
let mut cursor = data.cursor();
cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
let offset = cursor.read::<u32>()? as usize;
let end = data.len();
let offsets_data = FontData::new(data.read_array(offset..end)?);
let raw_offsets: &[BigEndian<Offset32>] = safe_read_array_to_end(&offsets_data, 0)?;
let lookups = ArrayOfOffsets::new(raw_offsets, offsets_data, ());
Ok(Self {
state_table,
lookups,
})
}
}
/// Ligature glyph substitution subtable.
#[derive(Clone)]
pub struct LigatureSubtable<'a> {
pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
/// Contains the set of ligature stack actions, one for each state.
pub ligature_actions: &'a [BigEndian<u32>],
/// Array of component indices which are summed to determine the index
/// of the final ligature glyph.
pub components: &'a [BigEndian<u16>],
/// Output ligature glyphs.
pub ligatures: &'a [BigEndian<GlyphId16>],
}
impl<'a> FontRead<'a> for LigatureSubtable<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let state_table = ExtendedStateTable::read(data)?;
let mut cursor = data.cursor();
cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
// None of these arrays have associated sizes, so we just read until
// the end of the data.
let lig_action_offset = cursor.read::<u32>()? as usize;
let component_offset = cursor.read::<u32>()? as usize;
let ligature_offset = cursor.read::<u32>()? as usize;
let ligature_actions = safe_read_array_to_end(&data, lig_action_offset)?;
let components = safe_read_array_to_end(&data, component_offset)?;
let ligatures = safe_read_array_to_end(&data, ligature_offset)?;
Ok(Self {
state_table,
ligature_actions,
components,
ligatures,
})
}
}
/// Insertion glyph substitution subtable.
#[derive(Clone)]
pub struct InsertionSubtable<'a> {
pub state_table: ExtendedStateTable<'a, InsertionEntryData>,
/// Insertion glyph table. The index and count of glyphs to insert is
/// determined by the state machine.
pub glyphs: &'a [BigEndian<GlyphId16>],
}
impl<'a> FontRead<'a> for InsertionSubtable<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let state_table = ExtendedStateTable::read(data)?;
let mut cursor = data.cursor();
cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
let glyphs_offset = cursor.read::<u32>()? as usize;
let glyphs = safe_read_array_to_end(&data, glyphs_offset)?;
Ok(Self {
state_table,
glyphs,
})
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeRecord<'a> for Chain<'a> {
fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
RecordResolver {
name: "Chain",
get_field: Box::new(move |idx, _data| match idx {
0usize => Some(Field::new("default_flags", self.default_flags())),
_ => None,
}),
data,
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeRecord<'a> for Subtable<'a> {
fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
RecordResolver {
name: "Subtable",
get_field: Box::new(move |idx, _data| match idx {
0usize => Some(Field::new("coverage", self.coverage())),
1usize => Some(Field::new("sub_feature_flags", self.sub_feature_flags())),
_ => None,
}),
data,
}
}
}
#[cfg(test)]
// Literal bytes are grouped according to layout in the spec
// for readabiity
#[allow(clippy::unusual_byte_groupings)]
mod tests {
use super::*;
use crate::{FontRef, TableProvider};
#[test]
fn parse_chain_flags_features() {
let font = FontRef::new(font_test_data::morx::FOUR).unwrap();
let morx = font.morx().unwrap();
let chain = morx.chains().iter().next().unwrap().unwrap();
assert_eq!(chain.default_flags(), 1);
let feature = chain.features()[0];
assert_eq!(feature.feature_type(), 4);
assert_eq!(feature.feature_settings(), 0);
assert_eq!(feature.enable_flags(), 1);
assert_eq!(feature.disable_flags(), 0xFFFFFFFF);
}
#[test]
fn parse_rearrangement() {
let font = FontRef::new(font_test_data::morx::FOUR).unwrap();
let morx = font.morx().unwrap();
let chain = morx.chains().iter().next().unwrap().unwrap();
let subtable = chain.subtables().iter().next().unwrap().unwrap();
assert_eq!(subtable.coverage(), 0x20_0000_00);
// Rearrangement is just a state table
let SubtableKind::Rearrangement(_kind) = subtable.kind().unwrap() else {
panic!("expected rearrangement subtable!");
};
}
#[test]
fn parse_contextual() {
let font = FontRef::new(font_test_data::morx::EIGHTEEN).unwrap();
let morx = font.morx().unwrap();
let chain = morx.chains().iter().next().unwrap().unwrap();
let subtable = chain.subtables().iter().next().unwrap().unwrap();
assert_eq!(subtable.coverage(), 0x20_0000_01);
let SubtableKind::Contextual(kind) = subtable.kind().unwrap() else {
panic!("expected contextual subtable!");
};
let lookup = kind.lookups.get(0).unwrap();
let expected = [None, None, Some(7u16), Some(8), Some(9), Some(10), Some(11)];
let values = (0..7).map(|gid| lookup.value(gid).ok()).collect::<Vec<_>>();
assert_eq!(values, &expected);
}
#[test]
fn parse_ligature() {
let font = FontRef::new(font_test_data::morx::FORTY_ONE).unwrap();
let morx = font.morx().unwrap();
let chain = morx.chains().iter().next().unwrap().unwrap();
let subtable = chain.subtables().iter().next().unwrap().unwrap();
assert_eq!(subtable.coverage(), 0x20_0000_02);
let SubtableKind::Ligature(kind) = subtable.kind().unwrap() else {
panic!("expected ligature subtable!");
};
let expected_actions = [0x3FFFFFFE, 0xBFFFFFFE];
// Note, we limit the number of elements because the arrays do not
// have specified lengths in the table
let actions = kind
.ligature_actions
.iter()
.take(2)
.map(|action| action.get())
.collect::<Vec<_>>();
assert_eq!(actions, &expected_actions);
let expected_components = [0u16, 1, 0, 0];
// See above explanation for the limit
let components = kind
.components
.iter()
.take(4)
.map(|comp| comp.get())
.collect::<Vec<_>>();
assert_eq!(components, &expected_components);
let expected_ligatures = [GlyphId16::new(5), GlyphId16::new(6)];
let ligatures = kind
.ligatures
.iter()
.map(|gid| gid.get())
.collect::<Vec<_>>();
assert_eq!(ligatures, &expected_ligatures);
}
#[test]
fn parse_non_contextual() {
let font = FontRef::new(font_test_data::morx::ONE).unwrap();
let morx = font.morx().unwrap();
let chain = morx.chains().iter().next().unwrap().unwrap();
let subtable = chain.subtables().iter().next().unwrap().unwrap();
assert_eq!(subtable.coverage(), 0x20_0000_04);
let SubtableKind::NonContextual(kind) = subtable.kind().unwrap() else {
panic!("expected non-contextual subtable!");
};
let expected_values = [None, None, Some(5u16), None, Some(7)];
let values = (0..5).map(|gid| kind.value(gid).ok()).collect::<Vec<_>>();
assert_eq!(values, &expected_values);
}
#[test]
fn parse_insertion() {
let font = FontRef::new(font_test_data::morx::THIRTY_FOUR).unwrap();
let morx = font.morx().unwrap();
let chain = morx.chains().iter().next().unwrap().unwrap();
let subtable = chain.subtables().iter().next().unwrap().unwrap();
assert_eq!(subtable.coverage(), 0x20_0000_05);
let SubtableKind::Insertion(kind) = subtable.kind().unwrap() else {
panic!("expected insertion subtable!");
};
let mut expected_glyphs = vec![];
for _ in 0..9 {
for gid in [3, 2] {
expected_glyphs.push(GlyphId16::new(gid));
}
}
assert_eq!(kind.glyphs, &expected_glyphs);
}
}

132
vendor/read-fonts/src/tables/mvar.rs vendored Normal file
View File

@@ -0,0 +1,132 @@
//! The [MVAR (Metrics Variation)](https://docs.microsoft.com/en-us/typography/opentype/spec/mvar) table
use super::variations::{DeltaSetIndex, ItemVariationStore};
/// Four-byte tags used to represent particular metric or other values.
pub mod tags {
use font_types::Tag;
/// Horizontal ascender.
pub const HASC: Tag = Tag::new(b"hasc");
/// Horizontal descender.
pub const HDSC: Tag = Tag::new(b"hdsc");
/// Horizontal line gap.
pub const HLGP: Tag = Tag::new(b"hlgp");
/// Horizontal clipping ascent.
pub const HCLA: Tag = Tag::new(b"hcla");
/// Horizontal clipping descent.
pub const HCLD: Tag = Tag::new(b"hcld");
/// Vertical ascender.
pub const VASC: Tag = Tag::new(b"vasc");
/// Vertical descender.
pub const VDSC: Tag = Tag::new(b"vdsc");
/// Vertical line gap.
pub const VLGP: Tag = Tag::new(b"vlgp");
/// Horizontal caret rise.
pub const HCRS: Tag = Tag::new(b"hcrs");
/// Horizontal caret run.
pub const HCRN: Tag = Tag::new(b"hcrn");
/// Horizontal caret offset.
pub const HCOF: Tag = Tag::new(b"hcof");
/// Vertical caret rise.
pub const VCRS: Tag = Tag::new(b"vcrs");
/// Vertical caret run.
pub const VCRN: Tag = Tag::new(b"vcrn");
/// Vertical caret offset.
pub const VCOF: Tag = Tag::new(b"vcof");
/// X-height.
pub const XHGT: Tag = Tag::new(b"xhgt");
/// Cap height.
pub const CPHT: Tag = Tag::new(b"cpht");
/// Subscript em x-offset.
pub const SBXO: Tag = Tag::new(b"sbxo");
/// Subscript em y-offset.
pub const SBYO: Tag = Tag::new(b"sbyo");
/// Subscript em x-size.
pub const SBXS: Tag = Tag::new(b"sbxs");
/// Subscript em y-size.
pub const SBYS: Tag = Tag::new(b"sbys");
/// Superscript em x-offset.
pub const SPXO: Tag = Tag::new(b"spxo");
/// Superscript em y-offset.
pub const SPYO: Tag = Tag::new(b"spyo");
/// Superscript em x-size.
pub const SPXS: Tag = Tag::new(b"spxs");
/// Superscript em y-size.
pub const SPYS: Tag = Tag::new(b"spys");
/// Strikeout size.
pub const STRS: Tag = Tag::new(b"strs");
/// Strikeout offset.
pub const STRO: Tag = Tag::new(b"stro");
/// Underline size.
pub const UNDS: Tag = Tag::new(b"unds");
/// Underline offset.
pub const UNDO: Tag = Tag::new(b"undo");
/// GaspRange\[0\]
pub const GSP0: Tag = Tag::new(b"gsp0");
/// GaspRange\[1\]
pub const GSP1: Tag = Tag::new(b"gsp1");
/// GaspRange\[2\]
pub const GSP2: Tag = Tag::new(b"gsp2");
/// GaspRange\[3\]
pub const GSP3: Tag = Tag::new(b"gsp3");
/// GaspRange\[4\]
pub const GSP4: Tag = Tag::new(b"gsp4");
/// GaspRange\[5\]
pub const GSP5: Tag = Tag::new(b"gsp5");
/// GaspRange\[6\]
pub const GSP6: Tag = Tag::new(b"gsp6");
/// GaspRange\[7\]
pub const GSP7: Tag = Tag::new(b"gsp7");
/// GaspRange\[8\]
pub const GSP8: Tag = Tag::new(b"gsp8");
/// GaspRange\[9\]
pub const GSP9: Tag = Tag::new(b"gsp9");
}
include!("../../generated/generated_mvar.rs");
impl Mvar<'_> {
/// Returns the metric delta for the specified tag and normalized
/// variation coordinates. Possible tags are found in the [tags]
/// module.
pub fn metric_delta(&self, tag: Tag, coords: &[F2Dot14]) -> Result<Fixed, ReadError> {
use std::cmp::Ordering;
let records = self.value_records();
let mut lo = 0;
let mut hi = records.len();
while lo < hi {
let i = (lo + hi) / 2;
let record = &records[i];
match tag.cmp(&record.value_tag()) {
Ordering::Less => {
hi = i;
}
Ordering::Greater => {
lo = i + 1;
}
Ordering::Equal => {
let ivs = self.item_variation_store().ok_or(ReadError::NullOffset)??;
return Ok(Fixed::from_i32(ivs.compute_delta(
DeltaSetIndex {
outer: record.delta_set_outer_index(),
inner: record.delta_set_inner_index(),
},
coords,
)?));
}
}
}
Err(ReadError::MetricIsMissing(tag))
}
}

309
vendor/read-fonts/src/tables/name.rs vendored Normal file
View File

@@ -0,0 +1,309 @@
//! The [name (Naming)](https://docs.microsoft.com/en-us/typography/opentype/spec/name) table
include!("../../generated/generated_name.rs");
pub use types::NameId;
impl<'a> Name<'a> {
/// The FontData containing the encoded name strings.
pub fn string_data(&self) -> FontData<'a> {
let base = self.offset_data();
let off = self.storage_offset();
base.split_off(off as usize).unwrap_or_default()
}
}
impl NameRecord {
/// Return a type that can decode the string data for this name entry.
pub fn string<'a>(&self, data: FontData<'a>) -> Result<NameString<'a>, ReadError> {
let start = self.string_offset().non_null().unwrap_or(0);
let end = start + self.length() as usize;
let data = data
.as_bytes()
.get(start..end)
.ok_or(ReadError::OutOfBounds)?;
let encoding = Encoding::new(self.platform_id(), self.encoding_id());
Ok(NameString { data, encoding })
}
// reference from fonttools:
// https://github.com/fonttools/fonttools/blob/c2119229cfb02cdb7c5a63374ef29d3d514259e8/Lib/fontTools/ttLib/tables/_n_a_m_e.py#L509
pub fn is_unicode(&self) -> bool {
self.platform_id() == 0
|| (self.platform_id() == 3 && [0, 1, 10].contains(&self.encoding_id()))
}
}
impl LangTagRecord {
/// Return a type that can decode the string data for this name entry.
pub fn lang_tag<'a>(&self, data: FontData<'a>) -> Result<NameString<'a>, ReadError> {
let start = self.lang_tag_offset().non_null().unwrap_or(0);
let end = start + self.length() as usize;
let data = data
.as_bytes()
.get(start..end)
.ok_or(ReadError::OutOfBounds)?;
let encoding = Encoding::Utf16Be;
Ok(NameString { data, encoding })
}
}
//-- all this is from pinot https://github.com/dfrg/pinot/blob/eff5239018ca50290fb890a84da3dd51505da364/src/name.rs
/// Entry for a name in the naming table.
///
/// This provides an iterator over characters.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct NameString<'a> {
data: &'a [u8],
encoding: Encoding,
}
impl<'a> NameString<'a> {
/// An iterator over the `char`s in this name.
pub fn chars(&self) -> CharIter<'a> {
CharIter {
data: self.data,
encoding: self.encoding,
pos: 0,
}
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> traversal::SomeString<'a> for NameString<'a> {
fn iter_chars(&self) -> Box<dyn Iterator<Item = char> + 'a> {
Box::new(self.into_iter())
}
}
#[cfg(feature = "experimental_traverse")]
impl NameRecord {
fn traverse_string<'a>(&self, data: FontData<'a>) -> traversal::FieldType<'a> {
FieldType::StringOffset(traversal::StringOffset {
offset: self.string_offset().into(),
target: self.string(data).map(|s| Box::new(s) as _),
})
}
}
#[cfg(feature = "experimental_traverse")]
impl LangTagRecord {
fn traverse_lang_tag<'a>(&self, data: FontData<'a>) -> traversal::FieldType<'a> {
FieldType::StringOffset(traversal::StringOffset {
offset: self.lang_tag_offset().into(),
target: self.lang_tag(data).map(|s| Box::new(s) as _),
})
}
}
impl<'a> IntoIterator for NameString<'a> {
type Item = char;
type IntoIter = CharIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.chars()
}
}
impl std::fmt::Display for NameString<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
for c in self.chars() {
c.fmt(f)?;
}
Ok(())
}
}
impl std::fmt::Debug for NameString<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "\"{self}\"")
}
}
/// An iterator over the chars of a name record.
#[derive(Clone)]
pub struct CharIter<'a> {
data: &'a [u8],
encoding: Encoding,
pos: usize,
}
impl CharIter<'_> {
fn bump_u16(&mut self) -> Option<u16> {
let result = self
.data
.get(self.pos..self.pos + 2)
.map(|x| u16::from_be_bytes(x.try_into().unwrap()))?;
self.pos += 2;
Some(result)
}
fn bump_u8(&mut self) -> Option<u8> {
let result = self.data.get(self.pos)?;
self.pos += 1;
Some(*result)
}
}
impl Iterator for CharIter<'_> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.data.len() {
return None;
}
let rep = core::char::REPLACEMENT_CHARACTER;
let raw_c = match self.encoding {
Encoding::Utf16Be => {
let c1 = self.bump_u16()? as u32;
if (0xD800..0xDC00).contains(&c1) {
let Some(c2) = self.bump_u16() else {
return Some(rep);
};
((c1 & 0x3FF) << 10) + (c2 as u32 & 0x3FF) + 0x10000
} else {
c1
}
}
Encoding::MacRoman => {
let c = self.bump_u8()?;
MacRomanMapping.decode(c) as u32
}
_ => return None,
};
Some(std::char::from_u32(raw_c).unwrap_or(rep))
}
}
/// The encoding used by the name table.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Encoding {
Utf16Be,
MacRoman,
Unknown,
}
impl Encoding {
/// Determine the coding from the platform and encoding id.
pub fn new(platform_id: u16, encoding_id: u16) -> Encoding {
match (platform_id, encoding_id) {
(0, _) => Encoding::Utf16Be,
(1, 0) => Encoding::MacRoman,
(3, 0) => Encoding::Utf16Be,
(3, 1) => Encoding::Utf16Be,
(3, 10) => Encoding::Utf16Be,
_ => Encoding::Unknown,
}
}
}
/// A helper for encoding and decoding Mac OS Roman encoded strings.
pub struct MacRomanMapping;
impl MacRomanMapping {
const START_REMAP: u8 = 128;
/// Convert from a mac-roman encoded byte to a `char`
pub fn decode(self, raw: u8) -> char {
if raw < Self::START_REMAP {
raw as char
} else {
let idx = raw - Self::START_REMAP;
char::from_u32(MAC_ROMAN_DECODE[idx as usize] as u32).unwrap()
}
}
/// convert from a char to a mac-roman encoded byte, if the char is in the mac-roman charset.
pub fn encode(self, c: char) -> Option<u8> {
let raw_c = c as u32;
let raw_c: u16 = raw_c.try_into().ok()?;
if raw_c < Self::START_REMAP as u16 {
Some(raw_c as u8)
} else {
match MAC_ROMAN_ENCODE.binary_search_by_key(&raw_c, |(unic, _)| *unic) {
Ok(idx) => Some(MAC_ROMAN_ENCODE[idx].1),
Err(_) => None,
}
}
}
}
/// A lookup table for the Mac Roman encoding. This matches the values `128..=255`
/// to specific Unicode values.
#[rustfmt::skip]
static MAC_ROMAN_DECODE: [u16; 128] = [
196, 197, 199, 201, 209, 214, 220, 225, 224, 226, 228, 227, 229, 231, 233,
232, 234, 235, 237, 236, 238, 239, 241, 243, 242, 244, 246, 245, 250, 249,
251, 252, 8224, 176, 162, 163, 167, 8226, 182, 223, 174, 169, 8482, 180,
168, 8800, 198, 216, 8734, 177, 8804, 8805, 165, 181, 8706, 8721, 8719,
960, 8747, 170, 186, 937, 230, 248, 191, 161, 172, 8730, 402, 8776, 8710,
171, 187, 8230, 160, 192, 195, 213, 338, 339, 8211, 8212, 8220, 8221, 8216,
8217, 247, 9674, 255, 376, 8260, 8364, 8249, 8250, 64257, 64258, 8225, 183,
8218, 8222, 8240, 194, 202, 193, 203, 200, 205, 206, 207, 204, 211, 212,
63743, 210, 218, 219, 217, 305, 710, 732, 175, 728, 729, 730, 184, 733,
731, 711,
];
/// A lookup pairing (sorted) Unicode values to Mac Roman values
#[rustfmt::skip]
static MAC_ROMAN_ENCODE: [(u16, u8); 128] = [
(160, 202), (161, 193), (162, 162), (163, 163),
(165, 180), (167, 164), (168, 172), (169, 169),
(170, 187), (171, 199), (172, 194), (174, 168),
(175, 248), (176, 161), (177, 177), (180, 171),
(181, 181), (182, 166), (183, 225), (184, 252),
(186, 188), (187, 200), (191, 192), (192, 203),
(193, 231), (194, 229), (195, 204), (196, 128),
(197, 129), (198, 174), (199, 130), (200, 233),
(201, 131), (202, 230), (203, 232), (204, 237),
(205, 234), (206, 235), (207, 236), (209, 132),
(210, 241), (211, 238), (212, 239), (213, 205),
(214, 133), (216, 175), (217, 244), (218, 242),
(219, 243), (220, 134), (223, 167), (224, 136),
(225, 135), (226, 137), (227, 139), (228, 138),
(229, 140), (230, 190), (231, 141), (232, 143),
(233, 142), (234, 144), (235, 145), (236, 147),
(237, 146), (238, 148), (239, 149), (241, 150),
(242, 152), (243, 151), (244, 153), (245, 155),
(246, 154), (247, 214), (248, 191), (249, 157),
(250, 156), (251, 158), (252, 159), (255, 216),
(305, 245), (338, 206), (339, 207), (376, 217),
(402, 196), (710, 246), (711, 255), (728, 249),
(729, 250), (730, 251), (731, 254), (732, 247),
(733, 253), (937, 189), (960, 185), (8211, 208),
(8212, 209), (8216, 212), (8217, 213), (8218, 226),
(8220, 210), (8221, 211), (8222, 227), (8224, 160),
(8225, 224), (8226, 165), (8230, 201), (8240, 228),
(8249, 220), (8250, 221), (8260, 218), (8364, 219),
(8482, 170), (8706, 182), (8710, 198), (8719, 184),
(8721, 183), (8730, 195), (8734, 176), (8747, 186),
(8776, 197), (8800, 173), (8804, 178), (8805, 179),
(9674, 215), (63743, 240), (64257, 222), (64258, 223),
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mac_roman() {
static INPUT: &str = "Joachim Müller-Lancé";
for c in INPUT.chars() {
let enc = MacRomanMapping.encode(c).unwrap();
assert_eq!(MacRomanMapping.decode(enc), c);
}
}
#[test]
fn lone_surrogate_at_end() {
let chars = CharIter {
// DEVANAGARI LETTER SHORT A (U+0904), unpaired high surrogate (0xD800)
data: &[0x09, 0x04, 0xD8, 0x00],
encoding: Encoding::Utf16Be,
pos: 0,
};
assert!(chars.eq(['ऄ', std::char::REPLACEMENT_CHARACTER].into_iter()))
}
}

190
vendor/read-fonts/src/tables/os2.rs vendored Normal file
View File

@@ -0,0 +1,190 @@
//! The [os2](https://docs.microsoft.com/en-us/typography/opentype/spec/os2) table
include!("../../generated/generated_os2.rs");
// unicode range array generated from the ot spec:
// (https://docs.microsoft.com/en-us/typography/opentype/spec/os2#ur).
// sorted by range start codepoints
pub static OS2_UNICODE_RANGES: [(u32, u32, u8); 169] = [
(0x0, 0x7F, 0), // Basic Latin
(0x80, 0xFF, 1), // Latin-1 Supplement
(0x100, 0x17F, 2), // Latin Extended-A
(0x180, 0x24F, 3), // Latin Extended-B
(0x250, 0x2AF, 4), // IPA Extensions
(0x2B0, 0x2FF, 5), // Spacing Modifier Letters
(0x300, 0x36F, 6), // Combining Diacritical Marks
(0x370, 0x3FF, 7), // Greek and Coptic
(0x400, 0x4FF, 9), // Cyrillic
(0x500, 0x52F, 9), // Cyrillic Supplement
(0x530, 0x58F, 10), // Armenian
(0x590, 0x5FF, 11), // Hebrew
(0x600, 0x6FF, 13), // Arabic
(0x700, 0x74F, 71), // Syriac
(0x750, 0x77F, 13), // Arabic Supplement
(0x780, 0x7BF, 72), // Thaana
(0x7C0, 0x7FF, 14), // NKo
(0x900, 0x97F, 15), // Devanagari
(0x980, 0x9FF, 16), // Bengali
(0xA00, 0xA7F, 17), // Gurmukhi
(0xA80, 0xAFF, 18), // Gujarati
(0xB00, 0xB7F, 19), // Oriya
(0xB80, 0xBFF, 20), // Tamil
(0xC00, 0xC7F, 21), // Telugu
(0xC80, 0xCFF, 22), // Kannada
(0xD00, 0xD7F, 23), // Malayalam
(0xD80, 0xDFF, 73), // Sinhala
(0xE00, 0xE7F, 24), // Thai
(0xE80, 0xEFF, 25), // Lao
(0xF00, 0xFFF, 70), // Tibetan
(0x1000, 0x109F, 74), // Myanmar
(0x10A0, 0x10FF, 26), // Georgian
(0x1100, 0x11FF, 28), // Hangul Jamo
(0x1200, 0x137F, 75), // Ethiopic
(0x1380, 0x139F, 75), // Ethiopic Supplement
(0x13A0, 0x13FF, 76), // Cherokee
(0x1400, 0x167F, 77), // Unified Canadian Aboriginal Syllabics
(0x1680, 0x169F, 78), // Ogham
(0x16A0, 0x16FF, 79), // Runic
(0x1700, 0x171F, 84), // Tagalog
(0x1720, 0x173F, 84), // Hanunoo
(0x1740, 0x175F, 84), // Buhid
(0x1760, 0x177F, 84), // Tagbanwa
(0x1780, 0x17FF, 80), // Khmer
(0x1800, 0x18AF, 81), // Mongolian
(0x1900, 0x194F, 93), // Limbu
(0x1950, 0x197F, 94), // Tai Le
(0x1980, 0x19DF, 95), // New Tai Lue
(0x19E0, 0x19FF, 80), // Khmer Symbols
(0x1A00, 0x1A1F, 96), // Buginese
(0x1B00, 0x1B7F, 27), // Balinese
(0x1B80, 0x1BBF, 112), // Sundanese
(0x1C00, 0x1C4F, 113), // Lepcha
(0x1C50, 0x1C7F, 114), // Ol Chiki
(0x1D00, 0x1D7F, 4), // Phonetic Extensions
(0x1D80, 0x1DBF, 4), // Phonetic Extensions Supplement
(0x1DC0, 0x1DFF, 6), // Combining Diacritical Marks Supplement
(0x1E00, 0x1EFF, 29), // Latin Extended Additional
(0x1F00, 0x1FFF, 30), // Greek Extended
(0x2000, 0x206F, 31), // General Punctuation
(0x2070, 0x209F, 32), // Superscripts And Subscripts
(0x20A0, 0x20CF, 33), // Currency Symbols
(0x20D0, 0x20FF, 34), // Combining Diacritical Marks For Symbols
(0x2100, 0x214F, 35), // Letterlike Symbols
(0x2150, 0x218F, 36), // Number Forms
(0x2190, 0x21FF, 37), // Arrows
(0x2200, 0x22FF, 38), // Mathematical Operators
(0x2300, 0x23FF, 39), // Miscellaneous Technical
(0x2400, 0x243F, 40), // Control Pictures
(0x2440, 0x245F, 41), // Optical Character Recognition
(0x2460, 0x24FF, 42), // Enclosed Alphanumerics
(0x2500, 0x257F, 43), // Box Drawing
(0x2580, 0x259F, 44), // Block Elements
(0x25A0, 0x25FF, 45), // Geometric Shapes
(0x2600, 0x26FF, 46), // Miscellaneous Symbols
(0x2700, 0x27BF, 47), // Dingbats
(0x27C0, 0x27EF, 38), // Miscellaneous Mathematical Symbols-A
(0x27F0, 0x27FF, 37), // Supplemental Arrows-A
(0x2800, 0x28FF, 82), // Braille Patterns
(0x2900, 0x297F, 37), // Supplemental Arrows-B
(0x2980, 0x29FF, 38), // Miscellaneous Mathematical Symbols-B
(0x2A00, 0x2AFF, 38), // Supplemental Mathematical Operators
(0x2B00, 0x2BFF, 37), // Miscellaneous Symbols and Arrows
(0x2C00, 0x2C5F, 97), // Glagolitic
(0x2C60, 0x2C7F, 29), // Latin Extended-C
(0x2C80, 0x2CFF, 8), // Coptic
(0x2D00, 0x2D2F, 26), // Georgian Supplement
(0x2D30, 0x2D7F, 98), // Tifinagh
(0x2D80, 0x2DDF, 75), // Ethiopic Extended
(0x2DE0, 0x2DFF, 9), // Cyrillic Extended-A
(0x2E00, 0x2E7F, 31), // Supplemental Punctuation
(0x2E80, 0x2EFF, 59), // CJK Radicals Supplement
(0x2F00, 0x2FDF, 59), // Kangxi Radicals
(0x2FF0, 0x2FFF, 59), // Ideographic Description Characters
(0x3000, 0x303F, 48), // CJK Symbols And Punctuation
(0x3040, 0x309F, 49), // Hiragana
(0x30A0, 0x30FF, 50), // Katakana
(0x3100, 0x312F, 51), // Bopomofo
(0x3130, 0x318F, 52), // Hangul Compatibility Jamo
(0x3190, 0x319F, 59), // Kanbun
(0x31A0, 0x31BF, 51), // Bopomofo Extended
(0x31C0, 0x31EF, 61), // CJK Strokes
(0x31F0, 0x31FF, 50), // Katakana Phonetic Extensions
(0x3200, 0x32FF, 54), // Enclosed CJK Letters And Months
(0x3300, 0x33FF, 55), // CJK Compatibility
(0x3400, 0x4DBF, 59), // CJK Unified Ideographs Extension A
(0x4DC0, 0x4DFF, 99), // Yijing Hexagram Symbols
(0x4E00, 0x9FFF, 59), // CJK Unified Ideographs
(0xA000, 0xA48F, 83), // Yi Syllables
(0xA490, 0xA4CF, 83), // Yi Radicals
(0xA500, 0xA63F, 12), // Vai
(0xA640, 0xA69F, 9), // Cyrillic Extended-B
(0xA700, 0xA71F, 5), // Modifier Tone Letters
(0xA720, 0xA7FF, 29), // Latin Extended-D
(0xA800, 0xA82F, 100), // Syloti Nagri
(0xA840, 0xA87F, 53), // Phags-pa
(0xA880, 0xA8DF, 115), // Saurashtra
(0xA900, 0xA92F, 116), // Kayah Li
(0xA930, 0xA95F, 117), // Rejang
(0xAA00, 0xAA5F, 118), // Cham
(0xAC00, 0xD7AF, 56), // Hangul Syllables
(0xD800, 0xDFFF, 57), // Non-Plane 0 *
(0xE000, 0xF8FF, 60), // Private Use Area (plane 0)
(0xF900, 0xFAFF, 61), // CJK Compatibility Ideographs
(0xFB00, 0xFB4F, 62), // Alphabetic Presentation Forms
(0xFB50, 0xFDFF, 63), // Arabic Presentation Forms-A
(0xFE00, 0xFE0F, 91), // Variation Selectors
(0xFE10, 0xFE1F, 65), // Vertical Forms
(0xFE20, 0xFE2F, 64), // Combining Half Marks
(0xFE30, 0xFE4F, 65), // CJK Compatibility Forms
(0xFE50, 0xFE6F, 66), // Small Form Variants
(0xFE70, 0xFEFF, 67), // Arabic Presentation Forms-B
(0xFF00, 0xFFEF, 68), // Halfwidth And Fullwidth Forms
(0xFFF0, 0xFFFF, 69), // Specials
(0x10000, 0x1007F, 101), // Linear B Syllabary
(0x10080, 0x100FF, 101), // Linear B Ideograms
(0x10100, 0x1013F, 101), // Aegean Numbers
(0x10140, 0x1018F, 102), // Ancient Greek Numbers
(0x10190, 0x101CF, 119), // Ancient Symbols
(0x101D0, 0x101FF, 120), // Phaistos Disc
(0x10280, 0x1029F, 121), // Lycian
(0x102A0, 0x102DF, 121), // Carian
(0x10300, 0x1032F, 85), // Old Italic
(0x10330, 0x1034F, 86), // Gothic
(0x10380, 0x1039F, 103), // Ugaritic
(0x103A0, 0x103DF, 104), // Old Persian
(0x10400, 0x1044F, 87), // Deseret
(0x10450, 0x1047F, 105), // Shavian
(0x10480, 0x104AF, 106), // Osmanya
(0x10800, 0x1083F, 107), // Cypriot Syllabary
(0x10900, 0x1091F, 58), // Phoenician
(0x10920, 0x1093F, 121), // Lydian
(0x10A00, 0x10A5F, 108), // Kharoshthi
(0x12000, 0x123FF, 110), // Cuneiform
(0x12400, 0x1247F, 110), // Cuneiform Numbers and Punctuation
(0x1D000, 0x1D0FF, 88), // Byzantine Musical Symbols
(0x1D100, 0x1D1FF, 88), // Musical Symbols
(0x1D200, 0x1D24F, 88), // Ancient Greek Musical Notation
(0x1D300, 0x1D35F, 109), // Tai Xuan Jing Symbols
(0x1D360, 0x1D37F, 111), // Counting Rod Numerals
(0x1D400, 0x1D7FF, 89), // Mathematical Alphanumeric Symbols
(0x1F000, 0x1F02F, 122), // Mahjong Tiles
(0x1F030, 0x1F09F, 122), // Domino Tiles
(0x20000, 0x2A6DF, 59), // CJK Unified Ideographs Extension B
(0x2F800, 0x2FA1F, 61), // CJK Compatibility Ideographs Supplement
(0xE0000, 0xE007F, 92), // Tags
(0xE0100, 0xE01EF, 91), // Variation Selectors Supplement
(0xF0000, 0xFFFFD, 90), // Private Use (plane 15)
(0x100000, 0x10FFFD, 90), // Private Use (plane 16)
];
#[cfg(test)]
mod tests {
use crate::{table_provider::TableProvider, FontRef};
#[test]
fn read_sample() {
let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
let table = font.os2().unwrap();
assert_eq!(table.version(), 4);
}
}

159
vendor/read-fonts/src/tables/post.rs vendored Normal file
View File

@@ -0,0 +1,159 @@
//! the [post (PostScript)](https://docs.microsoft.com/en-us/typography/opentype/spec/post#header) table
include!("../../generated/generated_post.rs");
impl<'a> Post<'a> {
/// The number of glyph names covered by this table
pub fn num_names(&self) -> usize {
match self.version() {
Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.len(),
Version16Dot16::VERSION_2_0 => self.num_glyphs().unwrap() as usize,
_ => 0,
}
}
pub fn glyph_name(&self, glyph_id: GlyphId16) -> Option<&str> {
let glyph_id = glyph_id.to_u16() as usize;
match self.version() {
Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.get(glyph_id).copied(),
Version16Dot16::VERSION_2_0 => {
let idx = self.glyph_name_index()?.get(glyph_id)?.get() as usize;
if idx < DEFAULT_GLYPH_NAMES.len() {
return DEFAULT_GLYPH_NAMES.get(idx).copied();
}
let idx = idx - DEFAULT_GLYPH_NAMES.len();
match self.string_data().unwrap().get(idx) {
Some(Ok(s)) => Some(s.0),
_ => None,
}
}
_ => None,
}
}
//FIXME: how do we want to traverse this? I want to stop needing to
// add special cases for things...
#[cfg(feature = "experimental_traverse")]
fn traverse_string_data(&self) -> FieldType<'a> {
FieldType::I8(-42) // meaningless value
}
}
/// A string in the post table.
///
/// This is basically just a newtype that knows how to parse from a Pascal-style
/// string.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PString<'a>(&'a str);
impl<'a> PString<'a> {
pub fn as_str(&self) -> &'a str {
self.0
}
}
impl std::ops::Deref for PString<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl PartialEq<&str> for PString<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl<'a> FontRead<'a> for PString<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let len: u8 = data.read_at(0)?;
let pstring = data
.as_bytes()
.get(1..len as usize + 1)
.ok_or(ReadError::OutOfBounds)?;
if pstring.is_ascii() {
Ok(PString(std::str::from_utf8(pstring).unwrap()))
} else {
//FIXME not really sure how we want to handle this?
Err(ReadError::MalformedData("Must be valid ascii"))
}
}
}
impl VarSize for PString<'_> {
type Size = u8;
}
/// The 258 glyph names defined for Macintosh TrueType fonts
#[rustfmt::skip]
pub static DEFAULT_GLYPH_NAMES: [&str; 258] = [
".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar",
"percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma",
"hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven",
"eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B",
"C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
"V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
"underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
"o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright",
"asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis",
"aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute",
"egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde",
"oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex",
"udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph",
"germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
"Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff",
"summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae",
"oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal",
"Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
"Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft",
"quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency",
"guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase",
"quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave",
"Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve",
"Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve",
"dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
"Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn",
"thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf",
"onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla",
"scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat",
];
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::{bebuffer::BeBuffer, post as test_data};
#[test]
fn test_post() {
let table = Post::read(test_data::SIMPLE.into()).unwrap();
assert_eq!(table.version(), Version16Dot16::VERSION_2_0);
assert_eq!(table.underline_position(), FWord::new(-75));
assert_eq!(table.glyph_name(GlyphId16::new(1)), Some(".notdef"));
assert_eq!(table.glyph_name(GlyphId16::new(2)), Some("space"));
assert_eq!(table.glyph_name(GlyphId16::new(7)), Some("hello"));
assert_eq!(table.glyph_name(GlyphId16::new(8)), Some("hi"));
assert_eq!(table.glyph_name(GlyphId16::new(9)), Some("hola"));
}
#[test]
fn parse_versioned_fields() {
fn make_basic_post(version: Version16Dot16) -> BeBuffer {
BeBuffer::new()
.push(version)
.push(Fixed::from_i32(5))
.extend([FWord::new(6), FWord::new(7)]) //underline pos/thickness
.push(0u32) // isFixedPitch
.extend([7u32, 8, 9, 10]) // min/max mem x
}
// basic table should not parse in v2.0, because that adds another field:
let buf = make_basic_post(Version16Dot16::VERSION_2_0);
assert!(Post::read(buf.data().into()).is_err());
// but it should be fine on version 3.0, which does not require any extra fields:
let buf = make_basic_post(Version16Dot16::VERSION_3_0);
assert!(Post::read(buf.data().into()).is_ok());
}
}

View File

@@ -0,0 +1,114 @@
//! PostScript (CFF and CFF2) common tables.
use std::fmt;
mod blend;
mod charset;
mod fd_select;
mod index;
mod stack;
mod string;
pub mod charstring;
pub mod dict;
include!("../../generated/generated_postscript.rs");
pub use blend::BlendState;
pub use charset::{Charset, CharsetIter};
pub use index::Index;
pub use stack::{Number, Stack};
pub use string::{Latin1String, StringId, STANDARD_STRINGS};
/// Errors that are specific to PostScript processing.
#[derive(Clone, Debug)]
pub enum Error {
InvalidIndexOffsetSize(u8),
ZeroOffsetInIndex,
InvalidVariationStoreIndex(u16),
StackOverflow,
StackUnderflow,
InvalidStackAccess(usize),
ExpectedI32StackEntry(usize),
InvalidNumber,
InvalidDictOperator(u8),
InvalidCharstringOperator(u8),
CharstringNestingDepthLimitExceeded,
MissingSubroutines,
MissingBlendState,
MissingPrivateDict,
MissingCharstrings,
Read(ReadError),
}
impl From<ReadError> for Error {
fn from(value: ReadError) -> Self {
Self::Read(value)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidIndexOffsetSize(size) => {
write!(f, "invalid offset size of {size} for INDEX (expected 1-4)")
}
Self::ZeroOffsetInIndex => {
write!(f, "invalid offset of 0 in INDEX (must be >= 1)")
}
Self::InvalidVariationStoreIndex(index) => {
write!(
f,
"variation store index {index} referenced an invalid variation region"
)
}
Self::StackOverflow => {
write!(f, "attempted to push a value to a full stack")
}
Self::StackUnderflow => {
write!(f, "attempted to pop a value from an empty stack")
}
Self::InvalidStackAccess(index) => {
write!(f, "invalid stack access for index {index}")
}
Self::ExpectedI32StackEntry(index) => {
write!(f, "attempted to read an integer at stack index {index}, but found a fixed point value")
}
Self::InvalidNumber => {
write!(f, "number is in an invalid format")
}
Self::InvalidDictOperator(operator) => {
write!(f, "dictionary operator {operator} is invalid")
}
Self::InvalidCharstringOperator(operator) => {
write!(f, "charstring operator {operator} is invalid")
}
Self::CharstringNestingDepthLimitExceeded => {
write!(
f,
"exceeded subroutine nesting depth limit {} while evaluating a charstring",
charstring::NESTING_DEPTH_LIMIT
)
}
Self::MissingSubroutines => {
write!(
f,
"encountered a callsubr operator but no subroutine index was provided"
)
}
Self::MissingBlendState => {
write!(
f,
"encountered a blend operator but no blend state was provided"
)
}
Self::MissingPrivateDict => {
write!(f, "CFF table does not contain a private dictionary")
}
Self::MissingCharstrings => {
write!(f, "CFF table does not contain a charstrings index")
}
Self::Read(err) => write!(f, "{err}"),
}
}
}

View File

@@ -0,0 +1,166 @@
//! Support for the dictionary and charstring blend operator.
use font_types::{BigEndian, F2Dot14, Fixed};
use super::Error;
use crate::tables::variations::{ItemVariationData, ItemVariationStore};
/// The maximum number of region scalars that we precompute.
///
/// Completely made up number chosen to balance size with trying to capture as
/// many precomputed regions as possible.
///
/// TODO: measure with a larger set of CFF2 fonts and adjust accordingly.
const MAX_PRECOMPUTED_SCALARS: usize = 16;
/// State for processing the blend operator for DICTs and charstrings.
///
/// To avoid allocation, scalars are computed on demand but this can be
/// prohibitively expensive in charstrings where blends are applied to
/// large numbers of elements. To amortize the cost, a fixed number of
/// precomputed scalars are stored internally and the overflow is computed
/// as needed.
///
/// The `MAX_PRECOMPUTED_SCALARS` constant determines the size of the
/// internal buffer (currently 16).
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#45-variation-data-operators>
pub struct BlendState<'a> {
store: ItemVariationStore<'a>,
coords: &'a [F2Dot14],
store_index: u16,
// The following are dependent on the current `store_index`
data: Option<ItemVariationData<'a>>,
region_indices: &'a [BigEndian<u16>],
scalars: [Fixed; MAX_PRECOMPUTED_SCALARS],
}
impl<'a> BlendState<'a> {
pub fn new(
store: ItemVariationStore<'a>,
coords: &'a [F2Dot14],
store_index: u16,
) -> Result<Self, Error> {
let mut state = Self {
store,
coords,
store_index,
data: None,
region_indices: &[],
scalars: Default::default(),
};
state.update_precomputed_scalars()?;
Ok(state)
}
/// Sets the active variation store index.
///
/// This should be called with the operand of the `vsindex` operator
/// for both DICTs and charstrings.
pub fn set_store_index(&mut self, store_index: u16) -> Result<(), Error> {
if self.store_index != store_index {
self.store_index = store_index;
self.update_precomputed_scalars()?;
}
Ok(())
}
/// Returns the number of variation regions for the currently active
/// variation store index.
pub fn region_count(&self) -> Result<usize, Error> {
Ok(self.region_indices.len())
}
/// Returns an iterator yielding scalars for each variation region of
/// the currently active variation store index.
pub fn scalars(&self) -> Result<impl Iterator<Item = Result<Fixed, Error>> + '_, Error> {
let total_count = self.region_indices.len();
let cached = &self.scalars[..MAX_PRECOMPUTED_SCALARS.min(total_count)];
let remaining_regions = if total_count > MAX_PRECOMPUTED_SCALARS {
&self.region_indices[MAX_PRECOMPUTED_SCALARS..]
} else {
&[]
};
Ok(cached.iter().copied().map(Ok).chain(
remaining_regions
.iter()
.map(|region_ix| self.region_scalar(region_ix.get())),
))
}
fn update_precomputed_scalars(&mut self) -> Result<(), Error> {
self.data = None;
self.region_indices = &[];
let store = &self.store;
let variation_data = store.item_variation_data();
let data = variation_data
.get(self.store_index as usize)
.ok_or(Error::InvalidVariationStoreIndex(self.store_index))??;
let region_indices = data.region_indexes();
let regions = self.store.variation_region_list()?.variation_regions();
// Precompute scalars for all regions up to MAX_PRECOMPUTED_SCALARS
for (region_ix, scalar) in region_indices
.iter()
.take(MAX_PRECOMPUTED_SCALARS)
.zip(&mut self.scalars)
{
// We can't use region_scalar here because self is already borrowed
// as mutable above
let region = regions.get(region_ix.get() as usize)?;
*scalar = region.compute_scalar(self.coords);
}
self.data = Some(data);
self.region_indices = region_indices;
Ok(())
}
fn region_scalar(&self, index: u16) -> Result<Fixed, Error> {
Ok(self
.store
.variation_region_list()?
.variation_regions()
.get(index as usize)
.map_err(Error::Read)?
.compute_scalar(self.coords))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{FontData, FontRead};
#[test]
fn example_blends() {
// args are (coords, expected_scalars)
example_test(&[-1.0], &[0.0, 1.0]);
example_test(&[-0.25], &[0.5, 0.0]);
example_test(&[-0.5], &[1.0, 0.0]);
example_test(&[-0.75], &[0.5, 0.5]);
example_test(&[0.0], &[0.0, 0.0]);
example_test(&[0.5], &[0.0, 0.0]);
example_test(&[1.0], &[0.0, 0.0]);
}
fn example_test(coords: &[f32], expected: &[f64]) {
let scalars = example_scalars_for_coords(coords);
let expected: Vec<_> = expected.iter().copied().map(Fixed::from_f64).collect();
assert_eq!(scalars, expected);
}
fn example_scalars_for_coords(coords: &[f32]) -> Vec<Fixed> {
let ivs = example_ivs();
let coords: Vec<_> = coords
.iter()
.map(|coord| F2Dot14::from_f32(*coord))
.collect();
let blender = BlendState::new(ivs, &coords, 0).unwrap();
blender.scalars().unwrap().map(|res| res.unwrap()).collect()
}
fn example_ivs() -> ItemVariationStore<'static> {
// ItemVariationStore is at offset 18 in the CFF example table
let ivs_data = &font_test_data::cff2::EXAMPLE[18..];
ItemVariationStore::read(FontData::new(ivs_data)).unwrap()
}
}

View File

@@ -0,0 +1,463 @@
//! CFF charset support.
use super::{
CharsetFormat0, CharsetFormat1, CharsetFormat2, CharsetRange1, CharsetRange2, CustomCharset,
FontData, FontRead, GlyphId, ReadError, StringId,
};
/// Character set for mapping from glyph to string identifiers.
///
/// See <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=21>
#[derive(Clone)]
pub struct Charset<'a> {
kind: CharsetKind<'a>,
num_glyphs: u32,
}
impl<'a> Charset<'a> {
pub fn new(
cff_data: FontData<'a>,
charset_offset: usize,
num_glyphs: u32,
) -> Result<Self, ReadError> {
let kind = match charset_offset {
0 => CharsetKind::IsoAdobe,
1 => CharsetKind::Expert,
2 => CharsetKind::ExpertSubset,
_ => {
let data = cff_data
.split_off(charset_offset)
.ok_or(ReadError::OutOfBounds)?;
CharsetKind::Custom(CustomCharset::read(data)?)
}
};
Ok(Self { kind, num_glyphs })
}
pub fn kind(&self) -> &CharsetKind<'a> {
&self.kind
}
pub fn num_glyphs(&self) -> u32 {
self.num_glyphs
}
/// Returns the string identifier for the given glyph identifier.
pub fn string_id(&self, glyph_id: GlyphId) -> Result<StringId, ReadError> {
let gid = glyph_id.to_u32();
if gid >= self.num_glyphs {
return Err(ReadError::OutOfBounds);
}
match &self.kind {
CharsetKind::IsoAdobe => {
// The ISOAdobe charset is an identity mapping of gid->sid up
// to 228 entries
// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=45>
if gid <= 228 {
Ok(StringId::new(gid as u16))
} else {
Err(ReadError::OutOfBounds)
}
}
CharsetKind::Expert => EXPERT_CHARSET
.get(gid as usize)
.copied()
.ok_or(ReadError::OutOfBounds)
.map(StringId::new),
CharsetKind::ExpertSubset => EXPERT_SUBSET_CHARSET
.get(gid as usize)
.copied()
.ok_or(ReadError::OutOfBounds)
.map(StringId::new),
CharsetKind::Custom(custom) => match custom {
CustomCharset::Format0(fmt) => fmt.string_id(glyph_id),
CustomCharset::Format1(fmt) => fmt.string_id(glyph_id),
CustomCharset::Format2(fmt) => fmt.string_id(glyph_id),
},
}
}
/// Returns an iterator over all of the glyph and string identifier
/// mappings.
pub fn iter(&self) -> CharsetIter<'a> {
match &self.kind {
CharsetKind::IsoAdobe
| CharsetKind::Expert
| CharsetKind::ExpertSubset
| CharsetKind::Custom(CustomCharset::Format0(_)) => {
CharsetIter(Iter::Simple(self.clone(), 0))
}
CharsetKind::Custom(CustomCharset::Format1(custom)) => CharsetIter(Iter::Custom1(
RangeIter::new(custom.ranges(), self.num_glyphs),
)),
CharsetKind::Custom(CustomCharset::Format2(custom)) => CharsetIter(Iter::Custom2(
RangeIter::new(custom.ranges(), self.num_glyphs),
)),
}
}
}
/// Predefined and custom character sets.
#[derive(Clone)]
pub enum CharsetKind<'a> {
IsoAdobe,
Expert,
ExpertSubset,
Custom(CustomCharset<'a>),
}
impl CharsetFormat0<'_> {
fn string_id(&self, glyph_id: GlyphId) -> Result<StringId, ReadError> {
let gid = glyph_id.to_u32() as usize;
if gid == 0 {
Ok(StringId::new(0))
} else {
self.glyph()
.get(gid - 1)
.map(|id| StringId::new(id.get()))
.ok_or(ReadError::OutOfBounds)
}
}
}
impl CharsetFormat1<'_> {
fn string_id(&self, glyph_id: GlyphId) -> Result<StringId, ReadError> {
string_id_from_ranges(self.ranges(), glyph_id)
}
}
impl CharsetFormat2<'_> {
fn string_id(&self, glyph_id: GlyphId) -> Result<StringId, ReadError> {
string_id_from_ranges(self.ranges(), glyph_id)
}
}
fn string_id_from_ranges<T: CharsetRange>(
ranges: &[T],
glyph_id: GlyphId,
) -> Result<StringId, ReadError> {
let mut gid = glyph_id.to_u32();
// The notdef glyph isn't explicitly mapped so we need to special case
// it and add -1 and +1 at a few places when processing ranges
if gid == 0 {
return Ok(StringId::new(0));
}
gid -= 1;
let mut end = 0u32;
// Each range provides the string ids for `n_left + 1` glyphs with
// the sequence of string ids starting at `first`. Since the counts
// are cumulative, we must scan them all in order until we find
// the range that contains our requested glyph.
for range in ranges {
let next_end = end
.checked_add(range.n_left() + 1)
.ok_or(ReadError::OutOfBounds)?;
if gid < next_end {
return (gid - end)
.checked_add(range.first())
.and_then(|sid| sid.try_into().ok())
.ok_or(ReadError::OutOfBounds)
.map(StringId::new);
}
end = next_end;
}
Err(ReadError::OutOfBounds)
}
/// Trait that unifies ranges for formats 1 and 2 so that we can implement
/// the tricky search logic once.
trait CharsetRange {
fn first(&self) -> u32;
fn n_left(&self) -> u32;
}
impl CharsetRange for CharsetRange1 {
fn first(&self) -> u32 {
self.first.get() as u32
}
fn n_left(&self) -> u32 {
self.n_left as u32
}
}
impl CharsetRange for CharsetRange2 {
fn first(&self) -> u32 {
self.first.get() as u32
}
fn n_left(&self) -> u32 {
self.n_left.get() as u32
}
}
/// Iterator over the glyph and string identifier mappings in a character set.
#[derive(Clone)]
pub struct CharsetIter<'a>(Iter<'a>);
impl Iterator for CharsetIter<'_> {
type Item = (GlyphId, StringId);
fn next(&mut self) -> Option<Self::Item> {
match &mut self.0 {
Iter::Simple(charset, cur) => {
let gid = GlyphId::new(*cur);
let sid = charset.string_id(gid).ok()?;
*cur = cur.checked_add(1)?;
Some((gid, sid))
}
Iter::Custom1(custom) => custom.next(),
Iter::Custom2(custom) => custom.next(),
}
}
}
#[derive(Clone)]
enum Iter<'a> {
/// Predefined sets and custom format 0 are just array lookups so we use
/// the builtin mapping function.
Simple(Charset<'a>, u32),
Custom1(RangeIter<'a, CharsetRange1>),
Custom2(RangeIter<'a, CharsetRange2>),
}
/// Custom iterator for range based formats.
///
/// Each individual lookup requires a linear scan through the ranges so this
/// provides a more efficient code path for iteration.
#[derive(Clone)]
struct RangeIter<'a, T> {
ranges: std::slice::Iter<'a, T>,
num_glyphs: u32,
gid: u32,
first: u32,
end: u32,
prev_end: u32,
}
impl<'a, T> RangeIter<'a, T>
where
T: CharsetRange,
{
fn new(ranges: &'a [T], num_glyphs: u32) -> Self {
let mut ranges = ranges.iter();
let (first, end) = next_range(&mut ranges).unwrap_or_default();
Self {
ranges,
num_glyphs,
gid: 0,
first,
end,
prev_end: 0,
}
}
fn next(&mut self) -> Option<(GlyphId, StringId)> {
if self.gid >= self.num_glyphs {
return None;
}
// The notdef glyph isn't explicitly mapped so we need to special case
// it and add -1 and +1 at a few places when processing ranges
if self.gid == 0 {
self.gid += 1;
return Some((GlyphId::new(0), StringId::new(0)));
}
let gid = self.gid - 1;
self.gid = self.gid.checked_add(1)?;
while gid >= self.end {
let (first, end) = next_range(&mut self.ranges)?;
self.prev_end = self.end;
self.first = first;
self.end = self.prev_end.checked_add(end)?;
}
let sid = self
.first
.checked_add(gid.checked_sub(self.prev_end)?)?
.try_into()
.ok()?;
Some((GlyphId::new(gid + 1), StringId::new(sid)))
}
}
fn next_range<T: CharsetRange>(ranges: &mut std::slice::Iter<T>) -> Option<(u32, u32)> {
ranges
.next()
.map(|range| (range.first(), range.n_left() + 1))
}
/// See "Expert" charset at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=47>
#[rustfmt::skip]
const EXPERT_CHARSET: &[u16] = &[
0, 1, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 13, 14, 15, 99,
239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 27, 28, 249, 250, 251, 252,
253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 109, 110,
267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282,
283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298,
299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
315, 316, 317, 318, 158, 155, 163, 319, 320, 321, 322, 323, 324, 325, 326, 150,
164, 169, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340,
341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356,
357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372,
373, 374, 375, 376, 377, 378,
];
/// See "Expert Subset" charset at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf#page=49>
#[rustfmt::skip]
const EXPERT_SUBSET_CHARSET: &[u16] = &[
0, 1, 231, 232, 235, 236, 237, 238, 13, 14, 15, 99, 239, 240, 241, 242,
243, 244, 245, 246, 247, 248, 27, 28, 249, 250, 251, 253, 254, 255, 256, 257,
258, 259, 260, 261, 262, 263, 264, 265, 266, 109, 110, 267, 268, 269, 270, 272,
300, 301, 302, 305, 314, 315, 158, 155, 163, 320, 321, 322, 323, 324, 325, 326,
150, 164, 169, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339,
340, 341, 342, 343, 344, 345, 346
];
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::bebuffer::BeBuffer;
#[test]
fn iso_adobe_charset() {
// Offset of 0 signifies the ISOAdobe charset
let charset_offset = 0;
let num_glyphs = 64;
// This is an identity mapping
let expected = |gid: GlyphId| Some(gid.to_u32());
test_simple_mapping(charset_offset, num_glyphs, expected);
}
#[test]
fn expert_charset() {
// Offset 1 signifies the expert charset
let charset_offset = 1;
let num_glyphs = 64;
// This is an array based mapping
let expected = |gid: GlyphId| {
EXPERT_CHARSET
.get(gid.to_u32() as usize)
.map(|id| *id as u32)
};
test_simple_mapping(charset_offset, num_glyphs, expected);
}
#[test]
fn expert_subset_charset() {
// Offset 2 signifies the expert subset charset
let charset_offset = 2;
let num_glyphs = 64;
// This is an array based mapping
let expected = |gid: GlyphId| {
EXPERT_SUBSET_CHARSET
.get(gid.to_u32() as usize)
.map(|id| *id as u32)
};
test_simple_mapping(charset_offset, num_glyphs, expected);
}
// Common test setup for identity or array based charset mappings
fn test_simple_mapping(
charset_offset: usize,
num_glyphs: u32,
expected: impl Fn(GlyphId) -> Option<u32>,
) {
let charset = Charset::new(FontData::new(&[]), charset_offset, num_glyphs).unwrap();
for gid in 0..num_glyphs {
let gid = GlyphId::new(gid);
assert_eq!(
charset.string_id(gid).unwrap().to_u16() as u32,
expected(gid).unwrap()
)
}
// Don't map glyphs beyond num_glyphs
for gid in num_glyphs..u16::MAX as u32 {
assert_eq!(charset.string_id(GlyphId::new(gid)).ok(), None);
}
}
#[test]
fn custom_mapping_format0() {
let mut buf = BeBuffer::new();
let num_glyphs = 6;
// Add some padding so we can generate an offset greater than 2
buf = buf.extend([0u8; 4]);
// format 0
buf = buf.push(0u8);
// glyph array: each sid is gid * 2
buf = buf.extend([2u16, 4, 6, 8, 10]);
let charset = Charset::new(FontData::new(buf.data()), 4, num_glyphs).unwrap();
// Test lookup code path
for gid in 0..num_glyphs {
assert_eq!(
charset.string_id(GlyphId::new(gid)).unwrap().to_u16() as u32,
gid * 2
)
}
// Test iterator code path
for (gid, sid) in charset.iter() {
assert_eq!(sid.to_u16() as u32, gid.to_u32() * 2);
}
assert_eq!(charset.iter().count() as u32, num_glyphs);
// Test out of bounds glyphs
for gid in num_glyphs..u16::MAX as u32 {
assert_eq!(charset.string_id(GlyphId::new(gid)).ok(), None);
}
}
#[test]
fn custom_mapping_format1() {
let mut buf = BeBuffer::new();
let num_glyphs = 7;
// Add some padding so we can generate an offset greater than 2
buf = buf.extend([0u8; 4]);
// format 1
buf = buf.push(1u8);
// Three disjoint range mappings
buf = buf.push(8u16).push(2u8);
buf = buf.push(1200u16).push(0u8);
buf = buf.push(20u16).push(1u8);
let expected_sids = [0, 8, 9, 10, 1200, 20, 21];
test_range_mapping(buf.data(), num_glyphs, &expected_sids);
}
#[test]
fn custom_mapping_format2() {
let mut buf = BeBuffer::new();
// Add some padding so we can generate an offset greater than 2
buf = buf.extend([0u8; 4]);
// format 2
buf = buf.push(2u8);
// Three disjoint range mappings
buf = buf.push(8u16).push(2u16);
buf = buf.push(1200u16).push(0u16);
buf = buf.push(20u16).push(800u16);
let mut expected_sids = vec![0, 8, 9, 10, 1200];
for i in 0..=800 {
expected_sids.push(i + 20);
}
let num_glyphs = expected_sids.len() as u32;
test_range_mapping(buf.data(), num_glyphs, &expected_sids);
}
// Common code for testing range based mappings
fn test_range_mapping(data: &[u8], num_glyphs: u32, expected_sids: &[u32]) {
let charset = Charset::new(FontData::new(data), 4, num_glyphs).unwrap();
// Test lookup code path
for (gid, sid) in expected_sids.iter().enumerate() {
assert_eq!(
charset.string_id(GlyphId::new(gid as _)).unwrap().to_u16() as u32,
*sid
)
}
// Test iterator code path
assert!(charset.iter().eq(expected_sids
.iter()
.enumerate()
.map(|(gid, sid)| (GlyphId::new(gid as u32), StringId::new(*sid as u16)))));
assert_eq!(charset.iter().count() as u32, num_glyphs);
// Test out of bounds glyphs
for gid in num_glyphs..u16::MAX as u32 {
assert_eq!(charset.string_id(GlyphId::new(gid)).ok(), None);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,818 @@
//! Parsing for PostScript DICTs.
use std::ops::Range;
use super::{BlendState, Error, Number, Stack, StringId};
use crate::{types::Fixed, Cursor, ReadError};
/// PostScript DICT operator.
///
/// See "Table 9 Top DICT Operator Entries" and "Table 23 Private DICT
/// Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf>
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Operator {
Version,
Notice,
FullName,
FamilyName,
Weight,
FontBbox,
CharstringsOffset,
PrivateDictRange,
VariationStoreOffset,
Copyright,
IsFixedPitch,
ItalicAngle,
UnderlinePosition,
UnderlineThickness,
PaintType,
CharstringType,
FontMatrix,
StrokeWidth,
FdArrayOffset,
FdSelectOffset,
BlueValues,
OtherBlues,
FamilyBlues,
FamilyOtherBlues,
SubrsOffset,
VariationStoreIndex,
BlueScale,
BlueShift,
BlueFuzz,
LanguageGroup,
ExpansionFactor,
Encoding,
Charset,
UniqueId,
Xuid,
SyntheticBase,
PostScript,
BaseFontName,
BaseFontBlend,
Ros,
CidFontVersion,
CidFontRevision,
CidFontType,
CidCount,
UidBase,
FontName,
StdHw,
StdVw,
DefaultWidthX,
NominalWidthX,
Blend,
StemSnapH,
StemSnapV,
ForceBold,
InitialRandomSeed,
}
impl Operator {
fn from_opcode(opcode: u8) -> Option<Self> {
use Operator::*;
Some(match opcode {
// Top DICT operators
0 => Version,
1 => Notice,
2 => FullName,
3 => FamilyName,
4 => Weight,
5 => FontBbox,
13 => UniqueId,
14 => Xuid,
15 => Charset,
16 => Encoding,
17 => CharstringsOffset,
18 => PrivateDictRange,
24 => VariationStoreOffset,
// Private DICT operators
6 => BlueValues,
7 => OtherBlues,
8 => FamilyBlues,
9 => FamilyOtherBlues,
10 => StdHw,
11 => StdVw,
19 => SubrsOffset,
20 => DefaultWidthX,
21 => NominalWidthX,
22 => VariationStoreIndex,
23 => Blend,
// Font DICT only uses PrivateDictRange
_ => return None,
})
}
fn from_extended_opcode(opcode: u8) -> Option<Self> {
use Operator::*;
Some(match opcode {
// Top DICT operators
0 => Copyright,
1 => IsFixedPitch,
2 => ItalicAngle,
3 => UnderlinePosition,
4 => UnderlineThickness,
5 => PaintType,
6 => CharstringType,
7 => FontMatrix,
8 => StrokeWidth,
20 => SyntheticBase,
21 => PostScript,
22 => BaseFontName,
23 => BaseFontBlend,
30 => Ros,
31 => CidFontVersion,
32 => CidFontRevision,
33 => CidFontType,
34 => CidCount,
35 => UidBase,
36 => FdArrayOffset,
37 => FdSelectOffset,
38 => FontName,
// Private DICT operators
9 => BlueScale,
10 => BlueShift,
11 => BlueFuzz,
12 => StemSnapH,
13 => StemSnapV,
14 => ForceBold,
17 => LanguageGroup,
18 => ExpansionFactor,
19 => InitialRandomSeed,
_ => return None,
})
}
}
/// Either a PostScript DICT operator or a (numeric) operand.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Token {
Operator(Operator),
Operand(Number),
}
impl From<Operator> for Token {
fn from(value: Operator) -> Self {
Self::Operator(value)
}
}
impl<T> From<T> for Token
where
T: Into<Number>,
{
fn from(value: T) -> Self {
Self::Operand(value.into())
}
}
/// Given a byte slice containing DICT data, returns an iterator yielding
/// raw operands and operators.
///
/// This does not perform any additional processing such as type conversion,
/// delta decoding or blending.
pub fn tokens(dict_data: &[u8]) -> impl Iterator<Item = Result<Token, Error>> + '_ + Clone {
let mut cursor = crate::FontData::new(dict_data).cursor();
std::iter::from_fn(move || {
if cursor.remaining_bytes() == 0 {
None
} else {
Some(parse_token(&mut cursor))
}
})
}
fn parse_token(cursor: &mut Cursor) -> Result<Token, Error> {
// Escape opcode for accessing extensions.
const ESCAPE: u8 = 12;
let b0 = cursor.read::<u8>()?;
Ok(if b0 == ESCAPE {
let b1 = cursor.read::<u8>()?;
Token::Operator(Operator::from_extended_opcode(b1).ok_or(Error::InvalidDictOperator(b1))?)
} else {
// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-3-operand-encoding>
match b0 {
28 | 29 | 32..=254 => Token::Operand(parse_int(cursor, b0)?.into()),
30 => Token::Operand(parse_bcd(cursor)?.into()),
_ => Token::Operator(Operator::from_opcode(b0).ok_or(Error::InvalidDictOperator(b0))?),
}
})
}
/// PostScript DICT Operator with its associated operands.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Entry {
Version(StringId),
Notice(StringId),
FullName(StringId),
FamilyName(StringId),
Weight(StringId),
FontBbox([Fixed; 4]),
CharstringsOffset(usize),
PrivateDictRange(Range<usize>),
VariationStoreOffset(usize),
Copyright(StringId),
IsFixedPitch(bool),
ItalicAngle(Fixed),
UnderlinePosition(Fixed),
UnderlineThickness(Fixed),
PaintType(i32),
CharstringType(i32),
FontMatrix([Fixed; 6]),
StrokeWidth(Fixed),
FdArrayOffset(usize),
FdSelectOffset(usize),
BlueValues(Blues),
OtherBlues(Blues),
FamilyBlues(Blues),
FamilyOtherBlues(Blues),
SubrsOffset(usize),
VariationStoreIndex(u16),
BlueScale(Fixed),
BlueShift(Fixed),
BlueFuzz(Fixed),
LanguageGroup(i32),
ExpansionFactor(Fixed),
Encoding(usize),
Charset(usize),
UniqueId(i32),
Xuid,
SyntheticBase(i32),
PostScript(StringId),
BaseFontName(StringId),
BaseFontBlend,
Ros {
registry: StringId,
ordering: StringId,
supplement: Fixed,
},
CidFontVersion(Fixed),
CidFontRevision(Fixed),
CidFontType(i32),
CidCount(u32),
UidBase(i32),
FontName(StringId),
StdHw(Fixed),
StdVw(Fixed),
DefaultWidthX(Fixed),
NominalWidthX(Fixed),
StemSnapH(StemSnaps),
StemSnapV(StemSnaps),
ForceBold(bool),
InitialRandomSeed(i32),
}
/// Given a byte slice containing DICT data, returns an iterator yielding
/// each operator with its associated operands.
///
/// This performs appropriate type conversions, decodes deltas and applies
/// blending.
///
/// If processing a Private DICT from a CFF2 table and an item variation
/// store is present, then `blend_state` must be provided.
pub fn entries<'a>(
dict_data: &'a [u8],
mut blend_state: Option<BlendState<'a>>,
) -> impl Iterator<Item = Result<Entry, Error>> + 'a {
let mut stack = Stack::new();
let mut token_iter = tokens(dict_data);
std::iter::from_fn(move || loop {
let token = match token_iter.next()? {
Ok(token) => token,
Err(e) => return Some(Err(e)),
};
match token {
Token::Operand(number) => match stack.push(number) {
Ok(_) => continue,
Err(e) => return Some(Err(e)),
},
Token::Operator(op) => {
if op == Operator::Blend || op == Operator::VariationStoreIndex {
let state = match blend_state.as_mut() {
Some(state) => state,
None => return Some(Err(Error::MissingBlendState)),
};
if op == Operator::VariationStoreIndex {
match stack
.get_i32(0)
.and_then(|ix| state.set_store_index(ix as u16))
{
Ok(_) => {}
Err(e) => return Some(Err(e)),
}
}
if op == Operator::Blend {
match stack.apply_blend(state) {
Ok(_) => continue,
Err(e) => return Some(Err(e)),
}
}
}
let entry = parse_entry(op, &mut stack);
stack.clear();
return Some(entry);
}
}
})
}
fn parse_entry(op: Operator, stack: &mut Stack) -> Result<Entry, Error> {
use Operator::*;
Ok(match op {
Version => Entry::Version(stack.pop_i32()?.into()),
Notice => Entry::Notice(stack.pop_i32()?.into()),
FullName => Entry::FullName(stack.pop_i32()?.into()),
FamilyName => Entry::FamilyName(stack.pop_i32()?.into()),
Weight => Entry::Weight(stack.pop_i32()?.into()),
FontBbox => Entry::FontBbox([
stack.get_fixed(0)?,
stack.get_fixed(1)?,
stack.get_fixed(2)?,
stack.get_fixed(3)?,
]),
CharstringsOffset => Entry::CharstringsOffset(stack.pop_i32()? as usize),
PrivateDictRange => {
let len = stack.get_i32(0)? as usize;
let start = stack.get_i32(1)? as usize;
let end = start.checked_add(len).ok_or(ReadError::OutOfBounds)?;
Entry::PrivateDictRange(start..end)
}
VariationStoreOffset => Entry::VariationStoreOffset(stack.pop_i32()? as usize),
Copyright => Entry::Copyright(stack.pop_i32()?.into()),
IsFixedPitch => Entry::IsFixedPitch(stack.pop_i32()? != 0),
ItalicAngle => Entry::ItalicAngle(stack.pop_fixed()?),
UnderlinePosition => Entry::UnderlinePosition(stack.pop_fixed()?),
UnderlineThickness => Entry::UnderlineThickness(stack.pop_fixed()?),
PaintType => Entry::PaintType(stack.pop_i32()?),
CharstringType => Entry::CharstringType(stack.pop_i32()?),
FontMatrix => Entry::FontMatrix([
stack.get_fixed(0)?,
stack.get_fixed(1)?,
stack.get_fixed(2)?,
stack.get_fixed(3)?,
stack.get_fixed(4)?,
stack.get_fixed(5)?,
]),
StrokeWidth => Entry::StrokeWidth(stack.pop_fixed()?),
FdArrayOffset => Entry::FdArrayOffset(stack.pop_i32()? as usize),
FdSelectOffset => Entry::FdSelectOffset(stack.pop_i32()? as usize),
BlueValues => {
stack.apply_delta_prefix_sum();
Entry::BlueValues(Blues::new(stack.fixed_values()))
}
OtherBlues => {
stack.apply_delta_prefix_sum();
Entry::OtherBlues(Blues::new(stack.fixed_values()))
}
FamilyBlues => {
stack.apply_delta_prefix_sum();
Entry::FamilyBlues(Blues::new(stack.fixed_values()))
}
FamilyOtherBlues => {
stack.apply_delta_prefix_sum();
Entry::FamilyOtherBlues(Blues::new(stack.fixed_values()))
}
SubrsOffset => Entry::SubrsOffset(stack.pop_i32()? as usize),
VariationStoreIndex => Entry::VariationStoreIndex(stack.pop_i32()? as u16),
BlueScale => Entry::BlueScale(stack.pop_fixed()?),
BlueShift => Entry::BlueShift(stack.pop_fixed()?),
BlueFuzz => Entry::BlueFuzz(stack.pop_fixed()?),
LanguageGroup => Entry::LanguageGroup(stack.pop_i32()?),
ExpansionFactor => Entry::ExpansionFactor(stack.pop_fixed()?),
Encoding => Entry::Encoding(stack.pop_i32()? as usize),
Charset => Entry::Charset(stack.pop_i32()? as usize),
UniqueId => Entry::UniqueId(stack.pop_i32()?),
Xuid => Entry::Xuid,
SyntheticBase => Entry::SyntheticBase(stack.pop_i32()?),
PostScript => Entry::PostScript(stack.pop_i32()?.into()),
BaseFontName => Entry::BaseFontName(stack.pop_i32()?.into()),
BaseFontBlend => Entry::BaseFontBlend,
Ros => Entry::Ros {
registry: stack.get_i32(0)?.into(),
ordering: stack.get_i32(1)?.into(),
supplement: stack.get_fixed(2)?,
},
CidFontVersion => Entry::CidFontVersion(stack.pop_fixed()?),
CidFontRevision => Entry::CidFontRevision(stack.pop_fixed()?),
CidFontType => Entry::CidFontType(stack.pop_i32()?),
CidCount => Entry::CidCount(stack.pop_i32()? as u32),
UidBase => Entry::UidBase(stack.pop_i32()?),
FontName => Entry::FontName(stack.pop_i32()?.into()),
StdHw => Entry::StdHw(stack.pop_fixed()?),
StdVw => Entry::StdVw(stack.pop_fixed()?),
DefaultWidthX => Entry::DefaultWidthX(stack.pop_fixed()?),
NominalWidthX => Entry::NominalWidthX(stack.pop_fixed()?),
StemSnapH => {
stack.apply_delta_prefix_sum();
Entry::StemSnapH(StemSnaps::new(stack.fixed_values()))
}
StemSnapV => {
stack.apply_delta_prefix_sum();
Entry::StemSnapV(StemSnaps::new(stack.fixed_values()))
}
ForceBold => Entry::ForceBold(stack.pop_i32()? != 0),
InitialRandomSeed => Entry::InitialRandomSeed(stack.pop_i32()?),
// Blend is handled at the layer above
Blend => unreachable!(),
})
}
/// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psblues.h#L141>
const MAX_BLUE_VALUES: usize = 7;
/// Operand for the `BlueValues`, `OtherBlues`, `FamilyBlues` and
/// `FamilyOtherBlues` operators.
///
/// These are used to generate zones when applying hints.
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub struct Blues {
values: [(Fixed, Fixed); MAX_BLUE_VALUES],
len: u32,
}
impl Blues {
pub fn new(values: impl Iterator<Item = Fixed>) -> Self {
let mut blues = Self::default();
let mut stash = Fixed::ZERO;
for (i, value) in values.take(MAX_BLUE_VALUES * 2).enumerate() {
if (i & 1) == 0 {
stash = value;
} else {
blues.values[i / 2] = (stash, value);
blues.len += 1;
}
}
blues
}
pub fn values(&self) -> &[(Fixed, Fixed)] {
&self.values[..self.len as usize]
}
}
/// Summary: older PostScript interpreters accept two values, but newer ones
/// accept 12. We'll assume that as maximum.
/// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5049.StemSnap.pdf>
const MAX_STEM_SNAPS: usize = 12;
/// Operand for the `StemSnapH` and `StemSnapV` operators.
///
/// These are used for stem darkening when applying hints.
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub struct StemSnaps {
values: [Fixed; MAX_STEM_SNAPS],
len: u32,
}
impl StemSnaps {
fn new(values: impl Iterator<Item = Fixed>) -> Self {
let mut snaps = Self::default();
for (value, target_value) in values.take(MAX_STEM_SNAPS).zip(&mut snaps.values) {
*target_value = value;
snaps.len += 1;
}
snaps
}
pub fn values(&self) -> &[Fixed] {
&self.values[..self.len as usize]
}
}
pub(crate) fn parse_int(cursor: &mut Cursor, b0: u8) -> Result<i32, Error> {
// Size b0 range Value range Value calculation
//--------------------------------------------------------------------------------
// 1 32 to 246 -107 to +107 b0 - 139
// 2 247 to 250 +108 to +1131 (b0 - 247) * 256 + b1 + 108
// 2 251 to 254 -1131 to -108 -(b0 - 251) * 256 - b1 - 108
// 3 28 -32768 to +32767 b1 << 8 | b2
// 5 29 -(2^31) to +(2^31 - 1) b1 << 24 | b2 << 16 | b3 << 8 | b4
// <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-3-operand-encoding>
Ok(match b0 {
32..=246 => b0 as i32 - 139,
247..=250 => (b0 as i32 - 247) * 256 + cursor.read::<u8>()? as i32 + 108,
251..=254 => -(b0 as i32 - 251) * 256 - cursor.read::<u8>()? as i32 - 108,
28 => cursor.read::<i16>()? as i32,
29 => cursor.read::<i32>()?,
_ => {
return Err(Error::InvalidNumber);
}
})
}
/// Parse a binary coded decimal number.
/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/82090e67c24259c343c83fd9cefe6ff0be7a7eca/src/cff/cffparse.c#L183>
fn parse_bcd(cursor: &mut Cursor) -> Result<Fixed, Error> {
// Value returned on overflow
const OVERFLOW: Fixed = Fixed::from_bits(0x7FFFFFFF);
// Value returned on underflow
const UNDERFLOW: Fixed = Fixed::ZERO;
// Limit at which we stop accumulating `number` and increase
// the exponent instead
const NUMBER_LIMIT: i32 = 0xCCCCCCC;
// Limit for the integral part of the result
const INTEGER_LIMIT: i32 = 0x7FFF;
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/82090e67c24259c343c83fd9cefe6ff0be7a7eca/src/cff/cffparse.c#L150>
const POWER_TENS: [i32; 10] = [
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
];
enum Phase {
Integer,
Fraction,
Exponent,
}
let mut phase = Phase::Integer;
let mut sign = 1i32;
let mut exponent_sign = 1i32;
let mut number = 0i32;
let mut exponent = 0i32;
let mut exponent_add = 0i32;
let mut integer_len = 0;
let mut fraction_len = 0;
// Nibble value Represents
//----------------------------------
// 0 to 9 0 to 9
// a . (decimal point)
// b E
// c E-
// d <reserved>
// e - (minus)
// f end of number
// <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>
'outer: loop {
let b = cursor.read::<u8>()?;
for nibble in [(b >> 4) & 0xF, b & 0xF] {
match phase {
Phase::Integer => match nibble {
0x0..=0x9 => {
if number >= NUMBER_LIMIT {
exponent_add += 1;
} else if nibble != 0 || number != 0 {
number = number * 10 + nibble as i32;
integer_len += 1;
}
}
0xE => sign = -1,
0xA => {
phase = Phase::Fraction;
}
0xB => {
phase = Phase::Exponent;
}
0xC => {
phase = Phase::Exponent;
exponent_sign = -1;
}
_ => break 'outer,
},
Phase::Fraction => match nibble {
0x0..=0x9 => {
if nibble == 0 && number == 0 {
exponent_add -= 1;
} else if number < NUMBER_LIMIT && fraction_len < 9 {
number = number * 10 + nibble as i32;
fraction_len += 1;
}
}
0xB => {
phase = Phase::Exponent;
}
0xC => {
phase = Phase::Exponent;
exponent_sign = -1;
}
_ => break 'outer,
},
Phase::Exponent => {
match nibble {
0x0..=0x9 => {
// Arbitrarily limit exponent
if exponent > 1000 {
return if exponent_sign == -1 {
Ok(UNDERFLOW)
} else {
Ok(OVERFLOW)
};
} else {
exponent = exponent * 10 + nibble as i32;
}
}
_ => break 'outer,
}
}
}
}
}
if number == 0 {
return Ok(Fixed::ZERO);
}
exponent *= exponent_sign;
exponent += exponent_add;
integer_len += exponent;
fraction_len -= exponent;
if integer_len > 5 {
return Ok(OVERFLOW);
}
if integer_len < -5 {
return Ok(UNDERFLOW);
}
// Remove non-significant digits
if integer_len < 0 {
number /= POWER_TENS[(-integer_len) as usize];
fraction_len += integer_len;
}
// Can only happen if exponent was non-zero
if fraction_len == 10 {
number /= 10;
fraction_len -= 1;
}
// Convert to fixed
let result = if fraction_len > 0 {
let b = POWER_TENS[fraction_len as usize];
if number / b > INTEGER_LIMIT {
0
} else {
(Fixed::from_bits(number) / Fixed::from_bits(b)).to_bits()
}
} else {
number = number.wrapping_mul(-fraction_len);
if number > INTEGER_LIMIT {
return Ok(OVERFLOW);
} else {
number << 16
}
};
Ok(Fixed::from_bits(result * sign))
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
use crate::{
tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead, FontRef,
TableProvider,
};
#[test]
fn int_operands() {
// Test the boundary conditions of the ranged int operators
let empty = FontData::new(&[]);
let min_byte = FontData::new(&[0]);
let max_byte = FontData::new(&[255]);
// 32..=246 => -107..=107
assert_eq!(parse_int(&mut empty.cursor(), 32).unwrap(), -107);
assert_eq!(parse_int(&mut empty.cursor(), 246).unwrap(), 107);
// 247..=250 => +108 to +1131
assert_eq!(parse_int(&mut min_byte.cursor(), 247).unwrap(), 108);
assert_eq!(parse_int(&mut max_byte.cursor(), 250).unwrap(), 1131);
// 251..=254 => -1131 to -108
assert_eq!(parse_int(&mut min_byte.cursor(), 251).unwrap(), -108);
assert_eq!(parse_int(&mut max_byte.cursor(), 254).unwrap(), -1131);
}
#[test]
fn binary_coded_decimal_operands() {
// From <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>:
//
// "A real number is terminated by one (or two) 0xf nibbles so that it is always padded
// to a full byte. Thus, the value -2.25 is encoded by the byte sequence (1e e2 a2 5f)
// and the value 0.140541E-3 by the sequence (1e 0a 14 05 41 c3 ff)."
//
// The initial 1e byte in the examples above is the dictionary operator to trigger
// parsing of BCD so it is dropped in the tests here.
let bytes = FontData::new(&[0xe2, 0xa2, 0x5f]);
assert_eq!(
parse_bcd(&mut bytes.cursor()).unwrap(),
Fixed::from_f64(-2.25)
);
let bytes = FontData::new(&[0x0a, 0x14, 0x05, 0x41, 0xc3, 0xff]);
assert_eq!(
parse_bcd(&mut bytes.cursor()).unwrap(),
Fixed::from_f64(0.140541E-3)
);
// Check that we match FreeType for 375e-4.
// Note: we used to parse 0.0375... but the new FT matching code
// has less precision
let bytes = FontData::new(&[0x37, 0x5c, 0x4f]);
assert_eq!(
parse_bcd(&mut bytes.cursor()).unwrap(),
Fixed::from_f64(0.0370025634765625)
);
}
#[test]
fn example_top_dict_tokens() {
use Operator::*;
let top_dict_data = &font_test_data::cff2::EXAMPLE[5..12];
let tokens: Vec<_> = tokens(top_dict_data).map(|entry| entry.unwrap()).collect();
let expected: &[Token] = &[
68.into(),
FdArrayOffset.into(),
56.into(),
CharstringsOffset.into(),
16.into(),
VariationStoreOffset.into(),
];
assert_eq!(&tokens, expected);
}
#[test]
fn example_top_dict_entries() {
use Entry::*;
let top_dict_data = &font_test_data::cff2::EXAMPLE[0x5..=0xB];
let entries: Vec<_> = entries(top_dict_data, None)
.map(|entry| entry.unwrap())
.collect();
let expected: &[Entry] = &[
FdArrayOffset(68),
CharstringsOffset(56),
VariationStoreOffset(16),
];
assert_eq!(&entries, expected);
}
#[test]
fn example_private_dict_entries() {
use Entry::*;
let private_dict_data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
let store =
ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
let coords = &[F2Dot14::from_f32(0.0)];
let blend_state = BlendState::new(store, coords, 0).unwrap();
let entries: Vec<_> = entries(private_dict_data, Some(blend_state))
.map(|entry| entry.unwrap())
.collect();
fn make_blues(values: &[f64]) -> Blues {
Blues::new(values.iter().copied().map(Fixed::from_f64))
}
fn make_stem_snaps(values: &[f64]) -> StemSnaps {
StemSnaps::new(values.iter().copied().map(Fixed::from_f64))
}
let expected: &[Entry] = &[
BlueValues(make_blues(&[
-20.0, 0.0, 472.0, 490.0, 525.0, 540.0, 645.0, 660.0, 670.0, 690.0, 730.0, 750.0,
])),
OtherBlues(make_blues(&[-250.0, -240.0])),
FamilyBlues(make_blues(&[
-20.0, 0.0, 473.0, 491.0, 525.0, 540.0, 644.0, 659.0, 669.0, 689.0, 729.0, 749.0,
])),
FamilyOtherBlues(make_blues(&[-249.0, -239.0])),
BlueScale(Fixed::from_f64(0.0370025634765625)),
BlueFuzz(Fixed::ZERO),
StdHw(Fixed::from_f64(55.0)),
StdVw(Fixed::from_f64(80.0)),
StemSnapH(make_stem_snaps(&[40.0, 55.0])),
StemSnapV(make_stem_snaps(&[80.0, 90.0])),
SubrsOffset(114),
];
assert_eq!(&entries, expected);
}
#[test]
fn noto_serif_display_top_dict_entries() {
use Entry::*;
let top_dict_data = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED)
.unwrap()
.cff()
.unwrap()
.top_dicts()
.get(0)
.unwrap();
let entries: Vec<_> = entries(top_dict_data, None)
.map(|entry| entry.unwrap())
.collect();
let expected = &[
Version(StringId::new(391)),
Notice(StringId::new(392)),
Copyright(StringId::new(393)),
FullName(StringId::new(394)),
FamilyName(StringId::new(395)),
FontBbox([-693.0, -470.0, 2797.0, 1048.0].map(Fixed::from_f64)),
Charset(517),
PrivateDictRange(549..587),
CharstringsOffset(521),
];
assert_eq!(&entries, expected);
}
// Fuzzer caught add with overflow when constructing private DICT
// range.
// See <https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=71746>
// and <https://oss-fuzz.com/testcase?key=4591358306746368>
#[test]
fn private_dict_range_avoid_overflow() {
// A Private DICT that tries to construct a range from -1..(-1 + -1)
// which overflows when converted to usize
let private_dict = BeBuffer::new()
.push(29u8) // integer operator
.push(-1i32) // integer value
.push(29u8) // integer operator
.push(-1i32) // integer value
.push(18u8) // PrivateDICT operator
.to_vec();
// Just don't panic
let _ = entries(&private_dict, None).count();
}
}

View File

@@ -0,0 +1,110 @@
//! Parsing for CFF FDSelect tables.
use types::GlyphId;
use super::FdSelect;
impl FdSelect<'_> {
/// Returns the associated font DICT index for the given glyph identifier.
pub fn font_index(&self, glyph_id: GlyphId) -> Option<u16> {
match self {
// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-11-fdselect-format-0>
Self::Format0(fds) => fds
.fds()
.get(glyph_id.to_u32() as usize)
.map(|fd| *fd as u16),
// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-12-fdselect-format-3>
Self::Format3(fds) => {
let ranges = fds.ranges();
let gid = glyph_id.to_u32();
let ix = match ranges.binary_search_by(|range| (range.first() as u32).cmp(&gid)) {
Ok(ix) => ix,
Err(ix) => ix.saturating_sub(1),
};
Some(ranges.get(ix)?.fd() as u16)
}
// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-14-fdselect-format-4>
Self::Format4(fds) => {
let ranges = fds.ranges();
let gid = glyph_id.to_u32();
let ix = match ranges.binary_search_by(|range| range.first().cmp(&gid)) {
Ok(ix) => ix,
Err(ix) => ix.saturating_sub(1),
};
Some(ranges.get(ix)?.fd())
}
}
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::{FdSelect, GlyphId};
use crate::FontRead;
use std::ops::Range;
#[test]
fn select_font_index() {
let map = &[
(0..10, 0),
(10..32, 4),
(32..34, 1),
(34..128, 12),
(128..1024, 2),
];
for data in make_fd_selects(map) {
let fd_select = FdSelect::read(data.data().into()).unwrap();
for (range, font_index) in map {
for gid in range.clone() {
assert_eq!(
fd_select.font_index(GlyphId::from(gid)).unwrap() as u8,
*font_index
)
}
}
}
}
/// Builds FDSelect structures in all three formats for the given
/// Range<GID> -> font index mapping.
fn make_fd_selects(map: &[(Range<u16>, u8)]) -> [BeBuffer; 3] {
let glyph_count = map.last().unwrap().0.end;
let format0 = {
let mut buf = BeBuffer::new();
buf = buf.push(0u8);
let mut fds = vec![0u8; glyph_count as usize];
for (range, font_index) in map {
for gid in range.clone() {
fds[gid as usize] = *font_index;
}
}
buf = buf.extend(fds);
buf
};
let format3 = {
let mut buf = BeBuffer::new();
buf = buf.push(3u8);
buf = buf.push(map.len() as u16);
for (range, font_index) in map {
buf = buf.push(range.start);
buf = buf.push(*font_index);
}
buf = buf.push(glyph_count);
buf
};
let format4 = {
let mut buf = BeBuffer::new();
buf = buf.push(4u8);
buf = buf.push(map.len() as u32);
for (range, font_index) in map {
buf = buf.push(range.start as u32);
buf = buf.push(*font_index as u16);
}
buf = buf.push(glyph_count as u32);
buf
};
[format0, format3, format4]
}
}

View File

@@ -0,0 +1,340 @@
//! Parsing for PostScript INDEX objects.
//!
//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>
use super::{Error, Index1, Index2};
use crate::codegen_prelude::*;
/// Common type for uniform access to CFF and CFF2 index formats.
#[derive(Clone)]
pub enum Index<'a> {
Empty,
Format1(Index1<'a>),
Format2(Index2<'a>),
}
impl<'a> Index<'a> {
/// Creates a new index from the given data.
///
/// The caller must specify whether the data comes from a `CFF2` table.
pub fn new(data: &'a [u8], is_cff2: bool) -> Result<Self, Error> {
let data = FontData::new(data);
Ok(if is_cff2 {
Index2::read(data).map(|ix| ix.into())?
} else {
Index1::read(data).map(|ix| ix.into())?
})
}
/// Returns the number of objects in the index.
pub fn count(&self) -> u32 {
match self {
Self::Empty => 0,
Self::Format1(ix) => ix.count() as u32,
Self::Format2(ix) => ix.count(),
}
}
/// Computes a bias that is added to a subroutine operator in a
/// charstring.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>
pub fn subr_bias(&self) -> i32 {
let count = self.count();
if count < 1240 {
107
} else if count < 33900 {
1131
} else {
32768
}
}
/// Returns the total size in bytes of the index table.
pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
match self {
Self::Empty => Ok(0),
Self::Format1(ix) => ix.size_in_bytes(),
Self::Format2(ix) => ix.size_in_bytes(),
}
}
/// Returns the offset at the given index.
pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
match self {
Self::Empty => Err(ReadError::OutOfBounds.into()),
Self::Format1(ix) => ix.get_offset(index),
Self::Format2(ix) => ix.get_offset(index),
}
}
/// Returns the data for the object at the given index.
pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
match self {
Self::Empty => Err(ReadError::OutOfBounds.into()),
Self::Format1(ix) => ix.get(index),
Self::Format2(ix) => ix.get(index),
}
}
pub fn off_size(&self) -> u8 {
match self {
Self::Empty => 0,
Self::Format1(ix) => ix.off_size(),
Self::Format2(ix) => ix.off_size(),
}
}
}
impl<'a> From<Index1<'a>> for Index<'a> {
fn from(value: Index1<'a>) -> Self {
Self::Format1(value)
}
}
impl<'a> From<Index2<'a>> for Index<'a> {
fn from(value: Index2<'a>) -> Self {
Self::Format2(value)
}
}
impl Default for Index<'_> {
fn default() -> Self {
Self::Empty
}
}
impl<'a> Index1<'a> {
/// Returns the total size in bytes of the index table.
pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
// 2 byte count + 1 byte off_size
const HEADER_SIZE: usize = 3;
// An empty CFF index contains only a 2 byte count field
const EMPTY_SIZE: usize = 2;
let count = self.count() as usize;
Ok(match count {
0 => EMPTY_SIZE,
_ => {
HEADER_SIZE
+ self.offsets().len()
+ self.get_offset(count).map_err(|_| ReadError::OutOfBounds)?
}
})
}
/// Returns the offset of the object at the given index.
pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
read_offset(
index,
self.count() as usize,
self.off_size(),
self.offsets(),
)
}
/// Returns the data for the object at the given index.
pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
self.data()
.get(self.get_offset(index)?..self.get_offset(index + 1)?)
.ok_or(ReadError::OutOfBounds.into())
}
}
impl<'a> Index2<'a> {
/// Returns the total size in bytes of the index table.
pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
// 4 byte count + 1 byte off_size
const HEADER_SIZE: usize = 5;
// An empty CFF2 index contains only a 4 byte count field
const EMPTY_SIZE: usize = 4;
let count = self.count() as usize;
Ok(match count {
0 => EMPTY_SIZE,
_ => {
HEADER_SIZE
+ self.offsets().len()
+ self.get_offset(count).map_err(|_| ReadError::OutOfBounds)?
}
})
}
/// Returns the offset of the object at the given index.
pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
read_offset(
index,
self.count() as usize,
self.off_size(),
self.offsets(),
)
}
/// Returns the data for the object at the given index.
pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
self.data()
.get(self.get_offset(index)?..self.get_offset(index + 1)?)
.ok_or(ReadError::OutOfBounds.into())
}
}
/// Reads an offset which is encoded as a variable sized integer.
fn read_offset(
index: usize,
count: usize,
offset_size: u8,
offset_data: &[u8],
) -> Result<usize, Error> {
// There are actually count + 1 entries in the offset array.
//
// "Offsets in the offset array are relative to the byte that precedes
// the object data. Therefore the first element of the offset array is
// always 1. (This ensures that every object has a corresponding offset
// which is always nonzero and permits the efficient implementation of
// dynamic object loading.)"
//
// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-7-index-format>
if index > count {
Err(ReadError::OutOfBounds)?;
}
let data_offset = index * offset_size as usize;
let offset_data = FontData::new(offset_data);
match offset_size {
1 => offset_data.read_at::<u8>(data_offset)? as usize,
2 => offset_data.read_at::<u16>(data_offset)? as usize,
3 => offset_data.read_at::<Uint24>(data_offset)?.to_u32() as usize,
4 => offset_data.read_at::<u32>(data_offset)? as usize,
_ => return Err(Error::InvalidIndexOffsetSize(offset_size)),
}
// As above, subtract one to get the actual offset.
.checked_sub(1)
.ok_or(Error::ZeroOffsetInIndex)
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
enum IndexParams {
Format1 { off_size: u8, count: usize },
Format2 { off_size: u8, count: usize },
}
#[test]
fn index_format1_offsize1_count4() {
test_index(IndexParams::Format1 {
off_size: 1,
count: 4,
});
}
#[test]
fn index_format1_offsize2_count64() {
test_index(IndexParams::Format1 {
off_size: 2,
count: 64,
});
}
#[test]
fn index_format1_offsize3_count128() {
test_index(IndexParams::Format1 {
off_size: 3,
count: 128,
});
}
#[test]
fn index_format1_offsize4_count256() {
test_index(IndexParams::Format1 {
off_size: 4,
count: 256,
});
}
#[test]
fn index_format2_offsize1_count4() {
test_index(IndexParams::Format2 {
off_size: 4,
count: 256,
});
}
#[test]
fn index_format2_offsize2_count64() {
test_index(IndexParams::Format2 {
off_size: 2,
count: 64,
});
}
#[test]
fn index_format2_offsize3_count128() {
test_index(IndexParams::Format2 {
off_size: 3,
count: 128,
});
}
#[test]
fn index_format2_offsize4_count256() {
test_index(IndexParams::Format2 {
off_size: 4,
count: 256,
});
}
fn test_index(params: IndexParams) {
let (fmt, off_size, count) = match params {
IndexParams::Format1 { off_size, count } => (1, off_size, count),
IndexParams::Format2 { off_size, count } => (2, off_size, count),
};
let buf = make_index(fmt, off_size, count);
let index = Index::new(buf.data(), fmt == 2).unwrap();
let built_off_size = match &index {
Index::Empty => 0,
Index::Format1(v1) => v1.off_size(),
Index::Format2(v2) => v2.off_size(),
};
assert_eq!(built_off_size, off_size);
assert_eq!(index.count(), count as u32);
for i in 0..count {
let object = index.get(i).unwrap();
let expected_len = (i + 1) * 10;
let expected_bytes = vec![i as u8; expected_len];
assert_eq!(object, expected_bytes);
}
}
fn make_index(fmt: u8, off_size: u8, count: usize) -> BeBuffer {
// We'll add `count` objects to the INDEX, each containing
// `(i + 1) * 10` bytes of the value `i`.
let mut buf = BeBuffer::new();
match fmt {
1 => buf = buf.push(count as u16),
2 => buf = buf.push(count as u32),
_ => panic!("INDEX fmt should be 1 or 2"),
}
if count == 0 {
return buf;
}
buf = buf.push(off_size);
// Offsets start at 1.
let mut offset = 1usize;
for i in 0..count + 1 {
buf = match off_size {
1 => buf.push(offset as u8),
2 => buf.push(offset as u16),
3 => buf.push(Uint24::checked_new(offset as u32).unwrap()),
4 => buf.push(offset as u32),
_ => panic!("off_size should be 1-4"),
};
offset += (i + 1) * 10;
}
// Now the data
for i in 0..count {
buf = buf.extend(std::iter::repeat(i as u8).take((i + 1) * 10));
}
buf
}
}

View File

@@ -0,0 +1,461 @@
//! Operand stack for CFF/CFF2 parsing.
use types::Fixed;
use super::{BlendState, Error};
/// Maximum size of the operand stack.
///
/// "Operators in Top DICT, Font DICTs, Private DICTs and CharStrings may be
/// preceded by up to a maximum of 513 operands."
///
/// <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-9-top-dict-operator-entries>
const MAX_STACK: usize = 513;
/// Operand stack for DICTs and charstrings.
///
/// The operand stack can contain either 32-bit integers or 16.16 fixed point
/// values. The type is known when pushing to the stack and the expected type
/// is also known (based on the operator) when reading from the stack, so the
/// conversion is performed on demand at read time.
///
/// Storing the entries as an enum would require 8 bytes each and since these
/// objects are created on the _stack_, we reduce the required size by storing
/// the entries in parallel arrays holding the raw 32-bit value and a flag that
/// tracks which values are fixed point.
pub struct Stack {
values: [i32; MAX_STACK],
value_is_fixed: [bool; MAX_STACK],
top: usize,
}
impl Stack {
pub fn new() -> Self {
Self {
values: [0; MAX_STACK],
value_is_fixed: [false; MAX_STACK],
top: 0,
}
}
pub fn is_empty(&self) -> bool {
self.top == 0
}
pub fn len(&self) -> usize {
self.top
}
pub fn verify_exact_len(&self, len: usize) -> Result<(), Error> {
if self.top != len {
Err(Error::StackUnderflow)
} else {
Ok(())
}
}
pub fn verify_at_least_len(&self, len: usize) -> Result<(), Error> {
if self.top < len {
Err(Error::StackUnderflow)
} else {
Ok(())
}
}
/// Returns true if the number of elements on the stack is odd.
///
/// Used for processing some charstring operators where an odd
/// count represents the presence of the glyph advance width at the
/// bottom of the stack.
pub fn len_is_odd(&self) -> bool {
self.top & 1 != 0
}
pub fn clear(&mut self) {
self.top = 0;
}
/// Reverse the order of all elements on the stack.
///
/// Some charstring operators are simpler to process on a reversed
/// stack.
pub fn reverse(&mut self) {
self.values[..self.top].reverse();
self.value_is_fixed[..self.top].reverse();
}
pub fn push(&mut self, number: impl Into<Number>) -> Result<(), Error> {
match number.into() {
Number::I32(value) => self.push_impl(value, false),
Number::Fixed(value) => self.push_impl(value.to_bits(), true),
}
}
/// Returns the 32-bit integer at the given index on the stack.
///
/// Will return an error if the value at that index was not pushed as an
/// integer.
pub fn get_i32(&self, index: usize) -> Result<i32, Error> {
let value = *self
.values
.get(index)
.ok_or(Error::InvalidStackAccess(index))?;
if self.value_is_fixed[index] {
// FreeType returns an error here rather than converting
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psstack.c#L145>
Err(Error::ExpectedI32StackEntry(index))
} else {
Ok(value)
}
}
/// Returns the 16.16 fixed point value at the given index on the stack.
///
/// If the value was pushed as an integer, it will be automatically
/// converted to 16.16 fixed point.
pub fn get_fixed(&self, index: usize) -> Result<Fixed, Error> {
let value = *self
.values
.get(index)
.ok_or(Error::InvalidStackAccess(index))?;
Ok(if self.value_is_fixed[index] {
Fixed::from_bits(value)
} else {
Fixed::from_i32(value)
})
}
/// Pops a 32-bit integer from the top of stack.
///
/// Will return an error if the top value on the stack was not pushed as an
/// integer.
pub fn pop_i32(&mut self) -> Result<i32, Error> {
let i = self.pop()?;
self.get_i32(i)
}
/// Pops a 16.16 fixed point value from the top of the stack.
///
/// If the value was pushed as an integer, it will be automatically
/// converted to 16.16 fixed point.
pub fn pop_fixed(&mut self) -> Result<Fixed, Error> {
let i = self.pop()?;
self.get_fixed(i)
}
/// Returns an iterator yielding all elements on the stack
/// as 16.16 fixed point values.
///
/// Used to read array style DICT entries such as blue values,
/// font matrix and font bounding box.
pub fn fixed_values(&self) -> impl Iterator<Item = Fixed> + '_ {
self.values[..self.top]
.iter()
.zip(&self.value_is_fixed)
.map(|(value, is_real)| {
if *is_real {
Fixed::from_bits(*value)
} else {
Fixed::from_i32(*value)
}
})
}
/// Returns an array of `N` 16.16 fixed point values starting at
/// `first_index`.
pub fn fixed_array<const N: usize>(&self, first_index: usize) -> Result<[Fixed; N], Error> {
let mut result = [Fixed::ZERO; N];
if first_index >= self.top {
return Err(Error::InvalidStackAccess(first_index));
}
let end = first_index + N;
if end > self.top {
return Err(Error::InvalidStackAccess(end - 1));
}
let range = first_index..end;
for ((src, is_fixed), dest) in self.values[range.clone()]
.iter()
.zip(&self.value_is_fixed[range])
.zip(&mut result)
{
let value = if *is_fixed {
Fixed::from_bits(*src)
} else {
Fixed::from_i32(*src)
};
*dest = value;
}
Ok(result)
}
/// Returns an iterator yielding all elements on the stack as number
/// values.
///
/// This is useful for capturing the current state of the stack.
pub fn number_values(&self) -> impl Iterator<Item = Number> + '_ {
self.values[..self.top]
.iter()
.zip(&self.value_is_fixed)
.map(|(value, is_fixed)| Number::from_stack(*value, *is_fixed))
}
/// Apply a prefix sum to decode delta-encoded numbers.
///
/// "The second and subsequent numbers in a delta are encoded as the
/// difference between successive values."
///
/// Roughly equivalent to the FreeType logic at
/// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/cff/cffparse.c#L1431>
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-6-operand-types>
pub fn apply_delta_prefix_sum(&mut self) {
if self.top > 1 {
let mut sum = Fixed::ZERO;
for (value, is_fixed) in self.values[..self.top]
.iter_mut()
.zip(&mut self.value_is_fixed)
{
let fixed_value = if *is_fixed {
Fixed::from_bits(*value)
} else {
Fixed::from_i32(*value)
};
// See <https://github.com/googlefonts/fontations/issues/1193>
// The "DIN Alternate" font contains incorrect blue values
// that cause an overflow in this computation. FreeType does
// not use checked arithmetic so we need to explicitly use
// wrapping behavior to produce matching outlines.
sum = sum.wrapping_add(fixed_value);
*value = sum.to_bits();
*is_fixed = true;
}
}
}
/// Apply the `blend` operator.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#syntax-for-font-variations-support-operators>
#[inline(never)]
pub fn apply_blend(&mut self, blend_state: &BlendState) -> Result<(), Error> {
// When the blend operator is invoked, the stack will contain a set
// of target values, followed by sets of deltas for those values for
// each variation region, followed by the count of target values.
//
// For example, if we're blending two target values across three
// variation regions, the stack would be setup as follows (parentheses
// added to signify grouping of deltas):
//
// value_0 value_1 (delta_0_0 delta_0_1 delta_0_2) (delta_1_0 delta_1_1 delta_1_2) 2
//
// where delta_i_j represents the delta for value i and region j.
//
// We compute the scalars for each region, multiply them by the
// associated deltas and add the result to the respective target value.
// Then the stack is popped so only the final target values remain.
let target_value_count = self.pop_i32()? as usize;
if target_value_count > self.top {
return Err(Error::StackUnderflow);
}
let region_count = blend_state.region_count()?;
// We expect at least `target_value_count * (region_count + 1)`
// elements on the stack.
let operand_count = target_value_count * (region_count + 1);
if self.len() < operand_count {
return Err(Error::StackUnderflow);
}
// The stack may contain more elements than necessary, so keep track of
// our active range.
let start = self.len() - operand_count;
let end = start + operand_count;
// For simplicity, convert all elements to fixed up front.
for (value, is_fixed) in self.values[start..end]
.iter_mut()
.zip(&mut self.value_is_fixed[start..])
{
if !*is_fixed {
*value = Fixed::from_i32(*value).to_bits();
*is_fixed = true;
}
}
let (values, deltas) = self.values[start..].split_at_mut(target_value_count);
// Note: we specifically loop over scalars in the outer loop to avoid
// computing them more than once in the case that we overflow our
// precomputed cache.
for (region_ix, maybe_scalar) in blend_state.scalars()?.enumerate() {
let scalar = maybe_scalar?;
// We could omit these in `BlendState::scalars()` but that would
// significantly reduce the clarity of the already complex
// chained iterator code there. Do the simple thing here instead.
if scalar == Fixed::ZERO {
continue;
}
for (value_ix, value) in values.iter_mut().enumerate() {
let delta_ix = (region_count * value_ix) + region_ix;
let delta = Fixed::from_bits(deltas[delta_ix]);
*value = (Fixed::from_bits(*value).wrapping_add(delta * scalar)).to_bits();
}
}
self.top = start + target_value_count;
Ok(())
}
fn push_impl(&mut self, value: i32, is_fixed: bool) -> Result<(), Error> {
if self.top == MAX_STACK {
return Err(Error::StackOverflow);
}
self.values[self.top] = value;
self.value_is_fixed[self.top] = is_fixed;
self.top += 1;
Ok(())
}
fn pop(&mut self) -> Result<usize, Error> {
if self.top > 0 {
self.top -= 1;
Ok(self.top)
} else {
Err(Error::StackUnderflow)
}
}
}
impl Default for Stack {
fn default() -> Self {
Self::new()
}
}
/// Either a signed 32-bit integer or a 16.16 fixed point number.
///
/// This represents the CFF "number" operand type.
/// See "Table 6 Operand Types" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf>
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Number {
I32(i32),
Fixed(Fixed),
}
impl Number {
fn from_stack(raw: i32, is_fixed: bool) -> Self {
if is_fixed {
Self::Fixed(Fixed::from_bits(raw))
} else {
Self::I32(raw)
}
}
}
impl From<i32> for Number {
fn from(value: i32) -> Self {
Self::I32(value)
}
}
impl From<Fixed> for Number {
fn from(value: Fixed) -> Self {
Self::Fixed(value)
}
}
impl std::fmt::Display for Number {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::I32(value) => value.fmt(f),
Self::Fixed(value) => value.fmt(f),
}
}
}
#[cfg(test)]
mod tests {
use types::{F2Dot14, Fixed};
use super::Stack;
use crate::{
tables::{postscript::BlendState, variations::ItemVariationStore},
FontData, FontRead,
};
#[test]
fn push_pop() {
let mut stack = Stack::new();
stack.push(20).unwrap();
stack.push(Fixed::from_f64(42.42)).unwrap();
assert!(!stack.len_is_odd());
stack.verify_exact_len(2).unwrap();
stack.verify_at_least_len(2).unwrap();
assert_eq!(stack.pop_fixed().unwrap(), Fixed::from_f64(42.42));
assert_eq!(stack.pop_i32().unwrap(), 20);
}
#[test]
fn push_fixed_pop_i32() {
let mut stack = Stack::new();
stack.push(Fixed::from_f64(42.42)).unwrap();
assert!(stack.pop_i32().is_err());
}
#[test]
fn push_i32_pop_fixed() {
let mut stack = Stack::new();
stack.push(123).unwrap();
assert_eq!(stack.pop_fixed().unwrap(), Fixed::from_f64(123.0));
}
#[test]
fn reverse() {
let mut stack = Stack::new();
stack.push(Fixed::from_f64(1.5)).unwrap();
stack.push(42).unwrap();
stack.push(Fixed::from_f64(4.2)).unwrap();
stack.reverse();
assert_eq!(stack.pop_fixed().unwrap(), Fixed::from_f64(1.5));
assert_eq!(stack.pop_i32().unwrap(), 42);
assert_eq!(stack.pop_fixed().unwrap(), Fixed::from_f64(4.2));
}
#[test]
fn delta_prefix_sum() {
let mut stack = Stack::new();
stack.push(Fixed::from_f64(1.5)).unwrap();
stack.push(42).unwrap();
stack.push(Fixed::from_f64(4.2)).unwrap();
stack.apply_delta_prefix_sum();
assert!(stack.len_is_odd());
let values: Vec<_> = stack.fixed_values().collect();
let expected = &[
Fixed::from_f64(1.5),
Fixed::from_f64(43.5),
Fixed::from_f64(47.69999694824219),
];
assert_eq!(&values, expected);
}
#[test]
fn blend() {
let ivs_data = &font_test_data::cff2::EXAMPLE[18..];
let ivs = ItemVariationStore::read(FontData::new(ivs_data)).unwrap();
// This coordinate will generate scalars [0.5, 0.5]
let coords = &[F2Dot14::from_f32(-0.75)];
let blend_state = BlendState::new(ivs, coords, 0).unwrap();
let mut stack = Stack::new();
// Push our target values
stack.push(10).unwrap();
stack.push(20).unwrap();
// Push deltas for 2 regions for the first value
stack.push(4).unwrap();
stack.push(-8).unwrap();
// Push deltas for 2 regions for the second value
stack.push(-60).unwrap();
stack.push(2).unwrap();
// Push target value count
stack.push(2).unwrap();
stack.apply_blend(&blend_state).unwrap();
let result: Vec<_> = stack.fixed_values().collect();
// Expected values:
// 0: 10 + (4 * 0.5) + (-8 * 0.5) = 8
// 1: 20 + (-60 * 0.5) + (2 * 0.5) = -9
let expected = &[Fixed::from_f64(8.0), Fixed::from_f64(-9.0)];
assert_eq!(&result, expected);
}
}

View File

@@ -0,0 +1,519 @@
//! PostScript string identifiers.
/// PostScript string identifier (SID).
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct StringId(u16);
impl StringId {
/// Creates an identifier from a 16-bit unsigned integer.
pub const fn new(raw: u16) -> Self {
Self(raw)
}
/// Returns the underlying identifier as a 16-bit unsigned integer.
pub const fn to_u16(self) -> u16 {
self.0
}
/// Resolves the identifier as a standard string.
///
/// If the identifier represents a standard string, returns `Ok(string)`,
/// otherwise returns `Err(index)` with the index that should be used to
/// retrieve the string from the CFF string INDEX.
///
/// The standard string set is available in the section
/// "Appendix A - Standard Strings" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf>.
pub fn standard_string(self) -> Result<Latin1String<'static>, usize> {
let ix = self.0 as usize;
if let Some(string) = STANDARD_STRINGS.get(ix) {
// The standard strings are all ASCII so it's safe to interpret them
// as Latin-1. This is verified in a unit test.
Ok(Latin1String::new(string.as_bytes()))
} else {
Err(ix - STANDARD_STRINGS.len())
}
}
}
impl From<i32> for StringId {
fn from(value: i32) -> Self {
Self::new(value as u16)
}
}
/// Reference to a Latin-1 encoded string.
///
/// Strings stored in all PostScript defined fonts are usually ASCII but are
/// technically encoded in Latin-1. This type wraps the raw string data to
/// prevent attempts to decode as UTF-8.
///
/// This implements `PartialEq<&str>` to support easy comparison with UTF-8
/// strings.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Latin1String<'a> {
chars: &'a [u8],
}
impl<'a> Latin1String<'a> {
/// Creates a new Latin-1 encoded string reference from the given bytes,
/// with each representing a character.
pub const fn new(chars: &'a [u8]) -> Self {
Self { chars }
}
/// Returns an iterator over the characters of the string.
///
/// This simply converts each byte to `char`.
pub fn chars(&self) -> impl Iterator<Item = char> + Clone + 'a {
self.chars.iter().map(|b| *b as char)
}
/// Returns the raw bytes of the string.
pub fn bytes(&self) -> &'a [u8] {
self.chars
}
}
impl PartialEq<&str> for Latin1String<'_> {
fn eq(&self, other: &&str) -> bool {
self.chars().eq(other.chars())
}
}
impl std::fmt::Display for Latin1String<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for ch in self.chars() {
write!(f, "{}", ch)?;
}
Ok(())
}
}
/// The PostScript standard string set.
///
/// See "Appendix A - Standard Strings" in <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf>
pub const STANDARD_STRINGS: &[&str] = &[
".notdef",
"space",
"exclam",
"quotedbl",
"numbersign",
"dollar",
"percent",
"ampersand",
"quoteright",
"parenleft",
"parenright",
"asterisk",
"plus",
"comma",
"hyphen",
"period",
"slash",
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"colon",
"semicolon",
"less",
"equal",
"greater",
"question",
"at",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"bracketleft",
"backslash",
"bracketright",
"asciicircum",
"underscore",
"quoteleft",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"braceleft",
"bar",
"braceright",
"asciitilde",
"exclamdown",
"cent",
"sterling",
"fraction",
"yen",
"florin",
"section",
"currency",
"quotesingle",
"quotedblleft",
"guillemotleft",
"guilsinglleft",
"guilsinglright",
"fi",
"fl",
"endash",
"dagger",
"daggerdbl",
"periodcentered",
"paragraph",
"bullet",
"quotesinglbase",
"quotedblbase",
"quotedblright",
"guillemotright",
"ellipsis",
"perthousand",
"questiondown",
"grave",
"acute",
"circumflex",
"tilde",
"macron",
"breve",
"dotaccent",
"dieresis",
"ring",
"cedilla",
"hungarumlaut",
"ogonek",
"caron",
"emdash",
"AE",
"ordfeminine",
"Lslash",
"Oslash",
"OE",
"ordmasculine",
"ae",
"dotlessi",
"lslash",
"oslash",
"oe",
"germandbls",
"onesuperior",
"logicalnot",
"mu",
"trademark",
"Eth",
"onehalf",
"plusminus",
"Thorn",
"onequarter",
"divide",
"brokenbar",
"degree",
"thorn",
"threequarters",
"twosuperior",
"registered",
"minus",
"eth",
"multiply",
"threesuperior",
"copyright",
"Aacute",
"Acircumflex",
"Adieresis",
"Agrave",
"Aring",
"Atilde",
"Ccedilla",
"Eacute",
"Ecircumflex",
"Edieresis",
"Egrave",
"Iacute",
"Icircumflex",
"Idieresis",
"Igrave",
"Ntilde",
"Oacute",
"Ocircumflex",
"Odieresis",
"Ograve",
"Otilde",
"Scaron",
"Uacute",
"Ucircumflex",
"Udieresis",
"Ugrave",
"Yacute",
"Ydieresis",
"Zcaron",
"aacute",
"acircumflex",
"adieresis",
"agrave",
"aring",
"atilde",
"ccedilla",
"eacute",
"ecircumflex",
"edieresis",
"egrave",
"iacute",
"icircumflex",
"idieresis",
"igrave",
"ntilde",
"oacute",
"ocircumflex",
"odieresis",
"ograve",
"otilde",
"scaron",
"uacute",
"ucircumflex",
"udieresis",
"ugrave",
"yacute",
"ydieresis",
"zcaron",
"exclamsmall",
"Hungarumlautsmall",
"dollaroldstyle",
"dollarsuperior",
"ampersandsmall",
"Acutesmall",
"parenleftsuperior",
"parenrightsuperior",
"twodotenleader",
"onedotenleader",
"zerooldstyle",
"oneoldstyle",
"twooldstyle",
"threeoldstyle",
"fouroldstyle",
"fiveoldstyle",
"sixoldstyle",
"sevenoldstyle",
"eightoldstyle",
"nineoldstyle",
"commasuperior",
"threequartersemdash",
"periodsuperior",
"questionsmall",
"asuperior",
"bsuperior",
"centsuperior",
"dsuperior",
"esuperior",
"isuperior",
"lsuperior",
"msuperior",
"nsuperior",
"osuperior",
"rsuperior",
"ssuperior",
"tsuperior",
"ff",
"ffi",
"ffl",
"parenleftinferior",
"parenrightinferior",
"Circumflexsmall",
"hyphensuperior",
"Gravesmall",
"Asmall",
"Bsmall",
"Csmall",
"Dsmall",
"Esmall",
"Fsmall",
"Gsmall",
"Hsmall",
"Ismall",
"Jsmall",
"Ksmall",
"Lsmall",
"Msmall",
"Nsmall",
"Osmall",
"Psmall",
"Qsmall",
"Rsmall",
"Ssmall",
"Tsmall",
"Usmall",
"Vsmall",
"Wsmall",
"Xsmall",
"Ysmall",
"Zsmall",
"colonmonetary",
"onefitted",
"rupiah",
"Tildesmall",
"exclamdownsmall",
"centoldstyle",
"Lslashsmall",
"Scaronsmall",
"Zcaronsmall",
"Dieresissmall",
"Brevesmall",
"Caronsmall",
"Dotaccentsmall",
"Macronsmall",
"figuredash",
"hypheninferior",
"Ogoneksmall",
"Ringsmall",
"Cedillasmall",
"questiondownsmall",
"oneeighth",
"threeeighths",
"fiveeighths",
"seveneighths",
"onethird",
"twothirds",
"zerosuperior",
"foursuperior",
"fivesuperior",
"sixsuperior",
"sevensuperior",
"eightsuperior",
"ninesuperior",
"zeroinferior",
"oneinferior",
"twoinferior",
"threeinferior",
"fourinferior",
"fiveinferior",
"sixinferior",
"seveninferior",
"eightinferior",
"nineinferior",
"centinferior",
"dollarinferior",
"periodinferior",
"commainferior",
"Agravesmall",
"Aacutesmall",
"Acircumflexsmall",
"Atildesmall",
"Adieresissmall",
"Aringsmall",
"AEsmall",
"Ccedillasmall",
"Egravesmall",
"Eacutesmall",
"Ecircumflexsmall",
"Edieresissmall",
"Igravesmall",
"Iacutesmall",
"Icircumflexsmall",
"Idieresissmall",
"Ethsmall",
"Ntildesmall",
"Ogravesmall",
"Oacutesmall",
"Ocircumflexsmall",
"Otildesmall",
"Odieresissmall",
"OEsmall",
"Oslashsmall",
"Ugravesmall",
"Uacutesmall",
"Ucircumflexsmall",
"Udieresissmall",
"Yacutesmall",
"Thornsmall",
"Ydieresissmall",
"001.000",
"001.001",
"001.002",
"001.003",
"Black",
"Bold",
"Book",
"Light",
"Medium",
"Regular",
"Roman",
"Semibold",
];
#[cfg(test)]
mod tests {
use super::{Latin1String, StringId, STANDARD_STRINGS};
#[test]
fn lets_latin1() {
let latin1 = Latin1String::new(&[223, 214, 209, 208]);
let utf8 = "ßÖÑÐ";
assert_ne!(latin1.chars, utf8.as_bytes());
assert_eq!(latin1, utf8);
}
#[test]
fn standard_strings() {
for (i, &std_string) in STANDARD_STRINGS.iter().enumerate() {
let sid = StringId::new(i as _);
let latin1 = sid.standard_string().unwrap();
// Ensure we can compare directly with &str
assert_eq!(latin1, std_string);
// Ensure our to_string() conversion works (via the Display impl)
assert_eq!(latin1.to_string(), std_string);
}
}
#[test]
fn not_a_standard_string() {
let sid = StringId::new(STANDARD_STRINGS.len() as _);
assert!(sid.standard_string().is_err());
assert_eq!(sid.standard_string().unwrap_err(), 0);
}
}

47
vendor/read-fonts/src/tables/sbix.rs vendored Normal file
View File

@@ -0,0 +1,47 @@
//! The [sbix (Standard Bitmap Graphics)](https://docs.microsoft.com/en-us/typography/opentype/spec/sbix) table
include!("../../generated/generated_sbix.rs");
impl<'a> Strike<'a> {
pub fn glyph_data(&self, glyph_id: GlyphId) -> Result<Option<GlyphData<'a>>, ReadError> {
let offsets = self.glyph_data_offsets();
let start_ix = glyph_id.to_u32() as usize;
let start = offsets.get(start_ix).ok_or(ReadError::OutOfBounds)?.get() as usize;
let end = offsets
.get(start_ix + 1)
.ok_or(ReadError::OutOfBounds)?
.get() as usize;
if start == end {
// Empty glyphs are okay
return Ok(None);
}
let data = self
.offset_data()
.slice(start..end)
.ok_or(ReadError::OutOfBounds)?;
Ok(Some(GlyphData::read(data)?))
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use crate::tables::sbix::Sbix;
#[test]
fn sbix_strikes_count_overflow_table() {
// Contains an invalid `num_strikes` values which would move the cursor outside the able.
// See https://issues.chromium.org/issues/347835680 for the ClusterFuzz report.
// Failure only reproduces on 32-bit, for example, run with:
// cargo test --target=i686-unknown-linux-gnu "sbix_strikes_count_overflow_table"
let sbix = BeBuffer::new()
.push(1u16) // version
.push(0u16) // flags
.push(u32::MAX); // num_strikes
let table = Sbix::read(sbix.data().into(), 5);
// Must not panic with "attempt to multiply with overflow".
assert!(table.is_err());
}
}

69
vendor/read-fonts/src/tables/stat.rs vendored Normal file
View File

@@ -0,0 +1,69 @@
//! The [STAT](https://learn.microsoft.com/en-us/typography/opentype/spec/stat) table
include!("../../generated/generated_stat.rs");
impl AxisValue<'_> {
/// Returns axis value for format 1, 2 and 3 axis value tables
///
/// For axis value format 2, returns the nominal value.
pub fn value(&self) -> Option<Fixed> {
match self {
Self::Format1(item) => Some(item.value()),
Self::Format2(item) => Some(item.nominal_value()),
Self::Format3(item) => Some(item.value()),
Self::Format4(_) => None,
}
}
/// Returns linked value for format 3 axis value tables
pub fn linked_value(&self) -> Option<Fixed> {
match self {
Self::Format3(item) => Some(item.linked_value()),
_ => None,
}
}
/// Returns axis index for format 1, 2 and 3 axis value tables
pub fn axis_index(&self) -> Option<u16> {
match self {
Self::Format1(item) => Some(item.axis_index()),
Self::Format2(item) => Some(item.axis_index()),
Self::Format3(item) => Some(item.axis_index()),
Self::Format4(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use types::{Fixed, NameId};
use crate::{table_provider::TableProvider, FontRef};
use super::*;
#[test]
fn smoke_test() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let table = font.stat().unwrap();
assert_eq!(table.design_axis_count(), 1);
let axis_record = &table.design_axes().unwrap()[0];
assert_eq!(axis_record.axis_tag(), Tag::new(b"wght"));
assert_eq!(axis_record.axis_name_id(), NameId::new(257));
assert_eq!(axis_record.axis_ordering(), 0);
let axis_values = table.offset_to_axis_values().unwrap().unwrap();
let axis_values = axis_values
.axis_values()
.iter()
.map(|x| x.unwrap())
.collect::<Vec<_>>();
assert_eq!(axis_values.len(), 3);
let last = &axis_values[2];
if let AxisValue::Format1(table) = last {
assert_eq!(table.axis_index(), 0);
assert_eq!(table.value_name_id(), NameId::new(264));
assert_eq!(table.value(), Fixed::from_f64(700.0));
}
}
}

110
vendor/read-fonts/src/tables/svg.rs vendored Normal file
View File

@@ -0,0 +1,110 @@
//! The [SVG](https://learn.microsoft.com/en-us/typography/opentype/spec/svg) table
use core::cmp::Ordering;
include!("../../generated/generated_svg.rs");
impl<'a> Svg<'a> {
/// Get the raw data of the SVG document. Is not guaranteed to be valid and might be compressed.
pub fn glyph_data(&self, glyph_id: GlyphId) -> Result<Option<&'a [u8]>, ReadError> {
let document_list = self.svg_document_list()?;
let svg_document = document_list
.document_records()
.binary_search_by(|r| {
if r.start_glyph_id.get() > glyph_id {
Ordering::Greater
} else if r.end_glyph_id.get() < glyph_id {
Ordering::Less
} else {
Ordering::Equal
}
})
.ok()
.and_then(|index| document_list.document_records().get(index))
.and_then(|r| {
let all_data = document_list.data.as_bytes();
all_data.get(
r.svg_doc_offset.get() as usize
..(r.svg_doc_offset.get() + r.svg_doc_length.get()) as usize,
)
});
Ok(svg_document)
}
}
#[cfg(test)]
mod tests {
use font_test_data::bebuffer::BeBuffer;
use super::*;
#[test]
fn read_dummy_svg_file() {
let data: [u16; 32] = [
0, // Version
0, 10, // SVGDocumentListOffset
0, 0, // Reserved
// SVGDocumentList
3, // numEntries
// documentRecords
// Record 1
1, // startGlyphID
3, // endGlyphID
0, 38, // svgDocOffset
0, 10, // svgDocLength
// Record 2
6, // startGlyphID
7, // endGlyphID
0, 48, // svgDocOffset
0, 6, // svgDocLength
// Record 3
9, // startGlyphID
9, // endGlyphID
0, 38, // svgDocOffset
0, 10,
// svgDocLength
// SVG Documents. Not actual valid SVGs, but just dummy data.
1, 0, 0, 0, 1, // Document 1
2, 0, 0, // Document 2
];
let mut buf = BeBuffer::new();
buf = buf.extend(data);
let table = Svg::read(buf.data().into()).unwrap();
let first_document = &[0, 1, 0, 0, 0, 0, 0, 0, 0, 1][..];
let second_document = &[0, 2, 0, 0, 0, 0][..];
assert_eq!(table.glyph_data(GlyphId::new(0)).unwrap(), None);
assert_eq!(
table.glyph_data(GlyphId::new(1)).unwrap(),
Some(first_document)
);
assert_eq!(
table.glyph_data(GlyphId::new(2)).unwrap(),
Some(first_document)
);
assert_eq!(
table.glyph_data(GlyphId::new(3)).unwrap(),
Some(first_document)
);
assert_eq!(table.glyph_data(GlyphId::new(4)).unwrap(), None);
assert_eq!(table.glyph_data(GlyphId::new(5)).unwrap(), None);
assert_eq!(
table.glyph_data(GlyphId::new(6)).unwrap(),
Some(second_document)
);
assert_eq!(
table.glyph_data(GlyphId::new(7)).unwrap(),
Some(second_document)
);
assert_eq!(table.glyph_data(GlyphId::new(8)).unwrap(), None);
assert_eq!(
table.glyph_data(GlyphId::new(9)).unwrap(),
Some(first_document)
);
assert_eq!(table.glyph_data(GlyphId::new(10)).unwrap(), None);
}
}

170
vendor/read-fonts/src/tables/trak.rs vendored Normal file
View File

@@ -0,0 +1,170 @@
//! The [tracking (trak)](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html) table.
include!("../../generated/generated_trak.rs");
impl<'a> TrackData<'a> {
/// Returns the size table for this set of tracking data.
///
/// The `offset_data` parameter comes from the [`Trak`] table.
pub fn size_table(
&self,
offset_data: FontData<'a>,
) -> Result<&'a [BigEndian<Fixed>], ReadError> {
let mut cursor = offset_data
.split_off(self.size_table_offset() as usize)
.ok_or(ReadError::OutOfBounds)?
.cursor();
cursor.read_array(self.n_sizes() as usize)
}
}
impl TrackTableEntry {
/// Returns the list of per-size tracking values for this entry.
///
/// The `offset_data` parameter comes from the [`Trak`] table and `n_sizes`
/// parameter comes from the parent [`TrackData`] table.
pub fn per_size_values<'a>(
&self,
offset_data: FontData<'a>,
n_sizes: u16,
) -> Result<&'a [BigEndian<i16>], ReadError> {
let mut cursor = offset_data
.split_off(self.offset() as usize)
.ok_or(ReadError::OutOfBounds)?
.cursor();
cursor.read_array(n_sizes as usize)
}
}
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::bebuffer::BeBuffer;
#[test]
fn parse_header() {
let table_data = example_track_table();
let trak = Trak::read(FontData::new(&table_data)).unwrap();
assert_eq!(trak.version(), MajorMinor::VERSION_1_0);
let _ = trak.horiz().unwrap().unwrap();
assert!(trak.vert().is_none());
}
#[test]
fn parse_tracks() {
let table_data = example_track_table();
let trak = Trak::read(FontData::new(&table_data)).unwrap();
let horiz = trak.horiz().unwrap().unwrap();
let track_table = horiz.track_table();
let expected_tracks = [
(Fixed::from_i32(-1), NameId::new(256), 52),
(Fixed::from_i32(0), NameId::new(258), 60),
(Fixed::from_i32(1), NameId::new(257), 56),
];
let tracks = track_table
.iter()
.map(|track| (track.track(), track.name_index(), track.offset()))
.collect::<Vec<_>>();
assert_eq!(tracks, expected_tracks);
let expected_per_size_tracking_values = [[-15i16, -7], [0, 0], [50, 20]];
for (track, expected_values) in track_table.iter().zip(expected_per_size_tracking_values) {
let values = track
.per_size_values(trak.offset_data(), horiz.n_sizes())
.unwrap()
.iter()
.map(|v| v.get())
.collect::<Vec<_>>();
assert_eq!(values, expected_values);
}
}
#[test]
fn parse_per_size_values() {
let table_data = example_track_table();
let trak = Trak::read(FontData::new(&table_data)).unwrap();
let horiz = trak.horiz().unwrap().unwrap();
let track_table = horiz.track_table();
let expected_per_size_tracking_values = [[-15i16, -7], [0, 0], [50, 20]];
for (track, expected_values) in track_table.iter().zip(expected_per_size_tracking_values) {
let values = track
.per_size_values(trak.offset_data(), horiz.n_sizes())
.unwrap()
.iter()
.map(|v| v.get())
.collect::<Vec<_>>();
assert_eq!(values, expected_values);
}
}
#[test]
fn parse_sizes() {
let table_data = example_track_table();
let trak = Trak::read(FontData::new(&table_data)).unwrap();
let horiz = trak.horiz().unwrap().unwrap();
let size_table = horiz
.size_table(trak.offset_data())
.unwrap()
.iter()
.map(|v| v.get())
.collect::<Vec<_>>();
let expected_sizes = [Fixed::from_i32(12), Fixed::from_i32(24)];
assert_eq!(size_table, expected_sizes);
}
#[test]
fn insufficient_data() {
let mut table_data = example_track_table();
// drop the last byte from the final per size value entry
table_data.pop();
let trak = Trak::read(FontData::new(&table_data)).unwrap();
let horiz = trak.horiz().unwrap().unwrap();
let track_table = horiz.track_table();
// The values for the second track will fail to parse
let expected_per_size_tracking_values = [Some([-15i16, -7]), None, Some([50, 20])];
for (track, expected_values) in track_table.iter().zip(expected_per_size_tracking_values) {
let values = track
.per_size_values(trak.offset_data(), horiz.n_sizes())
.ok()
.map(|values| values.iter().map(|v| v.get()).collect::<Vec<_>>());
assert_eq!(
values,
expected_values.map(|value| value.into_iter().collect())
);
}
}
#[test]
fn bad_offset() {
let mut table_data = example_track_table();
// modify offset of first track table entry to be OOB
table_data[26] = 255;
let trak = Trak::read(FontData::new(&table_data)).unwrap();
let horiz = trak.horiz().unwrap().unwrap();
let track_table = horiz.track_table();
assert!(matches!(
track_table[0].per_size_values(trak.offset_data(), horiz.n_sizes()),
Err(ReadError::OutOfBounds)
));
}
/// From <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html>
fn example_track_table() -> Vec<u8> {
let mut buf = BeBuffer::new();
// header
buf = buf.push(MajorMinor::VERSION_1_0);
buf = buf.extend([0u16, 12, 0, 0]);
// TrackData
buf = buf.extend([3u16, 2]);
buf = buf.push(44u32);
// Three sorted TrackTableEntry records
buf = buf.push(0xFFFF0000u32).extend([256u16, 52]);
buf = buf.push(0x00000000u32).extend([258u16, 60]);
buf = buf.push(0x00010000u32).extend([257u16, 56]);
// Size subtable
buf = buf.push(0x000C0000u32);
buf = buf.push(0x00180000u32);
// Per-size tracking data values
buf = buf.extend([-15i16, -7, 50, 20, 0, 0]);
buf.to_vec()
}
}

View File

@@ -0,0 +1,247 @@
//! A GPOS ValueRecord
use font_types::Nullable;
use types::{BigEndian, FixedSize, Offset16};
use super::ValueFormat;
use crate::{tables::layout::DeviceOrVariationIndex, ResolveNullableOffset};
#[cfg(feature = "experimental_traverse")]
use crate::traversal::{Field, FieldType, RecordResolver, SomeRecord};
use crate::{ComputeSize, FontData, FontReadWithArgs, ReadArgs, ReadError};
impl ValueFormat {
/// A mask with all the device/variation index bits set
pub const ANY_DEVICE_OR_VARIDX: Self = ValueFormat {
bits: 0x0010 | 0x0020 | 0x0040 | 0x0080,
};
/// Return the number of bytes required to store a [`ValueRecord`] in this format.
#[inline]
pub fn record_byte_len(self) -> usize {
self.bits().count_ones() as usize * u16::RAW_BYTE_LEN
}
}
/// A Positioning ValueRecord.
///
/// NOTE: we create these manually, since parsing is weird and depends on the
/// associated valueformat. That said, this isn't a great representation?
/// we could definitely do something much more in the zero-copy mode..
#[derive(Clone, Default, Eq)]
pub struct ValueRecord {
pub x_placement: Option<BigEndian<i16>>,
pub y_placement: Option<BigEndian<i16>>,
pub x_advance: Option<BigEndian<i16>>,
pub y_advance: Option<BigEndian<i16>>,
pub x_placement_device: BigEndian<Nullable<Offset16>>,
pub y_placement_device: BigEndian<Nullable<Offset16>>,
pub x_advance_device: BigEndian<Nullable<Offset16>>,
pub y_advance_device: BigEndian<Nullable<Offset16>>,
#[doc(hidden)]
// exposed so that we can preserve format when we round-trip a value record
pub format: ValueFormat,
}
// we ignore the format for the purpose of equality testing, it's redundant
impl PartialEq for ValueRecord {
fn eq(&self, other: &Self) -> bool {
self.x_placement == other.x_placement
&& self.y_placement == other.y_placement
&& self.x_advance == other.x_advance
&& self.y_advance == other.y_advance
&& self.x_placement_device == other.x_placement_device
&& self.y_placement_device == other.y_placement_device
&& self.x_advance_device == other.x_advance_device
&& self.y_advance_device == other.y_advance_device
}
}
impl ValueRecord {
pub fn read(data: FontData, format: ValueFormat) -> Result<Self, ReadError> {
let mut this = ValueRecord {
format,
..Default::default()
};
let mut cursor = data.cursor();
if format.contains(ValueFormat::X_PLACEMENT) {
this.x_placement = Some(cursor.read_be()?);
}
if format.contains(ValueFormat::Y_PLACEMENT) {
this.y_placement = Some(cursor.read_be()?);
}
if format.contains(ValueFormat::X_ADVANCE) {
this.x_advance = Some(cursor.read_be()?);
}
if format.contains(ValueFormat::Y_ADVANCE) {
this.y_advance = Some(cursor.read_be()?);
}
if format.contains(ValueFormat::X_PLACEMENT_DEVICE) {
this.x_placement_device = cursor.read_be()?;
}
if format.contains(ValueFormat::Y_PLACEMENT_DEVICE) {
this.y_placement_device = cursor.read_be()?;
}
if format.contains(ValueFormat::X_ADVANCE_DEVICE) {
this.x_advance_device = cursor.read_be()?;
}
if format.contains(ValueFormat::Y_ADVANCE_DEVICE) {
this.y_advance_device = cursor.read_be()?;
}
Ok(this)
}
pub fn x_placement(&self) -> Option<i16> {
self.x_placement.map(|val| val.get())
}
pub fn y_placement(&self) -> Option<i16> {
self.y_placement.map(|val| val.get())
}
pub fn x_advance(&self) -> Option<i16> {
self.x_advance.map(|val| val.get())
}
pub fn y_advance(&self) -> Option<i16> {
self.y_advance.map(|val| val.get())
}
pub fn x_placement_device<'a>(
&self,
data: FontData<'a>,
) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
self.x_placement_device.get().resolve(data)
}
pub fn y_placement_device<'a>(
&self,
data: FontData<'a>,
) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
self.y_placement_device.get().resolve(data)
}
pub fn x_advance_device<'a>(
&self,
data: FontData<'a>,
) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
self.x_advance_device.get().resolve(data)
}
pub fn y_advance_device<'a>(
&self,
data: FontData<'a>,
) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
self.y_advance_device.get().resolve(data)
}
}
impl ReadArgs for ValueRecord {
type Args = ValueFormat;
}
impl<'a> FontReadWithArgs<'a> for ValueRecord {
fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
ValueRecord::read(data, *args)
}
}
impl std::fmt::Debug for ValueRecord {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut f = f.debug_struct("ValueRecord");
self.x_placement.map(|x| f.field("x_placement", &x));
self.y_placement.map(|y| f.field("y_placement", &y));
self.x_advance.map(|x| f.field("x_advance", &x));
self.y_advance.map(|y| f.field("y_advance", &y));
if !self.x_placement_device.get().is_null() {
f.field("x_placement_device", &self.x_placement_device.get());
}
if !self.y_placement_device.get().is_null() {
f.field("y_placement_device", &self.y_placement_device.get());
}
if !self.x_advance_device.get().is_null() {
f.field("x_advance_device", &self.x_advance_device.get());
}
if !self.y_advance_device.get().is_null() {
f.field("y_advance_device", &self.y_advance_device.get());
}
f.finish()
}
}
impl ComputeSize for ValueRecord {
#[inline]
fn compute_size(args: &ValueFormat) -> Result<usize, ReadError> {
Ok(args.record_byte_len())
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> ValueRecord {
pub(crate) fn traversal_type(&self, data: FontData<'a>) -> FieldType<'a> {
FieldType::Record(self.clone().traverse(data))
}
pub(crate) fn get_field(&self, idx: usize, data: FontData<'a>) -> Option<Field<'a>> {
let fields = [
self.x_placement.is_some().then_some("x_placement"),
self.y_placement.is_some().then_some("y_placement"),
self.x_advance.is_some().then_some("x_advance"),
self.y_advance.is_some().then_some("y_advance"),
(!self.x_placement_device.get().is_null()).then_some("x_placement_device"),
(!self.y_placement_device.get().is_null()).then_some("y_placement_device"),
(!self.x_advance_device.get().is_null()).then_some("x_advance_device"),
(!self.y_advance_device.get().is_null()).then_some("y_advance_device"),
];
let name = fields.iter().filter_map(|x| *x).nth(idx)?;
let typ: FieldType = match name {
"x_placement" => self.x_placement().unwrap().into(),
"y_placement" => self.y_placement().unwrap().into(),
"x_advance" => self.x_advance().unwrap().into(),
"y_advance" => self.y_advance().unwrap().into(),
"x_placement_device" => {
FieldType::offset(self.x_placement_device.get(), self.x_placement_device(data))
}
"y_placement_device" => {
FieldType::offset(self.y_placement_device.get(), self.y_placement_device(data))
}
"x_advance_device" => {
FieldType::offset(self.x_advance_device.get(), self.x_advance_device(data))
}
"y_advance_device" => {
FieldType::offset(self.y_advance_device.get(), self.y_advance_device(data))
}
_ => panic!("hmm"),
};
Some(Field::new(name, typ))
}
}
#[cfg(feature = "experimental_traverse")]
impl<'a> SomeRecord<'a> for ValueRecord {
fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
RecordResolver {
name: "ValueRecord",
data,
get_field: Box::new(move |idx, data| self.get_field(idx, data)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_check_format_const() {
let format = ValueFormat::X_ADVANCE_DEVICE
| ValueFormat::Y_ADVANCE_DEVICE
| ValueFormat::Y_PLACEMENT_DEVICE
| ValueFormat::X_PLACEMENT_DEVICE;
assert_eq!(format, ValueFormat::ANY_DEVICE_OR_VARIDX);
assert_eq!(format.record_byte_len(), 4 * 2);
}
}

700
vendor/read-fonts/src/tables/varc.rs vendored Normal file
View File

@@ -0,0 +1,700 @@
//! the [VARC (Variable Composite/Component)](https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md) table
use super::variations::PackedDeltas;
pub use super::{
layout::{Condition, CoverageTable},
postscript::Index2,
};
#[cfg(feature = "libm")]
#[allow(unused_imports)]
use core_maths::*;
include!("../../generated/generated_varc.rs");
/// Let's us call self.something().get(i) instead of get(self.something(), i)
trait Get<'a> {
fn get(self, nth: usize) -> Result<&'a [u8], ReadError>;
}
impl<'a> Get<'a> for Option<Result<Index2<'a>, ReadError>> {
fn get(self, nth: usize) -> Result<&'a [u8], ReadError> {
self.transpose()?
.ok_or(ReadError::NullOffset)
.and_then(|index| index.get(nth).map_err(|_| ReadError::OutOfBounds))
}
}
impl Varc<'_> {
/// Friendlier accessor than directly using raw data via [Index2]
pub fn axis_indices(&self, nth: usize) -> Result<PackedDeltas, ReadError> {
let raw = self.axis_indices_list().get(nth)?;
Ok(PackedDeltas::consume_all(raw.into()))
}
/// Friendlier accessor than directly using raw data via [Index2]
///
/// nth would typically be obtained by looking up a [GlyphId] in [Self::coverage].
pub fn glyph(&self, nth: usize) -> Result<VarcGlyph<'_>, ReadError> {
let raw = Some(self.var_composite_glyphs()).get(nth)?;
Ok(VarcGlyph {
table: self,
data: raw.into(),
})
}
}
/// A VARC glyph doesn't have any root level attributes, it's just a list of components
///
/// <https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-composite-description>
pub struct VarcGlyph<'a> {
table: &'a Varc<'a>,
data: FontData<'a>,
}
impl<'a> VarcGlyph<'a> {
/// <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/ttLib/tables/otTables.py#L404-L409>
pub fn components(&self) -> impl Iterator<Item = Result<VarcComponent<'a>, ReadError>> {
VarcComponentIter {
table: self.table,
cursor: self.data.cursor(),
}
}
}
struct VarcComponentIter<'a> {
table: &'a Varc<'a>,
cursor: Cursor<'a>,
}
impl<'a> Iterator for VarcComponentIter<'a> {
type Item = Result<VarcComponent<'a>, ReadError>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor.is_empty() {
return None;
}
Some(VarcComponent::parse(self.table, &mut self.cursor))
}
}
#[allow(dead_code)] // TEMPORARY
pub struct VarcComponent<'a> {
flags: VarcFlags,
gid: GlyphId,
condition_index: Option<u32>,
axis_indices_index: Option<u32>,
axis_values: Option<PackedDeltas<'a>>,
axis_values_var_index: Option<u32>,
transform_var_index: Option<u32>,
transform: DecomposedTransform,
}
impl<'a> VarcComponent<'a> {
/// Requires access to VARC fields to fully parse.
///
/// * HarfBuzz [VarComponent::get_path_at](https://github.com/harfbuzz/harfbuzz/blob/0c2f5ecd51d11e32836ee136a1bc765d650a4ec0/src/OT/Var/VARC/VARC.cc#L132)
// TODO: do we want to be able to parse into an existing glyph to avoid allocation?
fn parse(table: &Varc, cursor: &mut Cursor<'a>) -> Result<Self, ReadError> {
let raw_flags = cursor.read_u32_var()?;
let flags = VarcFlags::from_bits_truncate(raw_flags);
// Ref https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-component-record
// This is a GlyphID16 if GID_IS_24BIT bit of flags is clear, else GlyphID24.
let gid = if flags.contains(VarcFlags::GID_IS_24BIT) {
GlyphId::new(cursor.read::<Uint24>()?.to_u32())
} else {
GlyphId::from(cursor.read::<u16>()?)
};
let condition_index = if flags.contains(VarcFlags::HAVE_CONDITION) {
Some(cursor.read_u32_var()?)
} else {
None
};
let (axis_indices_index, axis_values) = if flags.contains(VarcFlags::HAVE_AXES) {
// <https://github.com/harfbuzz/harfbuzz/blob/0c2f5ecd51d11e32836ee136a1bc765d650a4ec0/src/OT/Var/VARC/VARC.cc#L195-L206>
let axis_indices_index = cursor.read_u32_var()?;
let num_axis_values = table.axis_indices(axis_indices_index as usize)?.count();
// we need to consume num_axis_values entries in packed delta format
let deltas = if num_axis_values > 0 {
let Some(data) = cursor.remaining() else {
return Err(ReadError::OutOfBounds);
};
let deltas = PackedDeltas::new(data, num_axis_values);
*cursor = deltas.iter().end(); // jump past the packed deltas
Some(deltas)
} else {
None
};
(Some(axis_indices_index), deltas)
} else {
(None, None)
};
let axis_values_var_index = flags
.contains(VarcFlags::AXIS_VALUES_HAVE_VARIATION)
.then(|| cursor.read_u32_var())
.transpose()?;
let transform_var_index = if flags.contains(VarcFlags::TRANSFORM_HAS_VARIATION) {
Some(cursor.read_u32_var()?)
} else {
None
};
let mut transform = DecomposedTransform::default();
if flags.contains(VarcFlags::HAVE_TRANSLATE_X) {
transform.translate_x = cursor.read::<FWord>()?.to_i16() as f64
}
if flags.contains(VarcFlags::HAVE_TRANSLATE_Y) {
transform.translate_y = cursor.read::<FWord>()?.to_i16() as f64
}
if flags.contains(VarcFlags::HAVE_ROTATION) {
transform.rotation = cursor.read::<F4Dot12>()?.to_f32() as f64
}
if flags.contains(VarcFlags::HAVE_SCALE_X) {
transform.scale_x = cursor.read::<F6Dot10>()?.to_f32() as f64
}
transform.scale_y = if flags.contains(VarcFlags::HAVE_SCALE_Y) {
cursor.read::<F6Dot10>()?.to_f32() as f64
} else {
transform.scale_x
};
if flags.contains(VarcFlags::HAVE_SKEW_X) {
transform.skew_x = cursor.read::<F4Dot12>()?.to_f32() as f64
}
if flags.contains(VarcFlags::HAVE_SKEW_Y) {
transform.skew_y = cursor.read::<F4Dot12>()?.to_f32() as f64
}
if flags.contains(VarcFlags::HAVE_TCENTER_X) {
transform.center_x = cursor.read::<FWord>()?.to_i16() as f64
}
if flags.contains(VarcFlags::HAVE_TCENTER_Y) {
transform.center_y = cursor.read::<FWord>()?.to_i16() as f64
}
// Optional, process and discard one uint32var per each set bit in RESERVED_MASK.
let num_reserved = (raw_flags & VarcFlags::RESERVED_MASK.bits).count_ones();
for _ in 0..num_reserved {
cursor.read_u32_var()?;
}
Ok(VarcComponent {
flags,
gid,
condition_index,
axis_indices_index,
axis_values,
axis_values_var_index,
transform_var_index,
transform,
})
}
}
/// <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/misc/transform.py#L410>
pub struct DecomposedTransform {
translate_x: f64,
translate_y: f64,
rotation: f64, // degrees, counter-clockwise
scale_x: f64,
scale_y: f64,
skew_x: f64,
skew_y: f64,
center_x: f64,
center_y: f64,
}
impl Default for DecomposedTransform {
fn default() -> Self {
Self {
translate_x: 0.0,
translate_y: 0.0,
rotation: 0.0,
scale_x: 1.0,
scale_y: 1.0,
skew_x: 0.0,
skew_y: 0.0,
center_x: 0.0,
center_y: 0.0,
}
}
}
impl DecomposedTransform {
/// Convert decomposed form to 2x3 matrix form.
///
/// The first two values are x,y x-basis vector,
/// the second 2 values are x,y y-basis vector, and the third 2 are translation.
///
/// In augmented matrix
/// form, if this method returns `[a, b, c, d, e, f]` that is taken as:
///
/// ```text
/// | a c e |
/// | b d f |
/// | 0 0 1 |
/// ```
///
/// References:
/// * FontTools Python implementation <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/misc/transform.py#L484-L500>
/// * Wikipedia [affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)
pub fn matrix(&self) -> [f64; 6] {
// Python: t.translate(self.translateX + self.tCenterX, self.translateY + self.tCenterY)
let mut transform = [
1.0,
0.0,
0.0,
1.0,
self.translate_x + self.center_x,
self.translate_y + self.center_y,
];
// TODO: this produces very small floats for rotations, e.g. 90 degree rotation a basic scale
// puts 1.2246467991473532e-16 into [0]. Should we special case? Round?
// Python: t = t.rotate(math.radians(self.rotation))
if self.rotation != 0.0 {
let (s, c) = (self.rotation).to_radians().sin_cos();
transform = transform.transform([c, s, -s, c, 0.0, 0.0]);
}
// Python: t = t.scale(self.scaleX, self.scaleY)
if (self.scale_x, self.scale_y) != (1.0, 1.0) {
transform = transform.transform([self.scale_x, 0.0, 0.0, self.scale_y, 0.0, 0.0]);
}
// Python: t = t.skew(math.radians(self.skewX), math.radians(self.skewY))
if (self.skew_x, self.skew_y) != (0.0, 0.0) {
transform = transform.transform([
1.0,
self.skew_y.to_radians().tan(),
self.skew_x.to_radians().tan(),
1.0,
0.0,
0.0,
])
}
// Python: t = t.translate(-self.tCenterX, -self.tCenterY)
if (self.center_x, self.center_y) != (0.0, 0.0) {
transform = transform.transform([1.0, 0.0, 0.0, 1.0, -self.center_x, -self.center_y]);
}
transform
}
}
trait Transform {
fn transform(self, other: Self) -> Self;
}
impl Transform for [f64; 6] {
fn transform(self, other: Self) -> Self {
// Shamelessly copied from kurbo Affine Mul
[
self[0] * other[0] + self[2] * other[1],
self[1] * other[0] + self[3] * other[1],
self[0] * other[2] + self[2] * other[3],
self[1] * other[2] + self[3] * other[3],
self[0] * other[4] + self[2] * other[5] + self[4],
self[1] * other[4] + self[3] * other[5] + self[5],
]
}
}
impl<'a> MultiItemVariationData<'a> {
/// An [Index2] where each item is a [PackedDeltas]
pub fn delta_sets(&self) -> Result<Index2<'a>, ReadError> {
Index2::read(self.raw_delta_sets().into())
}
/// Read a specific delta set.
///
/// Equivalent to calling [Self::delta_sets], fetching item i, and parsing as [PackedDeltas]
pub fn delta_set(&self, i: usize) -> Result<PackedDeltas<'a>, ReadError> {
let index = self.delta_sets()?;
let raw_deltas = index.get(i).map_err(|_| ReadError::OutOfBounds)?;
Ok(PackedDeltas::consume_all(raw_deltas.into()))
}
}
#[cfg(test)]
mod tests {
use types::GlyphId16;
use crate::{FontRef, ReadError, TableProvider};
use super::{Condition, DecomposedTransform, Varc};
impl Varc<'_> {
fn conditions(&self) -> impl Iterator<Item = Condition<'_>> {
self.condition_list()
.expect("A condition list is present")
.expect("We could read the condition list")
.conditions()
.iter()
.enumerate()
.map(|(i, c)| c.unwrap_or_else(|e| panic!("condition {i} {e}")))
}
fn axis_indices_count(&self) -> Result<usize, ReadError> {
let Some(axis_indices_list) = self.axis_indices_list() else {
return Ok(0);
};
let axis_indices_list = axis_indices_list?;
Ok(axis_indices_list.count() as usize)
}
}
fn round6(v: f64) -> f64 {
(v * 1_000_000.0).round() / 1_000_000.0
}
trait Round {
fn round_for_test(self) -> Self;
}
impl Round for [f64; 6] {
fn round_for_test(self) -> Self {
[
round6(self[0]),
round6(self[1]),
round6(self[2]),
round6(self[3]),
round6(self[4]),
round6(self[5]),
]
}
}
#[test]
fn read_cjk_0x6868() {
let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
let table = font.varc().unwrap();
table.coverage().unwrap(); // should have coverage
}
#[test]
fn identify_all_conditional_types() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
// We should have all 5 condition types in order
assert_eq!(
(1..=5).collect::<Vec<_>>(),
table.conditions().map(|c| c.format()).collect::<Vec<_>>()
);
}
#[test]
fn read_condition_format1_axis_range() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
let Some(Condition::Format1AxisRange(condition)) =
table.conditions().find(|c| c.format() == 1)
else {
panic!("No such item");
};
assert_eq!(
(0, 0.5, 1.0),
(
condition.axis_index(),
condition.filter_range_min_value().to_f32(),
condition.filter_range_max_value().to_f32(),
)
);
}
#[test]
fn read_condition_format2_variable_value() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
let Some(Condition::Format2VariableValue(condition)) =
table.conditions().find(|c| c.format() == 2)
else {
panic!("No such item");
};
assert_eq!((1, 2), (condition.default_value(), condition.var_index(),));
}
#[test]
fn read_condition_format3_and() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
let Some(Condition::Format3And(condition)) = table.conditions().find(|c| c.format() == 3)
else {
panic!("No such item");
};
// Should reference a format 1 and a format 2
assert_eq!(
vec![1, 2],
condition
.conditions()
.iter()
.map(|c| c.unwrap().format())
.collect::<Vec<_>>()
);
}
#[test]
fn read_condition_format4_or() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
let Some(Condition::Format4Or(condition)) = table.conditions().find(|c| c.format() == 4)
else {
panic!("No such item");
};
// Should reference a format 1 and a format 2
assert_eq!(
vec![1, 2],
condition
.conditions()
.iter()
.map(|c| c.unwrap().format())
.collect::<Vec<_>>()
);
}
#[test]
fn read_condition_format5_negate() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
let Some(Condition::Format5Negate(condition)) =
table.conditions().find(|c| c.format() == 5)
else {
panic!("No such item");
};
// Should reference a format 1
assert_eq!(1, condition.condition().unwrap().format(),);
}
#[test]
fn read_axis_indices_list() {
let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
let table = font.varc().unwrap();
assert_eq!(table.axis_indices_count().unwrap(), 2);
assert_eq!(
vec![2, 3, 4, 5, 6],
table.axis_indices(1).unwrap().iter().collect::<Vec<_>>()
);
}
#[test]
fn read_glyph_6868() {
let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
let gid = font.cmap().unwrap().map_codepoint(0x6868_u32).unwrap();
let table = font.varc().unwrap();
let idx = table.coverage().unwrap().get(gid).unwrap();
let glyph = table.glyph(idx as usize).unwrap();
assert_eq!(
vec![GlyphId16::new(2), GlyphId16::new(5), GlyphId16::new(7)],
glyph
.components()
.map(|c| c.unwrap().gid)
.collect::<Vec<_>>()
);
}
// Expected created using the Python DecomposedTransform
#[test]
fn decomposed_scale_to_matrix() {
let scale_x = 2.0;
let scale_y = 3.0;
assert_eq!(
[scale_x, 0.0, 0.0, scale_y, 0.0, 0.0],
DecomposedTransform {
scale_x,
scale_y,
..Default::default()
}
.matrix()
.round_for_test()
);
}
// Expected created using the Python DecomposedTransform
#[test]
fn decomposed_rotate_to_matrix() {
assert_eq!(
[0.0, 1.0, -1.0, 0.0, 0.0, 0.0],
DecomposedTransform {
rotation: 90.0,
..Default::default()
}
.matrix()
.round_for_test()
);
}
// Expected created using the Python DecomposedTransform
#[test]
fn decomposed_skew_to_matrix() {
let skew_x: f64 = 30.0;
let skew_y: f64 = -60.0;
assert_eq!(
[
1.0,
round6(skew_y.to_radians().tan()),
round6(skew_x.to_radians().tan()),
1.0,
0.0,
0.0
],
DecomposedTransform {
skew_x,
skew_y,
..Default::default()
}
.matrix()
.round_for_test()
);
}
// Expected created using the Python DecomposedTransform
#[test]
fn decomposed_scale_rotate_to_matrix() {
let scale_x = 2.0;
let scale_y = 3.0;
assert_eq!(
[0.0, scale_x, -scale_y, 0.0, 0.0, 0.0],
DecomposedTransform {
scale_x,
scale_y,
rotation: 90.0,
..Default::default()
}
.matrix()
.round_for_test()
);
}
// Expected created using the Python DecomposedTransform
#[test]
fn decomposed_scale_rotate_translate_to_matrix() {
assert_eq!(
[0.0, 2.0, -1.0, 0.0, 10.0, 20.0],
DecomposedTransform {
scale_x: 2.0,
rotation: 90.0,
translate_x: 10.0,
translate_y: 20.0,
..Default::default()
}
.matrix()
.round_for_test()
);
}
// Expected created using the Python DecomposedTransform
#[test]
fn decomposed_scale_skew_translate_to_matrix() {
assert_eq!(
[-0.866025, 5.5, -0.5, 3.175426, 10.0, 20.0],
DecomposedTransform {
scale_x: 2.0,
scale_y: 3.0,
rotation: 30.0,
skew_x: 30.0,
skew_y: 60.0,
translate_x: 10.0,
translate_y: 20.0,
..Default::default()
}
.matrix()
.round_for_test()
);
}
// Expected created using the Python DecomposedTransform
#[test]
fn decomposed_rotate_around_to_matrix() {
assert_eq!(
[1.732051, 1.0, -0.5, 0.866025, 10.267949, 19.267949],
DecomposedTransform {
scale_x: 2.0,
rotation: 30.0,
translate_x: 10.0,
translate_y: 20.0,
center_x: 1.0,
center_y: 2.0,
..Default::default()
}
.matrix()
.round_for_test()
);
}
#[test]
fn read_multivar_store_region_list() {
let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
let table = font.varc().unwrap();
let varstore = table.multi_var_store().unwrap().unwrap();
let regions = varstore.region_list().unwrap().regions();
let sparse_regions = regions
.iter()
.map(|r| {
r.unwrap()
.region_axis_offsets()
.iter()
.map(|a| {
(
a.axis_index(),
a.start().to_f32(),
a.peak().to_f32(),
a.end().to_f32(),
)
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
// Check a sampling of the regions
assert_eq!(
vec![
vec![(0, 0.0, 1.0, 1.0),],
vec![(0, 0.0, 1.0, 1.0), (1, 0.0, 1.0, 1.0),],
vec![(6, -1.0, -1.0, 0.0),],
],
[0, 2, 38]
.into_iter()
.map(|i| sparse_regions[i].clone())
.collect::<Vec<_>>()
);
}
#[test]
fn read_multivar_store_delta_sets() {
let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
let table = font.varc().unwrap();
let varstore = table.multi_var_store().unwrap().unwrap();
assert_eq!(
vec![(3, 6), (33, 6), (10, 5), (25, 8),],
varstore
.variation_data()
.iter()
.map(|d| d.unwrap())
.map(|d| (d.region_index_count(), d.delta_sets().unwrap().count()))
.collect::<Vec<_>>()
);
assert_eq!(
vec![-1, 33, 0, 0, 0, 0],
varstore
.variation_data()
.get(0)
.unwrap()
.delta_set(5)
.unwrap()
.iter()
.collect::<Vec<_>>()
)
}
}

1958
vendor/read-fonts/src/tables/variations.rs vendored Normal file

File diff suppressed because it is too large Load Diff

3
vendor/read-fonts/src/tables/vhea.rs vendored Normal file
View File

@@ -0,0 +1,3 @@
//! the [vhea (Horizontal Header)](https://docs.microsoft.com/en-us/typography/opentype/spec/hhea) table
include!("../../generated/generated_vhea.rs");

18
vendor/read-fonts/src/tables/vmtx.rs vendored Normal file
View File

@@ -0,0 +1,18 @@
//! The [vmtx (Vertical Metrics)](https://docs.microsoft.com/en-us/typography/opentype/spec/vmtx) table
use super::hmtx;
pub use super::hmtx::LongMetric;
include!("../../generated/generated_vmtx.rs");
impl Vmtx<'_> {
/// Returns the advance height for the given glyph identifier.
pub fn advance(&self, glyph_id: GlyphId) -> Option<u16> {
hmtx::advance(self.v_metrics(), glyph_id)
}
/// Returns the top side bearing for the given glyph identifier.
pub fn side_bearing(&self, glyph_id: GlyphId) -> Option<i16> {
hmtx::side_bearing(self.v_metrics(), self.top_side_bearings(), glyph_id)
}
}

36
vendor/read-fonts/src/tables/vorg.rs vendored Normal file
View File

@@ -0,0 +1,36 @@
//! The [VORG (Vertical Origin)](https://docs.microsoft.com/en-us/typography/opentype/spec/vorg) table.
include!("../../generated/generated_vorg.rs");
impl Vorg<'_> {
/// Returns the y coordinate of the of the glyph's vertical origin.
pub fn vertical_origin_y(&self, glyph_id: GlyphId) -> i16 {
let gid = glyph_id.to_u32();
let metrics = self.vert_origin_y_metrics();
match metrics.binary_search_by(|rec| rec.glyph_index().to_u32().cmp(&gid)) {
Ok(ix) => metrics
.get(ix)
.map(|metric| metric.vert_origin_y())
.unwrap_or_default(),
_ => self.default_vert_origin_y(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FontRef, TableProvider};
#[test]
fn vertical_origins() {
let font = FontRef::new(font_test_data::VORG).unwrap();
let vorg = font.vorg().unwrap();
// Glyphs 1 and 3 have entries while 0 and 2 use the default value
// of 880
assert_eq!(vorg.vertical_origin_y(GlyphId::new(0)), 880);
assert_eq!(vorg.vertical_origin_y(GlyphId::new(1)), 867);
assert_eq!(vorg.vertical_origin_y(GlyphId::new(2)), 880);
assert_eq!(vorg.vertical_origin_y(GlyphId::new(3)), 824);
}
}

55
vendor/read-fonts/src/tables/vvar.rs vendored Normal file
View File

@@ -0,0 +1,55 @@
//! The [VVAR (Vertical Metrics Variation)](https://docs.microsoft.com/en-us/typography/opentype/spec/vvar) table
use super::variations::{self, DeltaSetIndexMap, ItemVariationStore};
include!("../../generated/generated_vvar.rs");
impl Vvar<'_> {
/// Returns the advance height delta for the specified glyph identifier and
/// normalized variation coordinates.
pub fn advance_height_delta(
&self,
glyph_id: GlyphId,
coords: &[F2Dot14],
) -> Result<Fixed, ReadError> {
variations::advance_delta(
self.advance_height_mapping(),
self.item_variation_store(),
glyph_id,
coords,
)
}
/// Returns the top side bearing delta for the specified glyph identifier and
/// normalized variation coordinates.
pub fn tsb_delta(&self, glyph_id: GlyphId, coords: &[F2Dot14]) -> Result<Fixed, ReadError> {
variations::item_delta(
self.tsb_mapping(),
self.item_variation_store(),
glyph_id,
coords,
)
}
/// Returns the bottom side bearing delta for the specified glyph identifier and
/// normalized variation coordinates.
pub fn bsb_delta(&self, glyph_id: GlyphId, coords: &[F2Dot14]) -> Result<Fixed, ReadError> {
variations::item_delta(
self.bsb_mapping(),
self.item_variation_store(),
glyph_id,
coords,
)
}
/// Returns the vertical origin delta for the specified glyph identifier and
/// normalized variation coordinates.
pub fn v_org_delta(&self, glyph_id: GlyphId, coords: &[F2Dot14]) -> Result<Fixed, ReadError> {
variations::item_delta(
self.v_org_mapping(),
self.item_variation_store(),
glyph_id,
coords,
)
}
}

View File

@@ -0,0 +1,71 @@
use types::{GlyphId16, MajorMinor};
use super::*;
use crate::tables::layout::{ClassDefFormat2, DeltaFormat, DeviceOrVariationIndex};
use font_test_data::gdef as test_data;
#[test]
fn gdef_header() {
let table = Gdef::read(test_data::GDEF_HEADER.into()).unwrap();
assert_eq!(table.version(), MajorMinor::VERSION_1_0);
assert_eq!(table.mark_attach_class_def_offset(), 0x5a);
}
#[test]
fn glyph_class_def_table() {
let table = ClassDefFormat2::read(test_data::GLYPHCLASSDEF_TABLE.into()).unwrap();
assert_eq!(table.class_range_count(), 4);
let last_record = &table.class_range_records()[3];
assert_eq!(last_record.start_glyph_id(), GlyphId16::new(0x18f));
assert_eq!(last_record.end_glyph_id(), GlyphId16::new(0x18f));
}
#[test]
fn attach_list_table() {
let table = AttachList::read(test_data::ATTACHLIST_TABLE.into()).unwrap();
assert_eq!(table.glyph_count(), 2);
assert_eq!(table.attach_point_offsets().len(), 2);
let attach_point = table.attach_points().get(1).unwrap();
assert_eq!(attach_point.point_indices()[0].get(), 14);
assert_eq!(attach_point.point_indices()[1].get(), 23);
}
#[test]
fn lig_caret_list() {
let table = LigCaretList::read(test_data::LIGCARETLIST_TABLE.into()).unwrap();
let glyph1 = table.lig_glyphs().get(0).unwrap();
let glyph2 = table.lig_glyphs().get(1).unwrap();
assert_eq!(glyph1.caret_value_offsets().len(), 1);
assert_eq!(glyph2.caret_value_offsets().len(), 2);
let g1c0: CaretValueFormat1 = glyph1.caret_value_offsets()[0]
.get()
.resolve(glyph1.offset_data())
.unwrap();
assert_eq!(g1c0.coordinate(), 603);
let g2c1: CaretValueFormat1 = glyph2.caret_value_offsets()[1]
.get()
.resolve(glyph2.offset_data())
.unwrap();
assert_eq!(g2c1.coordinate(), 1206);
}
#[test]
fn caretvalueformat3() {
let table = CaretValueFormat3::read(test_data::CARETVALUEFORMAT3_TABLE.into()).unwrap();
assert_eq!(table.coordinate(), 1206);
let DeviceOrVariationIndex::Device(device) = table.device().unwrap() else {
panic!("not a device table");
};
assert_eq!(device.start_size(), 12);
assert_eq!(device.end_size(), 17);
assert_eq!(device.delta_format(), DeltaFormat::Local4BitDeltas);
assert_eq!(
vec![0x1111, 0x2200],
device
.delta_value()
.iter()
.map(|x| x.get())
.collect::<Vec<_>>()
);
}

280
vendor/read-fonts/src/tests/test_gpos.rs vendored Normal file
View File

@@ -0,0 +1,280 @@
use crate::tables::layout::DeltaFormat;
use super::*;
use font_test_data::gpos as test_data;
#[test]
fn singleposformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-2-singleposformat1-subtable
let table = SinglePosFormat1::read(test_data::SINGLEPOSFORMAT1.into()).unwrap();
assert_eq!(table.value_format(), ValueFormat::Y_PLACEMENT);
assert_eq!(table.value_record().y_placement.unwrap().get(), -80);
let coverage = table.coverage().unwrap();
assert_eq!(coverage.iter().count(), 10);
}
#[test]
fn singleposformat2() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-3-singleposformat2-subtable
let table = SinglePosFormat2::read(test_data::SINGLEPOSFORMAT2.into()).unwrap();
assert_eq!(
table.value_format(),
ValueFormat::X_PLACEMENT | ValueFormat::X_ADVANCE
);
assert_eq!(table.value_count(), 3);
assert_eq!(
table.value_records().get(0).unwrap().x_placement(),
Some(50)
);
assert_eq!(table.value_records().get(1).unwrap().x_advance(), Some(25));
assert_eq!(
table.value_records().get(2).unwrap().x_placement(),
Some(10)
);
assert!(table.value_records().get(3).is_err());
}
#[test]
fn pairposformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-4-pairposformat1-subtable
let table = PairPosFormat1::read(test_data::PAIRPOSFORMAT1.into()).unwrap();
assert_eq!(table.value_format1(), ValueFormat::X_ADVANCE);
assert_eq!(table.value_format2(), ValueFormat::X_PLACEMENT);
assert_eq!(table.pair_set_count(), 2);
let set1 = table.pair_sets().get(0).unwrap();
let set2 = table.pair_sets().get(1).unwrap();
assert_eq!(set1.pair_value_records().iter().count(), 1);
assert_eq!(set2.pair_value_records().iter().count(), 1);
let rec1 = set1.pair_value_records().get(0).unwrap();
let rec2 = set2.pair_value_records().get(0).unwrap();
assert_eq!(rec1.second_glyph(), GlyphId16::new(0x59));
assert_eq!(rec1.value_record1().x_advance(), Some(-30));
assert!(rec1.value_record1().x_placement().is_none());
assert_eq!(rec1.value_record2().x_placement(), Some(-20));
assert_eq!(rec2.second_glyph(), GlyphId16::new(0x59));
assert_eq!(rec2.value_record1().x_advance(), Some(-40));
assert_eq!(rec2.value_record2().x_placement(), Some(-25));
}
#[test]
fn pairposformat2() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-5-pairposformat2-subtable
let table = PairPosFormat2::read(test_data::PAIRPOSFORMAT2.into()).unwrap();
assert_eq!(table.value_format1().record_byte_len(), 2);
assert_eq!(table.value_format2().record_byte_len(), 0);
assert_eq!(table.class1_count(), 2);
assert_eq!(table.class1_records().iter().count(), 2);
let class2 = table.class_def2().unwrap();
match class2 {
ClassDef::Format1(_) => panic!("expected format2"),
ClassDef::Format2(cls) => {
assert_eq!(
cls.class_range_records()[0].start_glyph_id.get(),
GlyphId16::new(0x6A)
);
}
}
}
#[test]
fn cursiveposformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-6-cursiveposformat1-subtable
let table = CursivePosFormat1::read(test_data::CURSIVEPOSFORMAT1.into()).unwrap();
assert_eq!(table.entry_exit_count(), 2);
assert_eq!(table.entry_exit_record().len(), 2);
let record2 = &table.entry_exit_record()[1];
let entry2: AnchorFormat1 = record2
.entry_anchor_offset()
.resolve(table.offset_data())
.unwrap()
.unwrap();
assert_eq!(entry2.x_coordinate(), 1500);
assert_eq!(entry2.y_coordinate(), 44);
}
#[test]
fn markbaseposformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-7-markbaseposformat1-subtable
let table = MarkBasePosFormat1::read(test_data::MARKBASEPOSFORMAT1.into()).unwrap();
let base_array = table.base_array().unwrap();
assert_eq!(base_array.base_records().iter().count(), 1);
let record = base_array.base_records().get(0).unwrap();
assert_eq!(record.base_anchor_offsets.len(), 2);
let anchor1: AnchorFormat1 = record.base_anchor_offsets[1]
.get()
.resolve(base_array.offset_data())
.unwrap()
.unwrap();
assert_eq!(anchor1.x_coordinate(), 830);
}
#[test]
fn markligposformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-8-markligposformat1-subtable
let table = MarkLigPosFormat1::read(test_data::MARKLIGPOSFORMAT1.into()).unwrap();
let lig_array = table.ligature_array().unwrap();
assert_eq!(lig_array.ligature_count(), 1);
let lig_attach = lig_array.ligature_attaches().get(0).unwrap();
assert_eq!(lig_attach.component_count(), 3);
let comp_record = lig_attach
.component_records()
.iter()
.nth(2)
.unwrap()
.unwrap();
assert!(comp_record.ligature_anchor_offsets[0].get().is_null());
assert!(comp_record.ligature_anchor_offsets[1].get().is_null());
}
#[test]
fn markmarkposformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-9-markmarkposformat1-subtable
let table = MarkMarkPosFormat1::read(test_data::MARKMARKPOSFORMAT1.into()).unwrap();
assert_eq!(table.mark_class_count(), 1);
let mark2array = table.mark2_array().unwrap();
dbg!(mark2array.offset_data());
assert_eq!(mark2array.mark2_count(), 1);
assert_eq!(mark2array.mark2_records().iter().count(), 1);
let record = mark2array.mark2_records().get(0).unwrap();
assert_eq!(record.mark2_anchor_offsets.len(), 1);
let anchor_off = record.mark2_anchor_offsets[0].get();
let anchor: AnchorFormat1 = anchor_off
.resolve(mark2array.offset_data())
.unwrap()
.unwrap();
assert_eq!(anchor.x_coordinate(), 221);
assert_eq!(anchor.y_coordinate(), 301);
}
#[test]
fn contextualposformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-10-contextual-positioning-format-1
let _table =
crate::tables::layout::SequenceContextFormat1::read(test_data::CONTEXTUALPOSFORMAT1.into())
.unwrap();
}
#[test]
fn contextualposformat2() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-11-contextual-positioning-format-1
let _table =
crate::tables::layout::SequenceContextFormat2::read(test_data::CONTEXTUALPOSFORMAT2.into())
.unwrap();
}
#[test]
fn contextualposformat3() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-12-contextual-positioning-format-3
let _table =
crate::tables::layout::SequenceContextFormat3::read(test_data::CONTEXTUALPOSFORMAT3.into())
.unwrap();
}
//FIXME: we don't have a way to instantiate individual records right now?
#[test]
fn sequencelookuprecord() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-13-sequencelookuprecord
let record = FontData::new(test_data::SEQUENCELOOKUPRECORD)
.read_ref_at::<crate::tables::layout::SequenceLookupRecord>(0)
.unwrap();
assert_eq!(record.sequence_index(), 1);
assert_eq!(record.lookup_list_index(), 1);
}
#[test]
fn valueformattable() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-14-valueformat-table-and-valuerecord
let table = SinglePosFormat1::read(test_data::VALUEFORMATTABLE.into()).unwrap();
assert_eq!(
table.value_format(),
ValueFormat::X_PLACEMENT
| ValueFormat::Y_ADVANCE
| ValueFormat::X_PLACEMENT_DEVICE
| ValueFormat::Y_ADVANCE_DEVICE
);
let record = table.value_record();
assert_eq!(record.y_advance(), Some(210));
let DeviceOrVariationIndex::Device(device) = record
.y_advance_device(table.offset_data())
.unwrap()
.unwrap()
else {
panic!("not a device");
};
assert_eq!((device.start_size(), device.end_size()), (11, 15));
assert_eq!(device.delta_format(), DeltaFormat::Local2BitDeltas);
assert_eq!(device.delta_value(), [0x5540]);
}
#[test]
fn anchorformat1() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-15-anchorformat1-table
let table = AnchorFormat1::read(test_data::ANCHORFORMAT1.into()).unwrap();
assert_eq!(table.x_coordinate(), 189);
assert_eq!(table.y_coordinate(), -103);
}
#[test]
fn anchorformat2() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-16-anchorformat2-table
let table = AnchorFormat2::read(test_data::ANCHORFORMAT2.into()).unwrap();
assert_eq!(table.x_coordinate(), 322);
assert_eq!(table.anchor_point(), 13);
}
#[test]
fn anchorformat3() {
// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-17-anchorformat3-table
let table = AnchorFormat3::read(test_data::ANCHORFORMAT3.into()).unwrap();
assert_eq!(table.x_coordinate(), 279);
assert_eq!(table.y_coordinate(), 1301);
let x_dev = table.x_device().unwrap().unwrap();
let y_dev = table.y_device().unwrap().unwrap();
let (DeviceOrVariationIndex::Device(x_dev), DeviceOrVariationIndex::Device(y_dev)) =
(x_dev, y_dev)
else {
panic!("missing device tables");
};
assert_eq!(x_dev.delta_format(), DeltaFormat::Local4BitDeltas);
assert_eq!(x_dev.delta_value(), [0x1111, 0x2200]);
assert_eq!(y_dev.delta_format(), DeltaFormat::Local4BitDeltas);
assert_eq!(y_dev.delta_value(), [0x1111, 0x2200]);
}
//NOTE: I think the sample bytes are missing the actual anchor tables??
// and so we can't really round-trip this...
//#[test]
//fn markarraytable() {
//// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#example-18-markarray-table-and-markrecord
//let bytes = [0x00, 0x02, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x10];
//let table = MarkArray::read(&bytes).unwrap();
//let owned = table.to_owned_obj(&[]).unwrap();
//let dumped = crate::write::dump_table(&owned);
//assert_hex_eq!(&bytes, &dumped);
//}[1, 1, 1, 1, 1]

View File

@@ -0,0 +1,78 @@
use super::*;
use font_test_data::gsub as test_data;
#[test]
fn singlesubstformat1() {
// https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-2-singlesubstformat1-subtable
let table = SingleSubstFormat1::read(test_data::SINGLESUBSTFORMAT1_TABLE.into()).unwrap();
assert_eq!(table.delta_glyph_id(), 192);
}
#[test]
fn singlesubstformat2() {
// https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-3-singlesubstformat2-subtable
let table = SingleSubstFormat2::read(test_data::SINGLESUBSTFORMAT2_TABLE.into()).unwrap();
assert_eq!(
table.substitute_glyph_ids(),
&[
GlyphId16::new(305),
GlyphId16::new(309),
GlyphId16::new(318),
GlyphId16::new(323)
],
);
}
#[test]
fn multiplesubstformat1() {
// https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-4-multiplesubstformat1-subtable
let table = MultipleSubstFormat1::read(test_data::MULTIPLESUBSTFORMAT1_TABLE.into()).unwrap();
assert_eq!(table.sequences().len(), 1);
let seq0 = table.sequences().get(0).unwrap();
assert_eq!(
seq0.substitute_glyph_ids(),
&[GlyphId16::new(26), GlyphId16::new(26), GlyphId16::new(29)]
);
}
#[test]
fn alternatesubstformat1() {
// https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-5-alternatesubstformat-1-subtable
let table = AlternateSubstFormat1::read(test_data::ALTERNATESUBSTFORMAT1_TABLE.into()).unwrap();
assert_eq!(table.alternate_sets().len(), 1);
let altset0 = table.alternate_sets().get(0).unwrap();
assert_eq!(
altset0.alternate_glyph_ids(),
&[GlyphId16::new(0xc9), GlyphId16::new(0xca)]
);
}
#[test]
fn ligaturesubstformat1() {
// https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-6-ligaturesubstformat1-subtable
let table = LigatureSubstFormat1::read(test_data::LIGATURESUBSTFORMAT1_TABLE.into()).unwrap();
assert_eq!(table.ligature_sets().len(), 2);
let ligset0 = table.ligature_sets().get(0).unwrap();
assert_eq!(ligset0.ligatures().len(), 1);
let lig0 = ligset0.ligatures().get(0).unwrap();
assert_eq!(lig0.ligature_glyph(), GlyphId16::new(347));
assert_eq!(
lig0.component_glyph_ids(),
&[GlyphId16::new(0x28), GlyphId16::new(0x17)]
);
let ligset1 = table.ligature_sets().get(1).unwrap();
let lig0 = ligset1.ligatures().get(0).unwrap();
assert_eq!(lig0.ligature_glyph(), GlyphId16::new(0xf1));
assert_eq!(
lig0.component_glyph_ids(),
&[GlyphId16::new(0x1a), GlyphId16::new(0x1d)]
);
}
//TODO:
// - https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-7-contextual-substitution-format-1
// - https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-8-contextual-substitution-format-2
// - https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-9-contextual-substitution-format-3
// - https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#example-10-reversechainsinglesubstformat1-subtable

785
vendor/read-fonts/src/traversal.rs vendored Normal file
View File

@@ -0,0 +1,785 @@
//! Experimental generic traversal of font tables.
//!
//! This module defines functionality that allows untyped access to font table
//! data. This is used as the basis for things like debug printing.
//!
//! The basis of traversal is the [`SomeTable`] trait, which is implemented for
//! all font tables. This trait provides the table's name, as well as ordered access
//! to the table's fields. Using this, it is possible to iterate through a table
//! and its subtables, records, and values.
//!
//! # Warning
//!
//! This functionality is considered experimental, and the API may break or be
//! removed without warning.
use std::{fmt::Debug, ops::Deref};
use types::{
BigEndian, F2Dot14, FWord, Fixed, GlyphId16, Int24, LongDateTime, MajorMinor, NameId, Nullable,
Offset16, Offset24, Offset32, Scalar, Tag, UfWord, Uint24, Version16Dot16,
};
use crate::{
array::{ComputedArray, VarLenArray},
read::{ComputeSize, ReadArgs},
FontData, FontRead, FontReadWithArgs, ReadError, VarSize,
};
/// Types of fields in font tables.
///
/// Fields can either be scalars, offsets to tables, or arrays.
pub enum FieldType<'a> {
I8(i8),
U8(u8),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I24(Int24),
U24(Uint24),
Tag(Tag),
FWord(FWord),
UfWord(UfWord),
MajorMinor(MajorMinor),
Version16Dot16(Version16Dot16),
F2Dot14(F2Dot14),
Fixed(Fixed),
LongDateTime(LongDateTime),
GlyphId16(GlyphId16),
NameId(NameId),
BareOffset(OffsetType),
ResolvedOffset(ResolvedOffset<'a>),
/// Used in tables like name/post so we can actually print the strings
StringOffset(StringOffset<'a>),
/// Used in COLR/CPAL
ArrayOffset(ArrayOffset<'a>),
Record(RecordResolver<'a>),
Array(Box<dyn SomeArray<'a> + 'a>),
Unknown,
}
/// Any offset type.
#[derive(Clone, Copy)]
pub enum OffsetType {
Offset16(u16),
Offset24(Uint24),
Offset32(u32),
}
impl OffsetType {
/// Return this offset as a u32.
pub fn to_u32(self) -> u32 {
match self {
Self::Offset16(val) => val.into(),
Self::Offset24(val) => val.into(),
Self::Offset32(val) => val,
}
}
}
/// An offset, as well as the table it references.
pub struct ResolvedOffset<'a> {
/// The raw offset
pub offset: OffsetType,
/// The parsed table pointed to by this offset, or an error if parsing fails.
pub target: Result<Box<dyn SomeTable<'a> + 'a>, ReadError>,
}
/// An offset to string data.
///
/// This is a special case for the name table (and maybe elsewhere?)
pub struct StringOffset<'a> {
pub offset: OffsetType,
pub target: Result<Box<dyn SomeString<'a> + 'a>, ReadError>,
}
/// An offset to an array.
pub struct ArrayOffset<'a> {
pub offset: OffsetType,
pub target: Result<Box<dyn SomeArray<'a> + 'a>, ReadError>,
}
pub(crate) struct ArrayOfOffsets<'a, O> {
type_name: &'static str,
offsets: &'a [O],
resolver: Box<dyn Fn(&O) -> FieldType<'a> + 'a>,
}
impl<'a, O> SomeArray<'a> for ArrayOfOffsets<'a, O> {
fn type_name(&self) -> &str {
self.type_name
}
fn len(&self) -> usize {
self.offsets.len()
}
fn get(&self, idx: usize) -> Option<FieldType<'a>> {
let off = self.offsets.get(idx)?;
let target = (self.resolver)(off);
Some(target)
}
}
impl<'a> FieldType<'a> {
/// makes a field, handling the case where this array may not be present in
/// all versions
pub fn array_of_records<T>(
type_name: &'static str,
records: &'a [T],
data: FontData<'a>,
) -> FieldType<'a>
where
T: Clone + SomeRecord<'a> + 'a,
{
ArrayOfRecords {
type_name,
data,
records,
}
.into()
}
// Convenience method for handling computed arrays
pub fn computed_array<T>(
type_name: &'static str,
array: ComputedArray<'a, T>,
data: FontData<'a>,
) -> FieldType<'a>
where
T: FontReadWithArgs<'a> + ComputeSize + SomeRecord<'a> + 'a,
T::Args: Copy + 'static,
{
ComputedArrayOfRecords {
type_name,
data,
array,
}
.into()
}
// Convenience method for handling VarLenArrays
pub fn var_array<T>(
type_name: &'static str,
array: VarLenArray<'a, T>,
data: FontData<'a>,
) -> FieldType<'a>
where
T: FontRead<'a> + VarSize + SomeRecord<'a> + 'a,
{
VarLenArrayOfRecords {
type_name,
data,
array,
}
.into()
}
/// Convenience method for creating a `FieldType` from an array of offsets.
///
/// The `resolver` argument is a function that takes an offset and resolves
/// it.
pub fn array_of_offsets<O>(
type_name: &'static str,
offsets: &'a [O],
resolver: impl Fn(&O) -> FieldType<'a> + 'a,
) -> Self
where {
FieldType::Array(Box::new(ArrayOfOffsets {
type_name,
offsets,
resolver: Box::new(resolver),
}))
}
/// Convenience method for creating a `FieldType` from an offset to an array.
pub fn offset_to_array_of_scalars<T: SomeArray<'a> + 'a>(
offset: impl Into<OffsetType>,
result: impl Into<Option<Result<T, ReadError>>>,
) -> Self {
let offset = offset.into();
match result.into() {
Some(target) => FieldType::ArrayOffset(ArrayOffset {
offset,
target: target.map(|x| Box::new(x) as Box<dyn SomeArray>),
}),
None => FieldType::BareOffset(offset),
}
}
/// Convenience method for creating a `FieldType` from an offset to an array.
pub fn offset_to_array_of_records<T: Clone + SomeRecord<'a> + 'a>(
offset: impl Into<OffsetType>,
result: impl Into<Option<Result<&'a [T], ReadError>>>,
type_name: &'static str,
data: FontData<'a>,
) -> Self {
let offset = offset.into();
match result.into() {
Some(target) => {
let target = target.map(|records| {
Box::new(ArrayOfRecords {
type_name,
data,
records,
}) as Box<dyn SomeArray>
});
FieldType::ArrayOffset(ArrayOffset { offset, target })
}
None => FieldType::BareOffset(offset),
}
}
//FIXME: I bet this is generating a *lot* of code
/// Convenience method for creating a `FieldType` for a resolved offset.
///
/// This handles cases where offsets are nullable, in which case the `result`
/// argument may be `None`.
pub fn offset<T: SomeTable<'a> + 'a>(
offset: impl Into<OffsetType>,
result: impl Into<Option<Result<T, ReadError>>>,
) -> Self {
let offset = offset.into();
match result.into() {
Some(target) => FieldType::ResolvedOffset(ResolvedOffset {
offset,
target: target.map(|x| Box::new(x) as Box<dyn SomeTable>),
}),
None => FieldType::BareOffset(offset),
}
}
/// Convenience method for creating a `FieldType` from an unknown offset.
pub fn unknown_offset(offset: impl Into<OffsetType>) -> Self {
Self::BareOffset(offset.into())
}
}
/// A generic field in a font table.
pub struct Field<'a> {
/// The field's name.
pub name: &'static str,
/// The field's value.
pub value: FieldType<'a>,
}
/// A generic table type.
///
/// This is intended to be used as a trait object, and is a way of generically
/// representing any table, providing ordered access to that table's fields.
pub trait SomeTable<'a> {
/// The name of this table
fn type_name(&self) -> &str;
/// Access this table's fields, in declaration order.
fn get_field(&self, idx: usize) -> Option<Field<'a>>;
}
impl<'a> dyn SomeTable<'a> + 'a {
/// Returns an iterator over this table's fields.
pub fn iter(&self) -> impl Iterator<Item = Field<'a>> + '_ {
FieldIter {
table: self,
idx: 0,
}
}
}
struct FieldIter<'a, 'b> {
table: &'b dyn SomeTable<'a>,
idx: usize,
}
impl<'a> Iterator for FieldIter<'a, '_> {
type Item = Field<'a>;
fn next(&mut self) -> Option<Self::Item> {
let this = self.idx;
self.idx += 1;
self.table.get_field(this)
}
}
impl<'a> SomeTable<'a> for Box<dyn SomeTable<'a> + 'a> {
fn type_name(&self) -> &str {
self.deref().type_name()
}
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
self.deref().get_field(idx)
}
}
/// A generic trait for records, which need to be passed in data
/// in order to fully resolve themselves.
pub trait SomeRecord<'a> {
fn traverse(self, data: FontData<'a>) -> RecordResolver<'a>;
}
/// A struct created from a record and the data it needs to resolve any
/// contained offsets.
pub struct RecordResolver<'a> {
pub(crate) name: &'static str,
pub(crate) get_field: Box<dyn Fn(usize, FontData<'a>) -> Option<Field<'a>> + 'a>,
pub(crate) data: FontData<'a>,
}
/// A generic trait for arrays.
pub trait SomeArray<'a> {
/// The name of this type. For an array of u16s, this is `[u16]`.
fn type_name(&self) -> &str;
/// The length of the array.
fn len(&self) -> usize;
/// Returns `true` if this array is empty.
fn is_empty(&self) -> bool {
self.len() == 0
}
/// Return the item at `idx`, or `None` if `idx` is out of bounds.
fn get(&self, idx: usize) -> Option<FieldType<'a>>;
}
impl<'a> dyn SomeArray<'a> + 'a {
/// Return an iterator over the contents of this array.
pub fn iter(&self) -> impl Iterator<Item = FieldType<'a>> + '_ {
ArrayIter {
array: self,
idx: 0,
}
}
}
struct ArrayIter<'a, 'b> {
array: &'b dyn SomeArray<'a>,
idx: usize,
}
impl<'a> Iterator for ArrayIter<'a, '_> {
type Item = FieldType<'a>;
fn next(&mut self) -> Option<Self::Item> {
let this = self.idx;
self.idx += 1;
self.array.get(this)
}
}
impl<'a, T: Scalar + Into<FieldType<'a>>> SomeArray<'a> for &'a [BigEndian<T>]
where
BigEndian<T>: Copy, // i don't know why i need this??
{
fn len(&self) -> usize {
(*self).len()
}
fn get(&self, idx: usize) -> Option<FieldType<'a>> {
(*self).get(idx).map(|val| val.get().into())
}
fn type_name(&self) -> &str {
let full_name = std::any::type_name::<T>();
full_name.split("::").last().unwrap_or(full_name)
}
}
impl<'a> SomeArray<'a> for &'a [u8] {
fn type_name(&self) -> &str {
"u8"
}
fn len(&self) -> usize {
(*self).len()
}
fn get(&self, idx: usize) -> Option<FieldType<'a>> {
(*self).get(idx).copied().map(Into::into)
}
}
impl<'a> SomeArray<'a> for Box<dyn SomeArray<'a> + 'a> {
fn type_name(&self) -> &str {
self.deref().type_name()
}
fn len(&self) -> usize {
self.deref().len()
}
fn get(&self, idx: usize) -> Option<FieldType<'a>> {
self.deref().get(idx)
}
}
pub trait SomeString<'a> {
fn iter_chars(&self) -> Box<dyn Iterator<Item = char> + 'a>;
}
impl<'a> SomeString<'a> for Box<dyn SomeString<'a> + 'a> {
fn iter_chars(&self) -> Box<dyn Iterator<Item = char> + 'a> {
self.deref().iter_chars()
}
}
// only used as Box<dyn SomeArray<'a>>
struct ArrayOfRecords<'a, T> {
pub(crate) type_name: &'static str,
pub(crate) data: FontData<'a>,
pub(crate) records: &'a [T],
}
// only used as Box<dyn SomeArray<'a>>
struct ComputedArrayOfRecords<'a, T: ReadArgs> {
pub(crate) type_name: &'static str,
pub(crate) data: FontData<'a>,
pub(crate) array: ComputedArray<'a, T>,
}
struct VarLenArrayOfRecords<'a, T> {
pub(crate) type_name: &'static str,
pub(crate) data: FontData<'a>,
pub(crate) array: VarLenArray<'a, T>,
}
impl<'a, T> SomeArray<'a> for ComputedArrayOfRecords<'a, T>
where
T: FontReadWithArgs<'a> + ComputeSize + SomeRecord<'a> + 'a,
T::Args: Copy + 'static,
Self: 'a,
{
fn len(&self) -> usize {
self.array.len()
}
fn get(&self, idx: usize) -> Option<FieldType<'a>> {
self.array
.get(idx)
.ok()
.map(|record| record.traverse(self.data).into())
}
fn type_name(&self) -> &str {
self.type_name
}
}
impl<'a, T: SomeRecord<'a> + Clone> SomeArray<'a> for ArrayOfRecords<'a, T> {
fn type_name(&self) -> &str {
self.type_name
}
fn len(&self) -> usize {
self.records.len()
}
fn get(&self, idx: usize) -> Option<FieldType<'a>> {
self.records
.get(idx)
.map(|record| record.clone().traverse(self.data).into())
}
}
impl<'a, T> SomeArray<'a> for VarLenArrayOfRecords<'a, T>
where
T: FontRead<'a> + VarSize + SomeRecord<'a> + 'a,
Self: 'a,
{
fn len(&self) -> usize {
self.array.iter().count()
}
fn get(&self, idx: usize) -> Option<FieldType<'a>> {
self.array
.get(idx)?
.ok()
.map(|record| record.traverse(self.data).into())
}
fn type_name(&self) -> &str {
self.type_name
}
}
impl<'a> Field<'a> {
/// Create a new field with the given name and value.
pub fn new(name: &'static str, value: impl Into<FieldType<'a>>) -> Self {
Field {
name,
value: value.into(),
}
}
}
/// A wrapper type that implements `Debug` for any table.
struct DebugPrintTable<'a, 'b>(pub &'b (dyn SomeTable<'a> + 'a));
/// A wrapper type that implements `Debug` for any array.
struct DebugPrintArray<'a, 'b>(pub &'b (dyn SomeArray<'a> + 'a));
impl<'a> Debug for FieldType<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::I8(arg0) => arg0.fmt(f),
Self::U8(arg0) => arg0.fmt(f),
Self::I16(arg0) => arg0.fmt(f),
Self::U16(arg0) => arg0.fmt(f),
Self::I32(arg0) => arg0.fmt(f),
Self::U32(arg0) => arg0.fmt(f),
Self::I24(arg0) => arg0.fmt(f),
Self::U24(arg0) => arg0.fmt(f),
Self::Tag(arg0) => arg0.fmt(f),
Self::FWord(arg0) => arg0.to_i16().fmt(f),
Self::UfWord(arg0) => arg0.to_u16().fmt(f),
Self::MajorMinor(arg0) => write!(f, "{}.{}", arg0.major, arg0.minor),
Self::Version16Dot16(arg0) => arg0.fmt(f),
Self::F2Dot14(arg0) => arg0.fmt(f),
Self::Fixed(arg0) => arg0.fmt(f),
Self::LongDateTime(arg0) => arg0.as_secs().fmt(f),
Self::GlyphId16(arg0) => {
write!(f, "g")?;
arg0.to_u16().fmt(f)
}
Self::NameId(arg0) => arg0.fmt(f),
Self::StringOffset(string) => match &string.target {
Ok(arg0) => arg0.as_ref().fmt(f),
Err(_) => string.target.fmt(f),
},
Self::ArrayOffset(array) => match &array.target {
Ok(arg0) => arg0.as_ref().fmt(f),
Err(_) => array.target.fmt(f),
},
Self::BareOffset(arg0) => write!(f, "0x{:04X}", arg0.to_u32()),
Self::ResolvedOffset(ResolvedOffset {
target: Ok(arg0), ..
}) => arg0.fmt(f),
Self::ResolvedOffset(arg0) => arg0.target.fmt(f),
Self::Record(arg0) => (arg0 as &(dyn SomeTable<'a> + 'a)).fmt(f),
Self::Array(arg0) => arg0.fmt(f),
Self::Unknown => write!(f, "no repr available"),
}
}
}
impl std::fmt::Debug for DebugPrintTable<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut debug_struct = f.debug_struct(self.0.type_name());
for field in self.0.iter() {
debug_struct.field(field.name, &field.value);
}
debug_struct.finish()
}
}
impl<'a> Debug for dyn SomeTable<'a> + 'a {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
DebugPrintTable(self).fmt(f)
}
}
impl<'a> Debug for dyn SomeString<'a> + 'a {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "\"")?;
for c in self.iter_chars() {
write!(f, "{c}")?
}
write!(f, "\"")
}
}
impl std::fmt::Debug for DebugPrintArray<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut debug_list = f.debug_list();
let mut idx = 0;
while let Some(item) = self.0.get(idx) {
idx += 1;
debug_list.entry(&item);
}
debug_list.finish()
}
}
impl<'a> Debug for dyn SomeArray<'a> + 'a {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
DebugPrintArray(self).fmt(f)
}
}
impl std::fmt::Display for OffsetType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:+}", self.to_u32())
}
}
// used to give us an auto-impl of Debug
impl<'a> SomeTable<'a> for RecordResolver<'a> {
fn type_name(&self) -> &str {
self.name
}
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
(self.get_field)(idx, self.data)
}
}
impl<'a> From<u8> for FieldType<'a> {
fn from(src: u8) -> FieldType<'a> {
FieldType::U8(src)
}
}
impl<'a> From<i8> for FieldType<'a> {
fn from(src: i8) -> FieldType<'a> {
FieldType::I8(src)
}
}
impl<'a> From<u16> for FieldType<'a> {
fn from(src: u16) -> FieldType<'a> {
FieldType::U16(src)
}
}
impl<'a> From<i16> for FieldType<'a> {
fn from(src: i16) -> FieldType<'a> {
FieldType::I16(src)
}
}
impl<'a> From<u32> for FieldType<'a> {
fn from(src: u32) -> FieldType<'a> {
FieldType::U32(src)
}
}
impl<'a> From<i32> for FieldType<'a> {
fn from(src: i32) -> FieldType<'a> {
FieldType::I32(src)
}
}
impl<'a> From<Uint24> for FieldType<'a> {
fn from(src: Uint24) -> FieldType<'a> {
FieldType::U24(src)
}
}
impl<'a> From<Int24> for FieldType<'a> {
fn from(src: Int24) -> FieldType<'a> {
FieldType::I24(src)
}
}
impl<'a> From<Tag> for FieldType<'a> {
fn from(src: Tag) -> FieldType<'a> {
FieldType::Tag(src)
}
}
impl<'a> From<FWord> for FieldType<'a> {
fn from(src: FWord) -> FieldType<'a> {
FieldType::FWord(src)
}
}
impl<'a> From<UfWord> for FieldType<'a> {
fn from(src: UfWord) -> FieldType<'a> {
FieldType::UfWord(src)
}
}
impl<'a> From<Fixed> for FieldType<'a> {
fn from(src: Fixed) -> FieldType<'a> {
FieldType::Fixed(src)
}
}
impl<'a> From<F2Dot14> for FieldType<'a> {
fn from(src: F2Dot14) -> FieldType<'a> {
FieldType::F2Dot14(src)
}
}
impl<'a> From<LongDateTime> for FieldType<'a> {
fn from(src: LongDateTime) -> FieldType<'a> {
FieldType::LongDateTime(src)
}
}
impl<'a> From<MajorMinor> for FieldType<'a> {
fn from(src: MajorMinor) -> FieldType<'a> {
FieldType::MajorMinor(src)
}
}
impl<'a> From<Version16Dot16> for FieldType<'a> {
fn from(src: Version16Dot16) -> FieldType<'a> {
FieldType::Version16Dot16(src)
}
}
impl<'a> From<GlyphId16> for FieldType<'a> {
fn from(src: GlyphId16) -> FieldType<'a> {
FieldType::GlyphId16(src)
}
}
impl<'a> From<NameId> for FieldType<'a> {
fn from(src: NameId) -> FieldType<'a> {
FieldType::NameId(src)
}
}
impl<'a> From<RecordResolver<'a>> for FieldType<'a> {
fn from(src: RecordResolver<'a>) -> Self {
FieldType::Record(src)
}
}
impl<'a, T: SomeArray<'a> + 'a> From<T> for FieldType<'a> {
fn from(src: T) -> Self {
FieldType::Array(Box::new(src))
}
}
impl From<Offset16> for OffsetType {
fn from(src: Offset16) -> OffsetType {
OffsetType::Offset16(src.to_u32() as u16)
}
}
impl From<Offset24> for OffsetType {
fn from(src: Offset24) -> OffsetType {
OffsetType::Offset24(Uint24::new(src.to_u32()))
}
}
impl From<Offset32> for OffsetType {
fn from(src: Offset32) -> OffsetType {
OffsetType::Offset32(src.to_u32())
}
}
impl<'a> From<Offset16> for FieldType<'a> {
fn from(src: Offset16) -> FieldType<'a> {
FieldType::BareOffset(src.into())
}
}
impl<'a> From<Offset24> for FieldType<'a> {
fn from(src: Offset24) -> FieldType<'a> {
FieldType::BareOffset(src.into())
}
}
impl<'a> From<Offset32> for FieldType<'a> {
fn from(src: Offset32) -> FieldType<'a> {
FieldType::BareOffset(src.into())
}
}
impl<T: Into<OffsetType> + Clone> From<Nullable<T>> for OffsetType {
fn from(src: Nullable<T>) -> Self {
src.offset().clone().into()
}
}