read_fonts/
read.rs

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
//! Traits for interpreting font data

#![deny(clippy::arithmetic_side_effects)]

use types::{FixedSize, Scalar, Tag};

use crate::font_data::FontData;

/// A type that can be read from raw table data.
///
/// This trait is implemented for all font tables that are self-describing: that
/// is, tables that do not require any external state in order to interpret their
/// underlying bytes. (Tables that require external state implement
/// [`FontReadWithArgs`] instead)
pub trait FontRead<'a>: Sized {
    /// Read an instance of `Self` from the provided data, performing validation.
    ///
    /// In the case of a table, this method is responsible for ensuring the input
    /// data is consistent: this means ensuring that any versioned fields are
    /// present as required by the version, and that any array lengths are not
    /// out-of-bounds.
    fn read(data: FontData<'a>) -> Result<Self, ReadError>;
}

//NOTE: this is separate so that it can be a super trait of FontReadWithArgs and
//ComputeSize, without them needing to know about each other? I'm not sure this
//is necessary, but I don't know the full hierarchy of traits I'm going to need
//yet, so this seems... okay?

/// A trait for a type that needs additional arguments to be read.
pub trait ReadArgs {
    type Args: Copy;
}

/// A trait for types that require external data in order to be constructed.
///
/// You should not need to use this directly; it is intended to be used from
/// generated code. Any type that requires external arguments also has a custom
/// `read` constructor where you can pass those arguments like normal.
pub trait FontReadWithArgs<'a>: Sized + ReadArgs {
    /// read an item, using the provided args.
    ///
    /// If successful, returns a new item of this type, and the number of bytes
    /// used to construct it.
    ///
    /// If a type requires multiple arguments, they will be passed as a tuple.
    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError>;
}

// a blanket impl of ReadArgs/FontReadWithArgs for general FontRead types.
//
// This is used by ArrayOfOffsets/ArrayOfNullableOffsets to provide a common
// interface for regardless of whether a type has args.
impl<'a, T: FontRead<'a>> ReadArgs for T {
    type Args = ();
}

impl<'a, T: FontRead<'a>> FontReadWithArgs<'a> for T {
    fn read_with_args(data: FontData<'a>, _: &Self::Args) -> Result<Self, ReadError> {
        Self::read(data)
    }
}

/// A trait for tables that have multiple possible formats.
pub trait Format<T> {
    /// The format value for this table.
    const FORMAT: T;
}

/// A type that can compute its size at runtime, based on some input.
///
/// For types with a constant size, see [`FixedSize`] and
/// for types which store their size inline, see [`VarSize`].
pub trait ComputeSize: ReadArgs {
    /// Compute the number of bytes required to represent this type.
    fn compute_size(args: &Self::Args) -> Result<usize, ReadError>;
}

/// A trait for types that have variable length.
///
/// As a rule, these types have an initial length field.
///
/// For types with a constant size, see [`FixedSize`] and
/// for types which can pre-compute their size, see [`ComputeSize`].
pub trait VarSize {
    /// The type of the first (length) field of the item.
    ///
    /// When reading this type, we will read this value first, and use it to
    /// determine the total length.
    type Size: Scalar + Into<u32>;

    #[doc(hidden)]
    fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
        let asu32 = data.read_at::<Self::Size>(pos).ok()?.into();
        (asu32 as usize).checked_add(Self::Size::RAW_BYTE_LEN)
    }

    /// Determine the total length required to store `count` items of `Self` in
    /// `data` starting from `start`.
    #[doc(hidden)]
    fn total_len_for_count(data: FontData, count: usize) -> Result<usize, ReadError> {
        (0..count).try_fold(0usize, |current_pos, _i| {
            Self::read_len_at(data, current_pos)
                .and_then(|i_len| current_pos.checked_add(i_len))
                .ok_or(ReadError::OutOfBounds)
        })
    }
}

/// An error that occurs when reading font data
#[derive(Debug, Clone, PartialEq)]
pub enum ReadError {
    OutOfBounds,
    // i64 is flexible enough to store any value we might encounter
    InvalidFormat(i64),
    InvalidSfnt(u32),
    InvalidTtc(Tag),
    InvalidCollectionIndex(u32),
    InvalidArrayLen,
    ValidationError,
    NullOffset,
    TableIsMissing(Tag),
    MetricIsMissing(Tag),
    MalformedData(&'static str),
}

impl std::fmt::Display for ReadError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            ReadError::OutOfBounds => write!(f, "An offset was out of bounds"),
            ReadError::InvalidFormat(x) => write!(f, "Invalid format '{x}'"),
            ReadError::InvalidSfnt(ver) => write!(f, "Invalid sfnt version 0x{ver:08X}"),
            ReadError::InvalidTtc(tag) => write!(f, "Invalid ttc tag {tag}"),
            ReadError::InvalidCollectionIndex(ix) => {
                write!(f, "Invalid index {ix} for font collection")
            }
            ReadError::InvalidArrayLen => {
                write!(f, "Specified array length not a multiple of item size")
            }
            ReadError::ValidationError => write!(f, "A validation error occurred"),
            ReadError::NullOffset => write!(f, "An offset was unexpectedly null"),
            ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
            ReadError::MetricIsMissing(tag) => write!(f, "the {tag} metric is missing"),
            ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for ReadError {}