Skip to content

Commit

Permalink
[read-fonts] add hdmx table (#1164)
Browse files Browse the repository at this point in the history
Also includes a small codegen change that uses `cursor.read()` instead of `cursor.read_be()` for `u8` fields in records.
  • Loading branch information
dfrg authored Oct 1, 2024
1 parent f80507c commit d4e29ac
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 0 deletions.
9 changes: 9 additions & 0 deletions font-codegen/src/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,15 @@ impl Field {
let count = self.attrs.count.as_ref().unwrap().count_expr();
quote!(cursor.read_computed_array(#count, &#args)?)
}
FieldType::Scalar { typ } => {
if typ == "u8" {
// We don't wrap u8 in BigEndian so we need to read it
// directly
quote!(cursor.read()?)
} else {
quote!(cursor.read_be()?)
}
}
_ => match self
.attrs
.read_with_args
Expand Down
226 changes: 226 additions & 0 deletions read-fonts/generated/generated_hdmx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// THIS FILE IS AUTOGENERATED.
// Any changes to this file will be overwritten.
// For more information about how codegen works, see font-codegen/README.md

#[allow(unused_imports)]
use crate::codegen_prelude::*;

/// The [Horizontal Device Metrics](https://learn.microsoft.com/en-us/typography/opentype/spec/hdmx) table.
#[derive(Debug, Clone, Copy)]
#[doc(hidden)]
pub struct HdmxMarker {
num_glyphs: u16,
records_byte_len: usize,
}

impl HdmxMarker {
fn version_byte_range(&self) -> Range<usize> {
let start = 0;
start..start + u16::RAW_BYTE_LEN
}
fn num_records_byte_range(&self) -> Range<usize> {
let start = self.version_byte_range().end;
start..start + u16::RAW_BYTE_LEN
}
fn size_device_record_byte_range(&self) -> Range<usize> {
let start = self.num_records_byte_range().end;
start..start + u32::RAW_BYTE_LEN
}
fn records_byte_range(&self) -> Range<usize> {
let start = self.size_device_record_byte_range().end;
start..start + self.records_byte_len
}
}

impl TopLevelTable for Hdmx<'_> {
/// `hdmx`
const TAG: Tag = Tag::new(b"hdmx");
}

impl ReadArgs for Hdmx<'_> {
type Args = u16;
}

impl<'a> FontReadWithArgs<'a> for Hdmx<'a> {
fn read_with_args(data: FontData<'a>, args: &u16) -> Result<Self, ReadError> {
let num_glyphs = *args;
let mut cursor = data.cursor();
cursor.advance::<u16>();
let num_records: u16 = cursor.read()?;
cursor.advance::<u32>();
let records_byte_len = (num_records as usize)
.checked_mul(<DeviceRecord as ComputeSize>::compute_size(&num_glyphs)?)
.ok_or(ReadError::OutOfBounds)?;
cursor.advance_by(records_byte_len);
cursor.finish(HdmxMarker {
num_glyphs,
records_byte_len,
})
}
}

impl<'a> Hdmx<'a> {
/// A constructor that requires additional arguments.
///
/// This type requires some external state in order to be
/// parsed.
pub fn read(data: FontData<'a>, num_glyphs: u16) -> Result<Self, ReadError> {
let args = num_glyphs;
Self::read_with_args(data, &args)
}
}

/// The [Horizontal Device Metrics](https://learn.microsoft.com/en-us/typography/opentype/spec/hdmx) table.
pub type Hdmx<'a> = TableRef<'a, HdmxMarker>;

impl<'a> Hdmx<'a> {
/// Table version number (set to 0).
pub fn version(&self) -> u16 {
let range = self.shape.version_byte_range();
self.data.read_at(range.start).unwrap()
}

/// Number of device records.
pub fn num_records(&self) -> u16 {
let range = self.shape.num_records_byte_range();
self.data.read_at(range.start).unwrap()
}

/// Size of device record, 32-bit aligned.
pub fn size_device_record(&self) -> u32 {
let range = self.shape.size_device_record_byte_range();
self.data.read_at(range.start).unwrap()
}

/// Array of device records.
pub fn records(&self) -> ComputedArray<'a, DeviceRecord<'a>> {
let range = self.shape.records_byte_range();
self.data.read_with_args(range, &self.num_glyphs()).unwrap()
}

pub(crate) fn num_glyphs(&self) -> u16 {
self.shape.num_glyphs
}
}

#[cfg(feature = "experimental_traverse")]
impl<'a> SomeTable<'a> for Hdmx<'a> {
fn type_name(&self) -> &str {
"Hdmx"
}
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
match idx {
0usize => Some(Field::new("version", self.version())),
1usize => Some(Field::new("num_records", self.num_records())),
2usize => Some(Field::new("size_device_record", self.size_device_record())),
3usize => Some(Field::new(
"records",
traversal::FieldType::computed_array(
"DeviceRecord",
self.records(),
self.offset_data(),
),
)),
_ => None,
}
}
}

#[cfg(feature = "experimental_traverse")]
impl<'a> std::fmt::Debug for Hdmx<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(self as &dyn SomeTable<'a>).fmt(f)
}
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceRecord<'a> {
/// Pixel size for following widths (as ppem).
pub pixel_size: u8,
/// Maximum width.
pub max_width: u8,
/// Array of glyphs (numgGlyphs 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 glyphs (numgGlyphs is from the 'maxp' table).
pub fn widths(&self) -> &'a [u8] {
self.widths
}
}

impl ReadArgs for DeviceRecord<'_> {
type Args = u16;
}

impl ComputeSize for DeviceRecord<'_> {
#[allow(clippy::needless_question_mark)]
fn compute_size(args: &u16) -> Result<usize, ReadError> {
let num_glyphs = *args;
let mut result = 0usize;
result = result
.checked_add(u8::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
result = result
.checked_add(u8::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
result = result
.checked_add(
(num_glyphs as usize)
.checked_mul(u8::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?,
)
.ok_or(ReadError::OutOfBounds)?;
Ok(result)
}
}

impl<'a> FontReadWithArgs<'a> for DeviceRecord<'a> {
fn read_with_args(data: FontData<'a>, args: &u16) -> Result<Self, ReadError> {
let mut cursor = data.cursor();
let num_glyphs = *args;
Ok(Self {
pixel_size: cursor.read()?,
max_width: cursor.read()?,
widths: cursor.read_array(num_glyphs as usize)?,
})
}
}

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) -> Result<Self, ReadError> {
let args = num_glyphs;
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,
}
}
}
6 changes: 6 additions & 0 deletions read-fonts/src/table_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ pub trait TableProvider<'a> {
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())?;
Expand Down
1 change: 1 addition & 0 deletions read-fonts/src/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod glyf;
pub mod gpos;
pub mod gsub;
pub mod gvar;
pub mod hdmx;
pub mod head;
pub mod hhea;
pub mod hmtx;
Expand Down
90 changes: 90 additions & 0 deletions read-fonts/src/tables/hdmx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! 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
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{be_buffer, be_buffer_add, test_helpers::BeBuffer};

#[test]
fn read_hdmx() {
let buf = make_hdmx();
let hdmx = Hdmx::read(buf.font_data(), 2).unwrap();
assert_eq!(hdmx.version(), 0);
assert_eq!(hdmx.num_records(), 3);
assert_eq!(hdmx.size_device_record(), 4);
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: 12,
widths: &[10, 12],
},
DeviceRecord {
pixel_size: 16,
max_width: 20,
widths: &[18, 20],
},
DeviceRecord {
pixel_size: 32,
max_width: 40,
widths: &[38, 40],
},
];
assert_eq!(records, expected_records);
}

#[test]
fn find_by_size() {
let buf = make_hdmx();
let hdmx = Hdmx::read(buf.font_data(), 2).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
4u32, // size_device_record
// 3 records [pixel_size, max_width, width0, width1]
[8u8, 12, 10, 12],
[16u8, 20, 18, 20],
[32u8, 40, 38, 40]
}
}
}
28 changes: 28 additions & 0 deletions resources/codegen_inputs/hdmx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![parse_module(read_fonts::tables::hdmx)]

/// The [Horizontal Device Metrics](https://learn.microsoft.com/en-us/typography/opentype/spec/hdmx) table.
#[read_args(num_glyphs: u16)]
#[tag = "hdmx"]
table Hdmx {
/// Table version number (set to 0).
version: u16,
/// Number of device records.
num_records: u16,
/// Size of device record, 32-bit aligned.
size_device_record: u32,
/// Array of device records.
#[count($num_records)]
#[read_with($num_glyphs)]
records: ComputedArray<DeviceRecord<'a>>,
}

#[read_args(num_glyphs: u16)]
record DeviceRecord<'a> {
/// Pixel size for following widths (as ppem).
pixel_size: u8,
/// Maximum width.
max_width: u8,
/// Array of glyphs (numgGlyphs is from the 'maxp' table).
#[count($num_glyphs)]
widths: [u8],
}
Loading

0 comments on commit d4e29ac

Please sign in to comment.