1use core::iter;
4
5use crate::{
6 alloc::{vec, Vec},
7 font::Cursor,
8 Font, TableTag,
9};
10
11#[cfg(feature = "woff2")]
12mod brotli;
13#[cfg(test)]
14mod tests;
15#[cfg(feature = "woff2")]
16mod woff2;
17
18pub(crate) trait WriteTable {
20 fn tag(&self) -> TableTag;
21
22 fn write_to_vec(&self, buffer: &mut Vec<u8>);
23}
24
25impl WriteTable for (TableTag, Cursor<'_>) {
26 fn tag(&self) -> TableTag {
27 self.0
28 }
29
30 fn write_to_vec(&self, buffer: &mut Vec<u8>) {
31 buffer.extend_from_slice(self.1.bytes());
32 }
33}
34
35pub(crate) trait VecExt {
37 fn write_u16(&mut self, value: u16);
38
39 fn write_i16(&mut self, value: i16);
40
41 fn write_u32(&mut self, value: u32);
42
43 fn write_i32(&mut self, value: i32);
44
45 fn write_u64(&mut self, value: u64);
46
47 fn write_i64(&mut self, value: i64);
48}
49
50impl VecExt for Vec<u8> {
51 fn write_u16(&mut self, value: u16) {
52 self.extend_from_slice(&value.to_be_bytes());
53 }
54
55 fn write_i16(&mut self, value: i16) {
56 self.extend_from_slice(&value.to_be_bytes());
57 }
58
59 fn write_u32(&mut self, value: u32) {
60 self.extend_from_slice(&value.to_be_bytes());
61 }
62
63 fn write_i32(&mut self, value: i32) {
64 self.extend_from_slice(&value.to_be_bytes());
65 }
66
67 fn write_u64(&mut self, value: u64) {
68 self.extend_from_slice(&value.to_be_bytes());
69 }
70
71 fn write_i64(&mut self, value: i64) {
72 self.extend_from_slice(&value.to_be_bytes());
73 }
74}
75
76impl Font<'_> {
77 pub fn to_opentype(&self) -> Vec<u8> {
79 self.to_writer().into_opentype()
80 }
81
82 #[cfg(feature = "woff2")]
84 #[cfg_attr(docsrs, doc(cfg(feature = "woff2")))]
85 pub fn to_woff2(&self) -> Vec<u8> {
86 self.to_writer().into_woff2()
87 }
88
89 fn to_writer(&self) -> FontWriter {
90 let mut writer = FontWriter::default();
91 writer.write(&self.cmap);
92 if let Some(variable) = &self.variable {
93 writer.write(&variable.stat);
94 writer.write(&variable.fvar);
95 writer.write(&variable.gvar);
96 }
97 writer.write(&self.hmtx);
98 writer.write(&self.hhea);
99 writer.write(&self.maxp);
100 writer.write(&self.name);
101 writer.write(&self.os2);
102 writer.write(&self.post);
103 writer.write(&self.glyf);
105 writer.write(&self.loca);
106 writer.write(&self.head);
107
108 for tag_and_cursor in &self.unparsed {
109 writer.write(tag_and_cursor);
110 }
111 writer
112 }
113}
114
115#[derive(Debug, Clone, Copy)]
116#[cfg_attr(test, derive(PartialEq))]
117struct TableRecord {
118 tag: TableTag,
119 checksum: u32,
120 offset: u32,
122 length: u32,
123}
124
125impl TableRecord {
126 fn write_opentype(&self, buffer: &mut Vec<u8>) {
127 buffer.extend_from_slice(&self.tag.0);
128 buffer.write_u32(self.checksum);
129 buffer.write_u32(self.offset);
130 buffer.write_u32(self.length);
131 }
132
133 fn self_checksum(&self) -> u32 {
134 u32::from_be_bytes(self.tag.0)
135 .wrapping_add(self.checksum)
136 .wrapping_add(self.offset)
137 .wrapping_add(self.length)
138 }
139}
140
141#[derive(Debug, Clone, Default)]
142struct FontWriter {
143 tables: Vec<TableRecord>,
144 table_data: Vec<u8>,
146}
147
148impl FontWriter {
149 fn write_custom(&mut self, tag: TableTag, with: impl FnOnce(&mut Vec<u8>)) {
150 let offset = self.table_data.len();
151 debug_assert_eq!(offset % 4, 0, "unaligned offset: {offset}");
152
153 with(&mut self.table_data);
154 let length = self.table_data.len() - offset;
155 if length % 4 > 0 {
157 let zero_padding = 4 - length % 4;
158 self.table_data.extend(iter::repeat_n(0_u8, zero_padding));
159 }
160
161 let checksum = Font::checksum(&self.table_data[offset..]);
162 let record = TableRecord {
163 tag,
164 checksum,
165 offset: u32::try_from(offset).expect("table offset overflow"),
166 length: u32::try_from(length).expect("table length overflow"),
167 };
168 #[cfg(feature = "tracing")]
169 tracing::debug!(?record, "written table record");
170 self.tables.push(record);
171 }
172
173 fn write(&mut self, table: &impl WriteTable) {
174 self.write_custom(table.tag(), |buffer| table.write_to_vec(buffer));
175 }
176
177 fn write_sfnt_header(&self) -> Vec<u8> {
178 let mut buffer = vec![];
179 buffer.write_u32(Font::SFNT_VERSION);
180
181 let table_count = u16::try_from(self.tables.len()).unwrap();
183 buffer.write_u16(table_count);
184 let entry_selector = u16::try_from(table_count.ilog2()).unwrap();
185 let search_range = 1 << (4 + entry_selector);
186 buffer.write_u16(search_range);
187 buffer.write_u16(entry_selector);
188 let range_shift = 16 * table_count - search_range;
189 buffer.write_u16(range_shift);
190
191 debug_assert_eq!(buffer.len(), Font::SFNT_HEADER_LEN);
192 buffer
193 }
194
195 fn data_offset(&self) -> usize {
197 Font::SFNT_HEADER_LEN + self.tables.len() * Font::TABLE_RECORD_LEN
198 }
199
200 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))]
201 fn into_opentype(mut self) -> Vec<u8> {
202 let mut buffer = self.write_sfnt_header();
203 self.adjust_data(Font::checksum(&buffer));
204
205 self.tables.sort_unstable_by_key(|record| record.tag.0);
206 for record in &self.tables {
207 record.write_opentype(&mut buffer);
208 }
209 buffer.extend(self.table_data);
210 buffer
211 }
212
213 fn adjust_data(&mut self, sfnt_header_checksum: u32) {
214 let data_offset = self.data_offset();
215 let data_offset_u32 = u32::try_from(data_offset).expect("data_offset overflow");
216
217 let mut file_checksum = sfnt_header_checksum;
218 for record in &mut self.tables {
219 record.offset += data_offset_u32;
220 file_checksum = file_checksum
221 .wrapping_add(record.self_checksum())
222 .wrapping_add(record.checksum);
223 }
224 self.patch_head_table(file_checksum, data_offset);
225 }
226
227 fn checksum_adjustment_offset(&self) -> usize {
228 let head_table = self
229 .tables
230 .iter()
231 .find(|record| record.tag == TableTag::HEAD)
232 .expect("head table is always present");
233 head_table.offset as usize + Font::HEAD_CHECKSUM_OFFSET
234 }
235
236 fn patch_head_table(&mut self, file_checksum: u32, data_offset: usize) {
237 let checksum_adjustment = Font::SFNT_CHECKSUM.wrapping_sub(file_checksum);
238
239 let offset = self.checksum_adjustment_offset() - data_offset;
241 #[cfg(feature = "tracing")]
242 tracing::debug!(
243 file_checksum,
244 checksum_adjustment,
245 offset,
246 "patching `head` table"
247 );
248 self.table_data[offset..offset + 4].copy_from_slice(&checksum_adjustment.to_be_bytes());
249 }
250}