1use bytes::Buf;
4use thiserror::Error;
5
6use crate::{
7 checksum::{calculate_font_checksum_adjustment, set_checksum_adjustment, ChecksumError},
8 magic_numbers::{TTF_CFF_FLAVOR, TTF_COLLECTION_FLAVOR, TTF_TRUE_TYPE_FLAVOR, WOFF2_SIGNATURE},
9 ttf_header::{calculate_header_size, TableDirectory},
10 woff2::{
11 collection_directory::{CollectionHeader, CollectionHeaderError},
12 header::{Woff2Header, Woff2HeaderError},
13 table_directory::{TableDirectoryError, Woff2TableDirectory, WriteTablesError, HEAD_TAG},
14 },
15};
16
17#[derive(Error, Debug)]
18pub enum DecodeError {
19 #[error("Invalid Woff2 File {0}")]
20 Invalid(String),
21 #[error("Unsupported feature {0}")]
22 Unsupported(&'static str),
23}
24
25impl From<ChecksumError> for DecodeError {
26 fn from(e: ChecksumError) -> Self {
27 DecodeError::Invalid(e.to_string())
28 }
29}
30
31impl From<CollectionHeaderError> for DecodeError {
32 fn from(e: CollectionHeaderError) -> Self {
33 DecodeError::Invalid(e.to_string())
34 }
35}
36
37impl From<TableDirectoryError> for DecodeError {
38 fn from(e: TableDirectoryError) -> Self {
39 DecodeError::Invalid(e.to_string())
40 }
41}
42
43impl From<Woff2HeaderError> for DecodeError {
44 fn from(e: Woff2HeaderError) -> Self {
45 DecodeError::Invalid(e.to_string())
46 }
47}
48
49impl From<WriteTablesError> for DecodeError {
50 fn from(e: WriteTablesError) -> Self {
51 match e {
52 WriteTablesError::Unsupported(e) => DecodeError::Unsupported(e),
53 _ => DecodeError::Invalid(e.to_string()),
54 }
55 }
56}
57
58impl From<std::io::Error> for DecodeError {
59 fn from(e: std::io::Error) -> Self {
60 DecodeError::Invalid(e.to_string())
61 }
62}
63
64pub fn is_woff2(input_buffer: &[u8]) -> bool {
66 input_buffer.starts_with(&WOFF2_SIGNATURE.0)
67}
68
69pub fn convert_woff2_to_ttf(input_buffer: &mut impl Buf) -> Result<Vec<u8>, DecodeError> {
71 let header = Woff2Header::from_buf(input_buffer)?;
72 header.is_valid_header()?;
73
74 if !matches!(
75 header.flavor,
76 TTF_COLLECTION_FLAVOR | TTF_CFF_FLAVOR | TTF_TRUE_TYPE_FLAVOR
77 ) {
78 Err(DecodeError::Invalid("Invalid font flavor".to_string()))?;
79 }
80
81 let table_directory = Woff2TableDirectory::from_buf(input_buffer, header.num_tables)?;
82
83 let mut collection_header = if header.flavor == TTF_COLLECTION_FLAVOR {
84 Some(CollectionHeader::from_buf(input_buffer, header.num_tables)?)
85 } else {
86 None
87 };
88
89 let mut decompressed_tables =
93 Vec::with_capacity(table_directory.uncompressed_length.try_into().unwrap());
94
95 brotli::BrotliDecompress(&mut input_buffer.reader(), &mut decompressed_tables)?;
96
97 let mut out_buffer = Vec::with_capacity(header.total_sfnt_size as usize);
106 let header_end = if let Some(collection_header) = &collection_header {
109 collection_header.calculate_header_size()
110 } else {
111 calculate_header_size(table_directory.tables.len())
112 };
113 out_buffer.resize(header_end, 0);
114 let ttf_tables = table_directory.write_to_buf(&mut out_buffer, &decompressed_tables)?;
115
116 let mut header_buffer = &mut out_buffer[..header_end];
117 if let Some(collection_header) = &mut collection_header {
118 for font in &mut collection_header.fonts {
120 font.table_indices
121 .sort_unstable_by_key(|&idx| ttf_tables[idx as usize].tag.0);
122 }
123 collection_header.write_to_buf(&mut header_buffer, &ttf_tables);
124 } else {
125 let ttf_header = TableDirectory::new(header.flavor, ttf_tables);
126 ttf_header.write_to_buf(&mut header_buffer);
127 let head_table_record = ttf_header
129 .find_table(HEAD_TAG)
130 .ok_or_else(|| DecodeError::Invalid("Missing `head` table".into()))?;
131 let checksum_adjustment = calculate_font_checksum_adjustment(&out_buffer);
132 let head_table = &mut out_buffer[head_table_record.get_range()];
133 set_checksum_adjustment(head_table, checksum_adjustment)?;
134 }
135
136 Ok(out_buffer)
137}
138
139#[cfg(test)]
140mod tests {
141 use std::io::Cursor;
142
143 use crate::test_resources::{FONTAWESOME_REGULAR_400, LATO_V22_LATIN_REGULAR};
144
145 use super::convert_woff2_to_ttf;
146
147 #[test]
148 fn read_sample_font() {
149 let buffer = LATO_V22_LATIN_REGULAR;
150 let ttf = convert_woff2_to_ttf(&mut Cursor::new(buffer)).unwrap();
151 assert_eq!(None, ttf_parser::fonts_in_collection(&ttf));
152 let _parsed_ttf = ttf_parser::Face::from_slice(&ttf, 1).unwrap();
153 }
154 #[test]
155 fn read_loca_is_not_after_glyf_font() {
158 let buffer = FONTAWESOME_REGULAR_400;
160 let ttf = convert_woff2_to_ttf(&mut Cursor::new(buffer)).unwrap();
161 assert_eq!(None, ttf_parser::fonts_in_collection(&ttf));
162 let _parsed_ttf = ttf_parser::Face::from_slice(&ttf, 1).unwrap();
163 }
164
165 #[test]
166 fn sample_font_is_woff2() {
167 assert!(super::is_woff2(LATO_V22_LATIN_REGULAR));
168 }
169}