font_subset/font/
woff2.rs1use super::{Cursor, Font};
4use crate::{alloc::Vec, utils::brotli, ParseError, ParseErrorKind, TableTag};
5
6impl Cursor<'_> {
7 fn read_u8(&mut self) -> Result<u8, ParseError> {
8 let [a, rest @ ..] = self.bytes else {
9 return Err(self.err(ParseErrorKind::UnexpectedEof));
10 };
11 self.bytes = rest;
12 self.offset += 1;
13 Ok(*a)
14 }
15
16 pub(crate) fn read_uint_base128(&mut self) -> Result<u32, ParseError> {
18 let offset = self.offset;
19 let mut val = 0_u32;
20 for _ in 0..5 {
21 let byte = self.read_u8()?;
22 val = (val << 7) + u32::from(byte & 0x7f);
23 if byte < 0x80 {
24 return Ok(val);
26 }
27 }
28 Err(ParseError {
29 kind: ParseErrorKind::UintBase128,
30 table: self.table,
31 offset,
32 })
33 }
34}
35
36impl TableTag {
37 pub(crate) const NULL_TRANSFORM_MASK: u8 = 0b_1100_0000;
38
39 fn parse_woff2(cursor: &mut Cursor<'_>) -> Result<Self, ParseError> {
40 let start_cursor = *cursor;
42
43 let raw_tag = cursor.read_u8()?;
44 let tag = Self::from_u8(raw_tag);
45 let tag = if let Some(tag) = tag {
46 tag
47 } else {
48 Self(cursor.read_byte_array::<4>()?)
49 };
50
51 let transform_bits = raw_tag & Self::NULL_TRANSFORM_MASK;
52 let expected_transform = match tag {
53 Self::GLYF | Self::LOCA => Self::NULL_TRANSFORM_MASK,
54 _ => 0,
55 };
56 if transform_bits != expected_transform {
57 return Err(start_cursor.err(ParseErrorKind::UnsupportedWoff2Table {
58 tag,
59 transform_bits: transform_bits >> 6,
60 }));
61 }
62
63 Ok(tag)
64 }
65}
66
67#[derive(Debug, Clone, Copy)]
68struct Woff2TableRecord {
69 tag: TableTag,
70 len: u32,
71}
72
73impl Woff2TableRecord {
74 fn parse(cursor: &mut Cursor<'_>) -> Result<Self, ParseError> {
75 let tag = TableTag::parse_woff2(cursor)?;
76 let len = cursor.read_uint_base128()?;
77 #[cfg(feature = "tracing")]
79 tracing::debug!(?tag, len, "parsed table record");
80 Ok(Self { tag, len })
81 }
82}
83
84impl Font<'_> {
85 pub(crate) const WOFF2_SIGNATURE: u32 = 0x_774f_4632;
86}
87
88#[cfg_attr(docsrs, doc(cfg(feature = "woff2")))]
94#[derive(Debug, Clone)]
95pub struct Woff2Reader {
96 table_records: Vec<Woff2TableRecord>,
97 table_data: Vec<u8>,
98}
99
100impl Woff2Reader {
101 #[allow(clippy::missing_panics_doc)] #[cfg_attr(
110 feature = "tracing",
111 tracing::instrument(
112 level = "debug",
113 name = "Woff2Reader::new",
114 err,
115 skip_all,
116 fields(bytes.len = bytes.len()),
117 )
118 )]
119 pub fn new(bytes: &[u8]) -> Result<Self, ParseError> {
120 let mut header_cursor = Cursor::new(bytes);
121 let bytes_len = u32::try_from(bytes.len())
122 .map_err(|_| header_cursor.err(ParseErrorKind::TooLargeFont(bytes.len())))?;
123
124 header_cursor
125 .read_u32_checked(|signature| check_exact!(signature, Font::WOFF2_SIGNATURE))?;
126 header_cursor.read_u32_checked(|version| check_exact!(version, Font::SFNT_VERSION))?;
127
128 header_cursor.read_u32_checked(|file_len| check_exact!(file_len, bytes_len))?;
129 let table_count = header_cursor.read_u16()?;
130 header_cursor.skip(6)?; let compressed_data_len = header_cursor.read_u32()?;
132 let compressed_data_len = usize::try_from(compressed_data_len).unwrap();
133 header_cursor.skip(24)?; #[cfg(feature = "tracing")]
136 tracing::debug!(table_count, compressed_data_len, "parsed header");
137
138 let table_records = (0..table_count)
139 .map(|_| Woff2TableRecord::parse(&mut header_cursor))
140 .collect::<Result<Vec<_>, _>>()?;
141
142 let data_cursor = header_cursor.read_range(0..compressed_data_len)?;
143 #[cfg(feature = "tracing")]
144 tracing::debug!(range = ?data_cursor.range(), "decompressing table data");
145 let table_data = brotli::decompress(data_cursor.bytes())
146 .map_err(|()| data_cursor.err(ParseErrorKind::BrotliDecompression))?;
147 #[cfg(feature = "tracing")]
148 tracing::debug!(table_data.len = table_data.len(), "decompressed table data");
149
150 Ok(Self {
151 table_records,
152 table_data,
153 })
154 }
155
156 pub fn opentype_len(&self) -> usize {
158 let table_size = self
159 .iter()
160 .map(|(_, cursor)| cursor.bytes().len().div_ceil(4) * 4 + Font::TABLE_RECORD_LEN)
161 .sum::<usize>();
162 table_size + Font::SFNT_HEADER_LEN
163 }
164
165 pub(crate) fn iter(&self) -> impl ExactSizeIterator<Item = (TableTag, Cursor<'_>)> + '_ {
167 let mut offset = 0_usize;
168 self.table_records.iter().map(move |record| {
169 let table_offset = offset;
170 offset += usize::try_from(record.len).unwrap();
171 let tag = record.tag;
172 let table_data = &self.table_data[table_offset..offset];
173 let table_cursor = Cursor::for_table(table_data, table_offset, tag);
174 (tag, table_cursor)
175 })
176 }
177
178 pub fn raw_tables(&self) -> impl ExactSizeIterator<Item = (TableTag, &[u8])> + '_ {
180 self.iter().map(|(tag, cursor)| (tag, cursor.bytes()))
181 }
182
183 pub fn read(&self) -> Result<Font<'_>, ParseError> {
189 Font::from_tables(self.iter())
190 }
191
192 pub(super) fn into_table_data(self) -> Vec<u8> {
193 self.table_data
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn all_woff2_tables_are_covered() {
203 for val in 0_u8..=62 {
204 let table = TableTag::from_u8(val).unwrap();
205 assert_eq!(table, TableTag::from_u8(val + 64).unwrap());
206 assert_eq!(table, TableTag::from_u8(val + 128).unwrap());
207 assert_eq!(table, TableTag::from_u8(val + 192).unwrap());
208 }
209 }
210}