Coverage Report

Created: 2026-10-28 07:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontations/read-fonts/src/tables/hdmx.rs
Line
Count
Source
1
//! The [Horizontal Device Metrics](https://learn.microsoft.com/en-us/typography/opentype/spec/hdmx) table.
2
3
include!("../../generated/generated_hdmx.rs");
4
5
use std::cmp::Ordering;
6
7
impl<'a> Hdmx<'a> {
8
    /// Returns for the device record that exactly matches the given
9
    /// size (as ppem).
10
0
    pub fn record_for_size(&self, size: u8) -> Option<DeviceRecord<'a>> {
11
0
        let records = self.records();
12
        // Need a custom binary search because we're working with
13
        // ComputedArray
14
0
        let mut lo = 0;
15
0
        let mut hi = records.len();
16
0
        while lo < hi {
17
0
            let mid = (lo + hi) / 2;
18
0
            let record = records.get(mid).ok()?;
19
0
            match record.pixel_size.cmp(&size) {
20
0
                Ordering::Less => lo = mid + 1,
21
0
                Ordering::Greater => hi = mid,
22
0
                Ordering::Equal => return Some(record),
23
            }
24
        }
25
0
        None
26
0
    }
27
}
28
29
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
30
pub struct DeviceRecord<'a> {
31
    /// Pixel size for following widths (as ppem).
32
    pub pixel_size: u8,
33
    /// Maximum width.
34
    pub max_width: u8,
35
    /// Array of glyphs (numGlyphs is from the 'maxp' table).
36
    pub widths: &'a [u8],
37
}
38
39
impl<'a> DeviceRecord<'a> {
40
    /// Pixel size for following widths (as ppem).
41
0
    pub fn pixel_size(&self) -> u8 {
42
0
        self.pixel_size
43
0
    }
44
45
    /// Maximum width.
46
0
    pub fn max_width(&self) -> u8 {
47
0
        self.max_width
48
0
    }
49
50
    /// Array of widths, indexed by glyph id.
51
0
    pub fn widths(&self) -> &'a [u8] {
52
0
        self.widths
53
0
    }
54
}
55
56
crate::impl_font_read_at!(DeviceRecord<'a>);
57
58
impl ReadArgs for DeviceRecord<'_> {
59
    type Args = (u32, u32);
60
}
61
62
impl ComputeSize for DeviceRecord<'_> {
63
0
    fn compute_size(args: (u32, u32)) -> Result<usize, ReadError> {
64
0
        let (_num_glyphs, size_device_record) = args;
65
        // Record size is explicitly defined in the parent hdmx table
66
0
        Ok(size_device_record as usize)
67
0
    }
68
}
69
70
impl<'a> FontRead<'a> for DeviceRecord<'a> {
71
0
    fn read_with_args(data: FontData<'a>, args: (u32, u32)) -> Result<Self, ReadError> {
72
0
        let mut cursor = data.cursor();
73
0
        let (num_glyphs, _size_device_record) = args;
74
        Ok(Self {
75
0
            pixel_size: cursor.read()?,
76
0
            max_width: cursor.read()?,
77
0
            widths: cursor.read_array(num_glyphs as usize)?,
78
        })
79
0
    }
80
}
81
82
#[allow(clippy::needless_lifetimes)]
83
impl<'a> DeviceRecord<'a> {
84
    /// A constructor that requires additional arguments.
85
    ///
86
    /// This type requires some external state in order to be
87
    /// parsed.
88
0
    pub fn read(
89
0
        data: FontData<'a>,
90
0
        num_glyphs: u32,
91
0
        size_device_record: u32,
92
0
    ) -> Result<Self, ReadError> {
93
0
        let args = (num_glyphs, size_device_record);
94
0
        Self::read_with_args(data, args)
95
0
    }
96
}
97
98
#[cfg(test)]
99
mod tests {
100
    use super::*;
101
    use font_test_data::{be_buffer, bebuffer::BeBuffer};
102
103
    #[test]
104
    fn read_hdmx() {
105
        let buf = make_hdmx();
106
        let hdmx = Hdmx::read(buf.data().into(), 3).unwrap();
107
        assert_eq!(hdmx.version(), 0);
108
        assert_eq!(hdmx.num_records(), 3);
109
        // Note: this table has sizes for 3 glyphs making each device
110
        // record 5 bytes in actual length, but each entry in the array
111
        // should be aligned to 32-bits so the size is bumped to 8 bytes
112
        //
113
        // See the HdmxHeader::sizeDeviceRecord field at
114
        // <https://learn.microsoft.com/en-us/typography/opentype/spec/hdmx>
115
        // "Size of a device record, 32-bit aligned."
116
        assert_eq!(hdmx.size_device_record(), 8);
117
        let records = hdmx
118
            .records()
119
            .iter()
120
            .map(|rec| rec.unwrap())
121
            .collect::<Vec<_>>();
122
        assert_eq!(records.len(), 3);
123
        let expected_records = [
124
            DeviceRecord {
125
                pixel_size: 8,
126
                max_width: 13,
127
                widths: &[10, 12, 13],
128
            },
129
            DeviceRecord {
130
                pixel_size: 16,
131
                max_width: 21,
132
                widths: &[18, 20, 21],
133
            },
134
            DeviceRecord {
135
                pixel_size: 32,
136
                max_width: 52,
137
                widths: &[38, 40, 52],
138
            },
139
        ];
140
        assert_eq!(records, expected_records);
141
    }
142
143
    #[test]
144
    fn find_by_size() {
145
        let buf = make_hdmx();
146
        let hdmx = Hdmx::read(buf.data().into(), 3).unwrap();
147
        assert_eq!(hdmx.record_for_size(8).unwrap().pixel_size, 8);
148
        assert_eq!(hdmx.record_for_size(16).unwrap().pixel_size, 16);
149
        assert_eq!(hdmx.record_for_size(32).unwrap().pixel_size, 32);
150
        assert!(hdmx.record_for_size(7).is_none());
151
        assert!(hdmx.record_for_size(20).is_none());
152
        assert!(hdmx.record_for_size(72).is_none());
153
    }
154
155
    fn make_hdmx() -> BeBuffer {
156
        be_buffer! {
157
            0u16,           // version
158
            3u16,           // num_records
159
            8u32,           // size_device_record
160
            // 3 records [pixel_size, max_width, width0, width1, ..padding]
161
            [8u8, 13, 10, 12, 13, 0, 0, 0],
162
            [16u8, 21, 18, 20, 21, 0, 0, 0],
163
            [32u8, 52, 38, 40, 52, 0, 0, 0]
164
        }
165
    }
166
}