use core::{fmt, ops};
use crate::{
alloc::{format, vec, String, Vec},
TableTag,
};
#[derive(Debug)]
#[non_exhaustive]
pub enum ParseErrorKind {
UnexpectedEof,
UnexpectedValue {
name: &'static str,
expected: String,
actual: u32,
},
InvalidCharCode(u32),
MissingTable,
UnalignedTable,
NoSupportedCmap,
OffsetOutOfBounds(usize),
RangeOutOfBounds {
range: ops::Range<usize>,
len: usize,
},
UnexpectedTableLen {
expected: usize,
actual: usize,
},
Checksum {
expected: u32,
actual: u32,
},
Utf16,
UintBase128,
UnsupportedWoff2Table {
tag: TableTag,
transform_bits: u8,
},
TooLargeFont(usize),
BrotliDecompression,
}
impl fmt::Display for ParseErrorKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedEof => formatter.write_str("unexpected end of the font data"),
Self::UnexpectedValue {
name,
expected,
actual,
} => {
write!(
formatter,
"unexpected value of `{name}`: expected {expected}, got {actual}"
)
}
Self::InvalidCharCode(val) => {
write!(formatter, "invalid Unicode char code: {val}")
}
Self::MissingTable => formatter.write_str("missing required font table"),
Self::UnalignedTable => {
formatter.write_str("font table is not aligned to a 4-byte boundary")
}
Self::NoSupportedCmap => {
formatter.write_str("no supported subtable in the `cmap` table")
}
Self::OffsetOutOfBounds(val) => {
write!(
formatter,
"offset ({val}) inferred from the table data is out of bounds"
)
}
Self::RangeOutOfBounds { range, len } => {
write!(
formatter,
"range ({range:?}) inferred from the table data is out of bounds (..{len})"
)
}
Self::UnexpectedTableLen { expected, actual } => {
write!(
formatter,
"unexpected table length: expected {expected}, got {actual}"
)
}
Self::Checksum { expected, actual } => {
write!(
formatter,
"unexpected checksum: expected {expected}, got {actual}"
)
}
Self::Utf16 => formatter.write_str("failed decoding UTF-16 string"),
Self::UintBase128 => formatter.write_str("failed decoding uint128 value"),
Self::UnsupportedWoff2Table {
tag,
transform_bits,
} => {
write!(
formatter,
"unsupported WOFF2 table tag ({tag}) with transform {transform_bits}"
)
}
Self::TooLargeFont(len) => {
write!(formatter, "font size ({len}) is too large to be processed")
}
Self::BrotliDecompression => formatter.write_str("Brotli decompression error"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseErrorKind {}
macro_rules! check_exact {
($val:ident, $expected:expr) => {
if $val == $expected {
Ok(())
} else {
Err($crate::ParseErrorKind::UnexpectedValue {
name: ::core::stringify!($val),
expected: $crate::alloc::ToString::to_string(&$expected),
actual: u32::from($val),
})
}
};
}
#[derive(Debug)]
pub struct ParseError {
pub(crate) kind: ParseErrorKind,
pub(crate) offset: usize,
pub(crate) table: Option<TableTag>,
}
impl fmt::Display for ParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(table) = self.table {
write!(formatter, "[{table}] ")?;
}
if self.offset > 0 {
write!(formatter, "{}: ", self.offset)?;
}
fmt::Display::fmt(&self.kind, formatter)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {}
impl ParseError {
pub(crate) fn missing_table(tag: TableTag) -> Self {
Self {
kind: ParseErrorKind::MissingTable,
offset: 0,
table: Some(tag),
}
}
pub fn kind(&self) -> &ParseErrorKind {
&self.kind
}
pub fn table(&self) -> Option<TableTag> {
self.table
}
pub fn offset(&self) -> usize {
self.offset
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum WarningKind {
ValueMismatch {
name: &'static str,
computed: String,
recorded: String,
},
}
impl fmt::Display for WarningKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ValueMismatch {
name,
computed,
recorded,
} => {
write!(
formatter,
"mismatch between computed ({computed}) and recorded ({recorded}) values of `{name}`"
)
}
}
}
}
#[derive(Debug)]
pub struct Warning {
kind: WarningKind,
table: Option<TableTag>,
}
impl fmt::Display for Warning {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(table) = self.table {
write!(formatter, "[{table}] ")?;
}
fmt::Display::fmt(&self.kind, formatter)
}
}
#[cfg(feature = "std")]
impl std::error::Error for Warning {}
impl Warning {
pub fn kind(&self) -> &WarningKind {
&self.kind
}
pub fn table(&self) -> Option<TableTag> {
self.table
}
}
#[derive(Debug)]
pub struct Warnings(Vec<Warning>);
impl fmt::Display for Warnings {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for warn in &self.0 {
writeln!(formatter, "- {warn}")?;
}
Ok(())
}
}
impl IntoIterator for Warnings {
type Item = Warning;
type IntoIter = vec::IntoIter<Warning>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cfg(feature = "std")]
impl std::error::Error for Warnings {}
impl Warnings {
pub(crate) fn empty() -> Self {
Self(vec![])
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &Warning> + '_ {
self.0.iter()
}
pub(crate) fn for_table(&mut self, table: TableTag) -> TableWarnings<'_> {
TableWarnings { inner: self, table }
}
pub fn into_result(self) -> Result<(), Self> {
if self.0.is_empty() {
Ok(())
} else {
Err(self)
}
}
}
#[derive(Debug)]
pub(crate) struct TableWarnings<'a> {
inner: &'a mut Warnings,
table: TableTag,
}
impl TableWarnings<'_> {
pub(crate) fn check_match<T: Copy + PartialEq + fmt::Debug>(
&mut self,
name: &'static str,
computed: T,
recorded: T,
) {
if computed != recorded {
self.inner.0.push(Warning {
kind: WarningKind::ValueMismatch {
name,
computed: format!("{computed:?}"),
recorded: format!("{recorded:?}"),
},
table: Some(self.table),
});
}
}
}