allsorts_subset_browser/
woff.rs

1//! Reading of the WOFF font format.
2
3use flate2::bufread::ZlibDecoder;
4
5use crate::binary::read::{ReadArray, ReadBinary, ReadBuf, ReadCtxt, ReadFrom, ReadScope};
6use crate::binary::U32Be;
7use crate::error::ParseError;
8use crate::tables::{FontTableProvider, SfntVersion};
9
10use std::borrow::Cow;
11use std::convert::TryFrom;
12use std::io::Read;
13
14/// The magic number identifying a WOFF file: 'wOFF'
15pub const MAGIC: u32 = 0x774F4646;
16
17#[derive(Clone)]
18pub struct WoffFont<'a> {
19    pub scope: ReadScope<'a>,
20    pub woff_header: WoffHeader,
21    pub table_directory: ReadArray<'a, TableDirectoryEntry>,
22}
23
24#[derive(Clone, Debug)]
25pub struct WoffHeader {
26    pub flavor: u32,
27    pub length: u32,
28    pub num_tables: u16,
29    pub total_sfnt_size: u32,
30    pub _major_version: u16,
31    pub _minor_version: u16,
32    pub meta_offset: u32,
33    pub meta_length: u32,
34    pub meta_orig_length: u32,
35    pub priv_offset: u32,
36    pub priv_length: u32,
37}
38
39#[derive(Debug, Clone)]
40pub struct TableDirectoryEntry {
41    pub tag: u32,
42    pub offset: u32,
43    pub comp_length: u32,
44    pub orig_length: u32,
45    pub orig_checksum: u32,
46}
47
48impl<'a> WoffFont<'a> {
49    /// The "sfnt version" of the input font
50    pub fn flavor(&self) -> u32 {
51        self.woff_header.flavor
52    }
53
54    /// Decompress and return the extended metadata XML if present
55    pub fn extended_metadata(&self) -> Result<Option<String>, ParseError> {
56        let offset = usize::try_from(self.woff_header.meta_offset)?;
57        let length = usize::try_from(self.woff_header.meta_length)?;
58        if offset == 0 || length == 0 {
59            return Ok(None);
60        }
61
62        let compressed_metadata = self.scope.offset_length(offset, length)?;
63        let mut z = ZlibDecoder::new(compressed_metadata.data());
64        let mut metadata = String::new();
65        z.read_to_string(&mut metadata)
66            .map_err(|_err| ParseError::CompressionError)?;
67
68        Ok(Some(metadata))
69    }
70
71    /// Find the table directory entry for the given `tag`
72    pub fn find_table_directory_entry(&self, tag: u32) -> Option<TableDirectoryEntry> {
73        self.table_directory
74            .iter()
75            .find(|table_entry| table_entry.tag == tag)
76    }
77}
78
79impl<'b> ReadBinary for WoffFont<'b> {
80    type HostType<'a> = WoffFont<'a>;
81
82    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
83        let scope = ctxt.scope();
84        let woff_header = ctxt.read::<WoffHeader>()?;
85        let table_directory =
86            ctxt.read_array::<TableDirectoryEntry>(usize::from(woff_header.num_tables))?;
87        Ok(WoffFont {
88            scope,
89            woff_header,
90            table_directory,
91        })
92    }
93}
94
95impl<'a> FontTableProvider for WoffFont<'a> {
96    fn table_data(&self, tag: u32) -> Result<Option<Cow<'_, [u8]>>, ParseError> {
97        self.find_table_directory_entry(tag)
98            .map(|table_entry| {
99                table_entry
100                    .read_table(&self.scope)
101                    .map(|table| table.into_data())
102            })
103            .transpose()
104    }
105
106    fn has_table(&self, tag: u32) -> bool {
107        self.find_table_directory_entry(tag).is_some()
108    }
109
110    fn table_tags(&self) -> Option<Vec<u32>> {
111        Some(self.table_directory.iter().map(|entry| entry.tag).collect())
112    }
113}
114
115impl<'a> SfntVersion for WoffFont<'a> {
116    fn sfnt_version(&self) -> u32 {
117        self.flavor()
118    }
119}
120
121impl ReadBinary for WoffHeader {
122    type HostType<'a> = Self;
123
124    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self, ParseError> {
125        let signature = ctxt.read_u32be()?;
126        match signature {
127            MAGIC => {
128                let flavor = ctxt.read_u32be()?;
129                let length = ctxt.read_u32be()?;
130                let num_tables = ctxt.read_u16be()?;
131                let reserved = ctxt.read_u16be()?;
132                // The header includes a reserved field; this MUST be set to zero. If this field is
133                // non-zero, a conforming user agent MUST reject the file as invalid.
134                ctxt.check(reserved == 0)?;
135                let total_sfnt_size = ctxt.read_u32be()?;
136                // The WOFF majorVersion and minorVersion fields specify the version number for a
137                // given WOFF file, which can be based on the version number of the input font but
138                // is not required to be. These fields have no effect on font loading or usage
139                // behavior in user agents.
140                let _major_version = ctxt.read_u16be()?;
141                let _minor_version = ctxt.read_u16be()?;
142                let meta_offset = ctxt.read_u32be()?;
143                let meta_length = ctxt.read_u32be()?;
144                let meta_orig_length = ctxt.read_u32be()?;
145                let priv_offset = ctxt.read_u32be()?;
146                let priv_length = ctxt.read_u32be()?;
147
148                Ok(WoffHeader {
149                    flavor,
150                    length,
151                    num_tables,
152                    total_sfnt_size,
153                    _major_version,
154                    _minor_version,
155                    meta_offset,
156                    meta_length,
157                    meta_orig_length,
158                    priv_offset,
159                    priv_length,
160                })
161            }
162            _ => Err(ParseError::BadVersion),
163        }
164    }
165}
166
167impl ReadFrom for TableDirectoryEntry {
168    type ReadType = ((U32Be, U32Be, U32Be), (U32Be, U32Be));
169    fn read_from(
170        ((tag, offset, comp_length), (orig_length, orig_checksum)): ((u32, u32, u32), (u32, u32)),
171    ) -> Self {
172        TableDirectoryEntry {
173            tag,
174            offset,
175            comp_length,
176            orig_length,
177            orig_checksum,
178        }
179    }
180}
181
182impl TableDirectoryEntry {
183    fn is_compressed(&self) -> bool {
184        self.comp_length != self.orig_length
185    }
186
187    /// Read and uncompress the contents of a table entry
188    pub fn read_table<'a>(&self, scope: &ReadScope<'a>) -> Result<ReadBuf<'a>, ParseError> {
189        let offset = usize::try_from(self.offset)?;
190        let length = usize::try_from(self.comp_length)?;
191        let table_data = scope.offset_length(offset, length)?;
192
193        if self.is_compressed() {
194            let mut z = ZlibDecoder::new(table_data.data());
195            let mut uncompressed = Vec::new();
196            z.read_to_end(&mut uncompressed)
197                .map_err(|_err| ParseError::CompressionError)?;
198
199            Ok(ReadBuf::from(uncompressed))
200        } else {
201            Ok(ReadBuf::from(table_data.data()))
202        }
203    }
204}