1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! Reading of the WOFF font format.

use crate::binary::read::{ReadArray, ReadBinary, ReadBuf, ReadCtxt, ReadFrom, ReadScope};
use crate::binary::U32Be;
use crate::error::ParseError;
use crate::tables::FontTableProvider;

use alloc::borrow::Cow;
use alloc::string::String;
use core::convert::TryFrom;

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

#[derive(Clone)]
pub struct WoffFont<'a> {
    pub scope: ReadScope<'a>,
    pub woff_header: WoffHeader,
    pub table_directory: ReadArray<'a, TableDirectoryEntry>,
}

#[derive(Clone, Debug)]
pub struct WoffHeader {
    pub flavor: u32,
    pub length: u32,
    pub num_tables: u16,
    pub total_sfnt_size: u32,
    pub _major_version: u16,
    pub _minor_version: u16,
    pub meta_offset: u32,
    pub meta_length: u32,
    pub meta_orig_length: u32,
    pub priv_offset: u32,
    pub priv_length: u32,
}

#[derive(Debug, Clone)]
pub struct TableDirectoryEntry {
    pub tag: u32,
    pub offset: u32,
    pub comp_length: u32,
    pub orig_length: u32,
    pub orig_checksum: u32,
}

impl<'a> WoffFont<'a> {
    /// The "sfnt version" of the input font
    pub fn flavor(&self) -> u32 {
        self.woff_header.flavor
    }

    /// Decompress and return the extended metadata XML if present
    pub fn extended_metadata(&self) -> Result<Option<String>, ParseError> {
        use miniz_oxide::inflate::decompress_to_vec;

        let offset = usize::try_from(self.woff_header.meta_offset)?;
        let length = usize::try_from(self.woff_header.meta_length)?;
        if offset == 0 || length == 0 {
            return Ok(None);
        }

        let compressed_metadata = self.scope.offset_length(offset, length)?;
        let metadata_uncompressed = decompress_to_vec(compressed_metadata.data()).map_err(|_err| ParseError::CompressionError)?;
        let metadata_string = String::from_utf8(metadata_uncompressed).map_err(|_err| ParseError::CompressionError)?;

        Ok(Some(metadata_string))
    }

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

impl<'a> ReadBinary<'a> for WoffFont<'a> {
    type HostType = Self;

    fn read(ctxt: &mut ReadCtxt<'a>) -> Result<Self, ParseError> {
        let scope = ctxt.scope();
        let mut peek = ctxt.clone();
        let magic = peek.read_u32be()?;
        match magic {
            MAGIC => {
                let woff_header = ctxt.read::<WoffHeader>()?;
                let table_directory =
                    ctxt.read_array::<TableDirectoryEntry>(usize::from(woff_header.num_tables))?;
                Ok(WoffFont {
                    scope,
                    woff_header,
                    table_directory,
                })
            }
            _ => Err(ParseError::BadVersion),
        }
    }
}

impl<'a> FontTableProvider for WoffFont<'a> {
    fn table_data<'b>(&'b self, tag: u32) -> Result<Option<Cow<'b, [u8]>>, ParseError> {
        self.find_table_directory_entry(tag)
            .map(|table_entry| {
                table_entry
                    .read_table(&self.scope)
                    .map(|table| table.into_data())
            })
            .transpose()
    }

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

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

    fn read(ctxt: &mut ReadCtxt<'a>) -> Result<Self, ParseError> {
        let signature = ctxt.read_u32be()?;
        match signature {
            MAGIC => {
                let flavor = ctxt.read_u32be()?;
                let length = ctxt.read_u32be()?;
                let num_tables = ctxt.read_u16be()?;
                let reserved = ctxt.read_u16be()?;
                // The header includes a reserved field; this MUST be set to zero. If this field is
                // non-zero, a conforming user agent MUST reject the file as invalid.
                ctxt.check(reserved == 0)?;
                let total_sfnt_size = ctxt.read_u32be()?;
                // The WOFF majorVersion and minorVersion fields specify the version number for a
                // given WOFF file, which can be based on the version number of the input font but
                // is not required to be. These fields have no effect on font loading or usage
                // behavior in user agents.
                let _major_version = ctxt.read_u16be()?;
                let _minor_version = ctxt.read_u16be()?;
                let meta_offset = ctxt.read_u32be()?;
                let meta_length = ctxt.read_u32be()?;
                let meta_orig_length = ctxt.read_u32be()?;
                let priv_offset = ctxt.read_u32be()?;
                let priv_length = ctxt.read_u32be()?;

                Ok(WoffHeader {
                    flavor,
                    length,
                    num_tables,
                    total_sfnt_size,
                    _major_version,
                    _minor_version,
                    meta_offset,
                    meta_length,
                    meta_orig_length,
                    priv_offset,
                    priv_length,
                })
            }
            _ => Err(ParseError::BadVersion),
        }
    }
}

impl<'a> ReadFrom<'a> for TableDirectoryEntry {
    type ReadType = ((U32Be, U32Be, U32Be), (U32Be, U32Be));
    fn from(
        ((tag, offset, comp_length), (orig_length, orig_checksum)): ((u32, u32, u32), (u32, u32)),
    ) -> Self {
        TableDirectoryEntry {
            tag,
            offset,
            comp_length,
            orig_length,
            orig_checksum,
        }
    }
}

impl TableDirectoryEntry {
    fn is_compressed(&self) -> bool {
        self.comp_length != self.orig_length
    }

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

        if self.is_compressed() {
            use miniz_oxide::inflate::decompress_to_vec;
            let table_uncompressed = decompress_to_vec(table_data.data())
            .map_err(|_err| ParseError::CompressionError)?;
            Ok(ReadBuf::from(table_uncompressed))
        } else {
            Ok(ReadBuf::from(table_data.data()))
        }
    }
}