/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 | | } |