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 {}