1
//! Reading of the WOFF font format.
2

            
3
use flate2::bufread::ZlibDecoder;
4

            
5
use crate::binary::read::{ReadArray, ReadBinary, ReadBuf, ReadCtxt, ReadFrom, ReadScope};
6
use crate::binary::U32Be;
7
use crate::error::ParseError;
8
use crate::tables::{FontTableProvider, SfntVersion};
9

            
10
use std::borrow::Cow;
11
use std::io::Read;
12

            
13
/// The magic number identifying a WOFF file: 'wOFF'
14
pub const MAGIC: u32 = 0x774F4646;
15

            
16
#[derive(Clone)]
17
pub struct WoffFont<'a> {
18
    pub scope: ReadScope<'a>,
19
    pub woff_header: WoffHeader,
20
    pub table_directory: ReadArray<'a, TableDirectoryEntry>,
21
}
22

            
23
#[derive(Clone, Debug)]
24
pub struct WoffHeader {
25
    pub flavor: u32,
26
    pub length: u32,
27
    pub num_tables: u16,
28
    pub total_sfnt_size: u32,
29
    pub _major_version: u16,
30
    pub _minor_version: u16,
31
    pub meta_offset: u32,
32
    pub meta_length: u32,
33
    pub meta_orig_length: u32,
34
    pub priv_offset: u32,
35
    pub priv_length: u32,
36
}
37

            
38
#[derive(Debug, Clone)]
39
pub struct TableDirectoryEntry {
40
    pub tag: u32,
41
    pub offset: u32,
42
    pub comp_length: u32,
43
    pub orig_length: u32,
44
    pub orig_checksum: u32,
45
}
46

            
47
impl WoffFont<'_> {
48
    /// The "sfnt version" of the input font
49
    pub fn flavor(&self) -> u32 {
50
        self.woff_header.flavor
51
    }
52

            
53
    /// Decompress and return the extended metadata XML if present
54
    pub fn extended_metadata(&self) -> Result<Option<String>, ParseError> {
55
        let offset = usize::try_from(self.woff_header.meta_offset)?;
56
        let length = usize::try_from(self.woff_header.meta_length)?;
57
        if offset == 0 || length == 0 {
58
            return Ok(None);
59
        }
60

            
61
        let compressed_metadata = self.scope.offset_length(offset, length)?;
62
        let mut z = ZlibDecoder::new(compressed_metadata.data());
63
        let mut metadata = String::new();
64
        z.read_to_string(&mut metadata)
65
            .map_err(|_err| ParseError::CompressionError)?;
66

            
67
        Ok(Some(metadata))
68
    }
69

            
70
    /// Find the table directory entry for the given `tag`
71
    pub fn find_table_directory_entry(&self, tag: u32) -> Option<TableDirectoryEntry> {
72
        self.table_directory
73
            .iter()
74
            .find(|table_entry| table_entry.tag == tag)
75
    }
76
}
77

            
78
impl ReadBinary for WoffFont<'_> {
79
    type HostType<'a> = WoffFont<'a>;
80

            
81
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
82
        let scope = ctxt.scope();
83
        let woff_header = ctxt.read::<WoffHeader>()?;
84
        let table_directory =
85
            ctxt.read_array::<TableDirectoryEntry>(usize::from(woff_header.num_tables))?;
86
        Ok(WoffFont {
87
            scope,
88
            woff_header,
89
            table_directory,
90
        })
91
    }
92
}
93

            
94
impl FontTableProvider for WoffFont<'_> {
95
    fn table_data(&self, tag: u32) -> Result<Option<Cow<'_, [u8]>>, ParseError> {
96
        self.find_table_directory_entry(tag)
97
            .map(|table_entry| {
98
                table_entry
99
                    .read_table(&self.scope)
100
                    .map(|table| table.into_data())
101
            })
102
            .transpose()
103
    }
104

            
105
    fn has_table(&self, tag: u32) -> bool {
106
        self.find_table_directory_entry(tag).is_some()
107
    }
108

            
109
    fn table_tags(&self) -> Option<Vec<u32>> {
110
        Some(self.table_directory.iter().map(|entry| entry.tag).collect())
111
    }
112
}
113

            
114
impl SfntVersion for WoffFont<'_> {
115
    fn sfnt_version(&self) -> u32 {
116
        self.flavor()
117
    }
118
}
119

            
120
impl ReadBinary for WoffHeader {
121
    type HostType<'a> = Self;
122

            
123
    fn read(ctxt: &mut ReadCtxt<'_>) -> Result<Self, ParseError> {
124
        let signature = ctxt.read_u32be()?;
125
        match signature {
126
            MAGIC => {
127
                let flavor = ctxt.read_u32be()?;
128
                let length = ctxt.read_u32be()?;
129
                let num_tables = ctxt.read_u16be()?;
130
                let reserved = ctxt.read_u16be()?;
131
                // The header includes a reserved field; this MUST be set to zero. If this field is
132
                // non-zero, a conforming user agent MUST reject the file as invalid.
133
                ctxt.check(reserved == 0)?;
134
                let total_sfnt_size = ctxt.read_u32be()?;
135
                // The WOFF majorVersion and minorVersion fields specify the version number for a
136
                // given WOFF file, which can be based on the version number of the input font but
137
                // is not required to be. These fields have no effect on font loading or usage
138
                // behavior in user agents.
139
                let _major_version = ctxt.read_u16be()?;
140
                let _minor_version = ctxt.read_u16be()?;
141
                let meta_offset = ctxt.read_u32be()?;
142
                let meta_length = ctxt.read_u32be()?;
143
                let meta_orig_length = ctxt.read_u32be()?;
144
                let priv_offset = ctxt.read_u32be()?;
145
                let priv_length = ctxt.read_u32be()?;
146

            
147
                Ok(WoffHeader {
148
                    flavor,
149
                    length,
150
                    num_tables,
151
                    total_sfnt_size,
152
                    _major_version,
153
                    _minor_version,
154
                    meta_offset,
155
                    meta_length,
156
                    meta_orig_length,
157
                    priv_offset,
158
                    priv_length,
159
                })
160
            }
161
            _ => Err(ParseError::BadVersion),
162
        }
163
    }
164
}
165

            
166
impl ReadFrom for TableDirectoryEntry {
167
    type ReadType = ((U32Be, U32Be, U32Be), (U32Be, U32Be));
168
    fn read_from(
169
        ((tag, offset, comp_length), (orig_length, orig_checksum)): ((u32, u32, u32), (u32, u32)),
170
    ) -> Self {
171
        TableDirectoryEntry {
172
            tag,
173
            offset,
174
            comp_length,
175
            orig_length,
176
            orig_checksum,
177
        }
178
    }
179
}
180

            
181
impl TableDirectoryEntry {
182
    fn is_compressed(&self) -> bool {
183
        self.comp_length != self.orig_length
184
    }
185

            
186
    /// Read and uncompress the contents of a table entry
187
    pub fn read_table<'a>(&self, scope: &ReadScope<'a>) -> Result<ReadBuf<'a>, ParseError> {
188
        let offset = usize::try_from(self.offset)?;
189
        let length = usize::try_from(self.comp_length)?;
190
        let table_data = scope.offset_length(offset, length)?;
191

            
192
        if self.is_compressed() {
193
            let mut z = ZlibDecoder::new(table_data.data());
194
            let mut uncompressed = Vec::new();
195
            z.read_to_end(&mut uncompressed)
196
                .map_err(|_err| ParseError::CompressionError)?;
197

            
198
            Ok(ReadBuf::from(uncompressed))
199
        } else {
200
            Ok(ReadBuf::from(table_data.data()))
201
        }
202
    }
203
}