use std::{fmt::Display, io, path::PathBuf};
use fontdrasil::{
coords::{DesignCoord, NormalizedCoord, NormalizedLocation, UserCoord, UserLocation},
types::GlyphName,
variations::{DeltaError, VariationModelError},
};
use kurbo::Point;
use smol_str::SmolStr;
use thiserror::Error;
use write_fonts::types::{InvalidTag, Tag};
use crate::ir::GlobalMetric;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
BadSource(#[from] BadSource),
#[error("{0} does not exist")]
NoSuchPath(PathBuf),
#[error(transparent)]
BadGlyph(#[from] BadGlyph),
#[error("Failed to delete file {path}: '{source}'")]
DeleteFailed {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("UPEM value {0} outside valid range 16..=16384")]
InvalidUpem(f64),
#[error("Inconsistent UPEM values: {0:?}")]
InconsistentUpem(Vec<u16>),
#[error("Variation model error: '{0}'")]
VariationModelError(
#[from]
#[source]
VariationModelError,
),
#[error("feature files are non-identical: {0}, {1}")]
NonIdenticalFea(PathBuf, PathBuf),
#[error("axis '{0}' missing at least one of default/min/max mapping")]
MissingAxisMapping(Tag),
#[error("no glyph for name '{0}'")]
NoGlyphForName(GlyphName),
#[error("Missing required axis values for {0}")]
NoAxisDefinitions(String),
#[error("Axis {0} has no entry in axes")]
NoEntryInAxes(String),
#[error("Axis definitions are inconsistent: '{0}'")]
InconsistentAxisDefinitions(String),
#[error("Missing layer '{0}'")]
NoSuchLayer(String),
#[error("No files associated with glyph {0}")]
NoStateForGlyph(GlyphName),
#[error("No design space location(s) associated with glyph {0}")]
NoLocationsForGlyph(GlyphName),
#[error("Asked to create work for something other than the last input we created")]
UnableToCreateGlyphIrWork,
#[error("Unexpected state encountered in a state set")]
UnexpectedState,
#[error("Duplicate location for {what}: {loc:?}")]
DuplicateUserLocation { what: String, loc: UserLocation },
#[error("Global metadata very bad, very very bad")]
InvalidGlobalMetadata,
#[error("No default master in {0}")]
NoDefaultMaster(PathBuf),
#[error("Missing mapping on {axis_name} for {field} at {value:?}. Mappings {mappings:?}")]
MissingMappingForDesignCoord {
axis_name: String,
field: String,
mappings: Vec<(UserCoord, DesignCoord)>,
value: DesignCoord,
},
#[error("Invalid tag '{raw_tag}': {cause}")]
InvalidTag { raw_tag: String, cause: InvalidTag },
#[error("Source file contained a construct we don't yet support: {0}")]
UnsupportedConstruct(String),
#[error("Inconsistent palette size, [0] has {size_0}, [{n}] has {size_n}")]
InconsistentPaletteLength {
size_0: usize,
n: usize,
size_n: usize,
},
#[error("Unknown {0}: {1}")]
UnknownEntry(&'static str, String),
#[error("Invalid {0}: {1}")]
InvalidEntry(&'static str, String),
#[error("Variation model error when variating {0:?}: {1}")]
MetricVariationError(GlobalMetric, #[source] VariationModelError),
#[error("Delta error when variating {0:?}: {1}")]
MetricDeltaError(GlobalMetric, #[source] DeltaError),
}
#[derive(Debug, Error)]
#[error("Reading source failed for '{path}': '{kind}'")]
pub struct BadSource {
path: PathBuf,
pub kind: BadSourceKind,
}
#[derive(Debug)]
pub enum BadSourceKind {
ExpectedDirectory,
ExpectedFile,
UnrecognizedExtension,
ExpectedParent,
Io(io::Error),
Custom(String),
}
#[derive(Debug, Error)]
#[error("Invalid source glyph '{name}': '{kind}'")]
pub struct BadGlyph {
name: GlyphName,
kind: BadGlyphKind,
}
#[derive(Debug)]
pub enum BadGlyphKind {
NoInstances,
DuplicateLocation(NormalizedLocation),
NoDefaultLocation,
MissingLayer(String),
MissingMaster(String),
MultipleDefaultLocations,
UndefinedAtNormalizedLocation(NormalizedLocation),
UndefinedAtNormalizedPosition { axis: Tag, pos: NormalizedCoord },
NoAxisPosition(Tag),
PathConversion(PathConversionError),
Anchor(BadAnchor),
BadDeltas(DeltaError),
FrontendSpecific(String),
}
#[derive(Debug, Error)]
#[error("Invalid anchor '{name}': '{kind}'")]
pub struct BadAnchor {
name: SmolStr,
kind: BadAnchorReason,
}
#[derive(Clone, Debug, PartialEq)]
pub enum BadAnchorReason {
Ambiguous(NormalizedLocation),
NoDefault,
ZeroIndex,
NumberedMarkAnchor,
NilMarkGroup,
}
#[derive(Debug, Error, PartialEq)]
pub enum PathConversionError {
#[error("has {num_offcurve} consecutive offcurve points {points:?}")]
TooManyOffcurvePoints {
num_offcurve: usize,
points: Vec<Point>,
},
#[error("contour contains a 'move' that is not the first point: {point}")]
MoveAfterFirstPoint { point: Point },
#[error("{0}")]
Parse(String),
}
impl Display for BadAnchorReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BadAnchorReason::NoDefault => write!(f, "no value at default location"),
BadAnchorReason::Ambiguous(loc) => write!(f, "multiple definitions at {loc:?}"),
BadAnchorReason::ZeroIndex => write!(f, "ligature indexes must begin with '1'"),
BadAnchorReason::NumberedMarkAnchor => write!(f, "mark anchors cannot be numbered"),
BadAnchorReason::NilMarkGroup => write!(f, "mark anchor key is nil"),
}
}
}
impl BadSource {
pub fn new(path: impl Into<PathBuf>, kind: impl Into<BadSourceKind>) -> Self {
Self {
path: path.into(),
kind: kind.into(),
}
}
pub fn custom(path: impl Into<PathBuf>, msg: impl Display) -> Self {
Self::new(path, BadSourceKind::Custom(msg.to_string()))
}
}
impl BadGlyph {
pub fn new(name: impl Into<GlyphName>, kind: impl Into<BadGlyphKind>) -> Self {
Self {
name: name.into(),
kind: kind.into(),
}
}
}
impl BadAnchor {
pub(crate) fn new(name: impl Into<SmolStr>, kind: impl Into<BadAnchorReason>) -> Self {
Self {
name: name.into(),
kind: kind.into(),
}
}
}
impl From<std::io::Error> for BadSourceKind {
fn from(src: std::io::Error) -> BadSourceKind {
BadSourceKind::Io(src)
}
}
impl From<PathConversionError> for BadGlyphKind {
fn from(src: PathConversionError) -> BadGlyphKind {
BadGlyphKind::PathConversion(src)
}
}
impl From<BadAnchor> for BadGlyphKind {
fn from(src: BadAnchor) -> BadGlyphKind {
BadGlyphKind::Anchor(src)
}
}
impl From<DeltaError> for BadGlyphKind {
fn from(src: DeltaError) -> BadGlyphKind {
BadGlyphKind::BadDeltas(src)
}
}
impl std::fmt::Display for BadSourceKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
BadSourceKind::ExpectedDirectory => f.write_str("expected directory"),
BadSourceKind::ExpectedFile => f.write_str("expected file"),
BadSourceKind::UnrecognizedExtension => f.write_str("unknown file extension"),
BadSourceKind::ExpectedParent => f.write_str("missing parent directory"),
BadSourceKind::Io(e) => e.fmt(f),
BadSourceKind::Custom(e) => f.write_str(e),
}
}
}
impl std::fmt::Display for BadGlyphKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
BadGlyphKind::NoInstances => f.write_str("no instances"),
BadGlyphKind::DuplicateLocation(loc) => write!(f, "duplicate location {loc:?}"),
BadGlyphKind::NoDefaultLocation => f.write_str("no default location"),
BadGlyphKind::MultipleDefaultLocations => f.write_str("multiple default locations"),
BadGlyphKind::UndefinedAtNormalizedLocation(loc) => {
write!(f, "undefined at required location {loc:?}")
}
BadGlyphKind::UndefinedAtNormalizedPosition { axis, pos } => {
write!(f, "undefined on {axis} at required position {pos:?}")
}
BadGlyphKind::PathConversion(e) => write!(f, "invalid path: '{e}'"),
BadGlyphKind::MissingLayer(name) => write!(f, "missing layer '{name}'"),
BadGlyphKind::MissingMaster(name) => write!(f, "missing master '{name}'"),
BadGlyphKind::NoAxisPosition(axis) => write!(f, "no position on '{axis}' axis"),
BadGlyphKind::Anchor(e) => write!(f, "bad anchor: '{e}'"),
BadGlyphKind::BadDeltas(e) => write!(f, "delta error: '{e}'"),
BadGlyphKind::FrontendSpecific(e) => write!(f, "{}", e),
}
}
}