Skip to main content

eot_parse/
metadata.rs

1// Copyright (c) 2013-2026 Brennan T. Vincent <brennanv@email.arizona.edu>
2// Copyright (c) 2026 Kiƫd Llaentenn <kiedtl@tilde.team>
3//
4// This file is part of eot-rs, which is based on libeot, which is licensed under the MPL license,
5// version 2.0. For full details, read the LICENSE and PATENTS file.
6
7use std::io::{Cursor, Read, Seek, SeekFrom};
8
9use byteorder::{LE, ReadBytesExt};
10
11use crate::core::*;
12
13#[derive(Clone)]
14#[repr(C)]
15pub struct Metadata {
16    pub total_size: u32,
17    pub version: EOTVersion,
18    pub flags: u32,
19    pub panose: [u8; 10],
20    pub charset: EOTCharset,
21    pub italic: bool,
22    pub weight: u32,
23    pub permissions: u16,
24    pub unicode_range: [u32; 4],
25    pub code_page_range: [u32; 2],
26    pub check_sum_adjustment: u32,
27    pub family_name: Vec<u16>,
28    pub style_name: Vec<u16>,
29    pub version_name: Vec<u16>,
30    pub full_name: Vec<u16>,
31    pub num_root_strings: ::core::ffi::c_uint,
32    pub font_data_size: u32,
33    pub font_data_offset: u32,
34    pub eudc_info: EUDCInfo,
35    pub do_not_use: Vec<u16>,
36}
37
38impl Metadata {
39    pub const ZERO: Metadata = Metadata {
40        total_size: 0,
41        version: 0 as EOTVersion,
42        flags: 0,
43        panose: [0; 10],
44        charset: ANSI_CHARSET,
45        italic: false,
46        weight: 0,
47        permissions: 0,
48        unicode_range: [0; 4],
49        code_page_range: [0; 2],
50        check_sum_adjustment: 0,
51        family_name: Vec::new(),
52        style_name: Vec::new(),
53        version_name: Vec::new(),
54        full_name: Vec::new(),
55        do_not_use: Vec::new(),
56        num_root_strings: 0,
57        font_data_size: 0,
58        font_data_offset: 0,
59        eudc_info: EUDCInfo {
60            exists: false,
61            code_page: 0,
62            flags: 0,
63            font_data: Vec::new(),
64        },
65    };
66}
67
68fn skip(c: &mut Cursor<&[u8]>, amount: u16) -> Result<(), Error> {
69    c.seek(SeekFrom::Current(amount as i64)).map_err(|_| Error::INSUFFICIENT_BYTES)?;
70    Ok(())
71}
72
73fn skip_padding(c: &mut Cursor<&[u8]>, amount: u16) -> Result<(), Error> {
74    for _ in 0..(amount as usize) {
75        if c.read_u8().map_err(|_| Error::INSUFFICIENT_BYTES)? != 0 {
76            return Err(Error::CORRUPT_FILE_PADDING_NOT_ZERO);
77        }
78    }
79    Ok(())
80}
81
82fn read_u32_le2(c: &mut Cursor<&[u8]>) -> Result<u32, Error> {
83    c.read_u32::<LE>().map_err(|_| Error::INSUFFICIENT_BYTES)
84}
85
86fn read_u16_le2(c: &mut Cursor<&[u8]>) -> Result<u16, Error> {
87    c.read_u16::<LE>().map_err(|_| Error::INSUFFICIENT_BYTES)
88}
89
90/// Returns (total_length, metadata_length, font_length)
91fn read_metadata_length(c: &mut Cursor<&[u8]>) -> Result<(u32, u32, u32), Error> {
92    let total_length = read_u32_le2(c)?;
93    let font_length = read_u32_le2(c)?;
94    if let Some(diff) = total_length.checked_sub(font_length) {
95        Ok((total_length, diff, font_length))
96    } else {
97        Err(Error::CORRUPT_FILE)
98    }
99}
100
101fn read_u16_array(c: &mut Cursor<&[u8]>) -> Result<Vec<u16>, Error> {
102    let size = read_u16_le2(c)? as usize;
103
104    if !size.is_multiple_of(2) {
105        return Err(Error::BOGUS_STRING_SIZE);
106    }
107
108    let mut buf = Vec::with_capacity(size / 2);
109    for _ in 0..size / 2 {
110        buf.push(read_u16_le2(c)?);
111    }
112
113    Ok(buf)
114}
115
116fn read_byte_array(c: &mut Cursor<&[u8]>) -> Result<Vec<u8>, Error> {
117    let size = read_u32_le2(c)? as usize;
118
119    let mut buf = Vec::with_capacity(size);
120    for _ in 0..size {
121        buf.push(c.read_u8().map_err(|_| Error::INSUFFICIENT_BYTES)?);
122    }
123
124    Ok(buf)
125}
126
127fn read_metadata_with_version(
128    c: &mut Cursor<&[u8]>, meta: &mut Metadata, version: EOTVersion,
129) -> Result<(), Error> {
130    meta.version = version;
131
132    meta.flags = read_u32_le2(c)?;
133    c.read_exact(&mut meta.panose).map_err(|_| Error::INSUFFICIENT_BYTES)?;
134    meta.charset = c.read_u8().map_err(|_| Error::INSUFFICIENT_BYTES)? as u32;
135    meta.italic = c.read_u8().map_err(|_| Error::INSUFFICIENT_BYTES)? != 0;
136    meta.weight = read_u32_le2(c)?;
137    meta.permissions = read_u16_le2(c)?;
138
139    if read_u16_le2(c)? != 0x504c {
140        return Err(Error::CORRUPT_FILE);
141    }
142
143    for i in 0..4 {
144        meta.unicode_range[i] = read_u32_le2(c)?;
145    }
146
147    for i in 0..2 {
148        meta.code_page_range[i] = read_u32_le2(c)?;
149    }
150
151    meta.check_sum_adjustment = read_u32_le2(c)?;
152    skip(c, 16)?; // Reserved
153
154    skip_padding(c, 2)?;
155    meta.family_name = read_u16_array(c)?;
156
157    skip_padding(c, 2)?;
158    meta.style_name = read_u16_array(c)?;
159
160    skip_padding(c, 2)?;
161    meta.version_name = read_u16_array(c)?;
162
163    skip_padding(c, 2)?;
164    meta.full_name = read_u16_array(c)?;
165
166    if meta.version > VERSION_1 {
167        skip_padding(c, 2)?;
168        meta.do_not_use = read_u16_array(c)?;
169
170        if meta.version == VERSION_3 {
171            skip(c, 4)?; // RootStringChecksum: unused
172            meta.eudc_info.code_page = read_u32_le2(c)?;
173
174            skip_padding(c, 2)?;
175
176            // Signature is reserved and not used (must be zeroed), so do nothing with this.
177            let signature_size = read_u16_le2(c)?;
178            skip_padding(c, signature_size)?;
179
180            meta.eudc_info.flags = read_u32_le2(c)?;
181            meta.eudc_info.font_data = read_byte_array(c)?;
182            meta.eudc_info.exists = !meta.eudc_info.font_data.is_empty();
183        }
184    }
185
186    // The cursor spans the whole file, so its position is already the absolute
187    // offset of the font data; no base offset needs to be added here.
188    meta.font_data_offset = c.position() as u32;
189    let expected_header_size = meta.total_size.wrapping_sub(meta.font_data_size);
190    if meta.font_data_offset < expected_header_size {
191        return Err(Error::HEADER_TOO_BIG);
192    }
193
194    Ok(())
195}
196
197pub fn read_metadata(bytes: &[u8]) -> Result<Metadata, Error> {
198    let mut c = Cursor::new(bytes);
199    let (total_size, metadata_size, font_data_size) = read_metadata_length(&mut c)?;
200
201    if bytes.len() < metadata_size as usize {
202        return Err(Error::INSUFFICIENT_BYTES);
203    }
204
205    let coded_version = match read_u32_le2(&mut c)? {
206        0x00010000 => VERSION_1,
207        0x00020001 => VERSION_2,
208        0x00020002 => VERSION_3,
209        _ => return Err(Error::CORRUPT_FILE),
210    };
211
212    let mut try_version = coded_version;
213    let mut bumped_up = false;
214    let mut knocked_down = false;
215
216    loop {
217        let mut met = Metadata::ZERO;
218        met.total_size = total_size;
219        met.font_data_size = font_data_size;
220        let pos = c.position() as usize;
221
222        if bytes.len() < met.font_data_size as usize + pos {
223            return Err(Error::CORRUPT_FILE);
224        }
225
226        match read_metadata_with_version(&mut c, &mut met, try_version) {
227            Ok(()) =>
228                if try_version == coded_version {
229                    return Ok(met);
230                } else {
231                    return Err(Error::WARN_BAD_VERSION);
232                },
233            Err(Error::HEADER_TOO_BIG) => {
234                if knocked_down || try_version == VERSION_3 {
235                    return Err(Error::CORRUPT_FILE);
236                }
237                knocked_down = false;
238                bumped_up = true;
239                try_version += 1;
240            }
241            Err(Error::INSUFFICIENT_BYTES) => {
242                if bumped_up || try_version == VERSION_1 {
243                    return Err(Error::CORRUPT_FILE);
244                }
245                knocked_down = true;
246                bumped_up = false;
247                try_version -= 1;
248            }
249            Err(e) => return Err(e),
250        }
251    }
252}
253
254/// Please think twice before circumventing this function. Does your personal sense of morality
255/// really let you take others' work without their permission?
256///
257/// I'm not suggesting any system of morality is right or wrong; I'm merely asking that you reflect
258/// on it before changing anything here.
259pub fn can_legally_edit(metadata: &Metadata) -> bool {
260    const EDITING_MASK: u16 = 0x8;
261    metadata.permissions == 0 || ((metadata.permissions & EDITING_MASK) != 0)
262}