use std::io::Error as IoError;
use std::path::PathBuf;
use plist::Error as PlistError;
use quick_xml::events::attributes::AttrError;
use quick_xml::{DeError, Error as XmlError};
use thiserror::Error;
pub use crate::shared_types::ColorError;
use crate::write::CustomSerializationError;
use crate::Name;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DesignSpaceLoadError {
#[error("failed to read file")]
Io(#[from] IoError),
#[error("failed to deserialize")]
DeError(#[from] DeError),
}
#[derive(Debug, Error)]
pub enum NamingError {
#[error("item '{0}' already exists")]
Duplicate(String),
#[error("item '{0}' does not exist")]
Missing(String),
#[error("'{0}' is not a valid name")]
Invalid(String),
#[error("only the default layer may be named 'public.default'")]
ReservedName,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GlifLoadError {
#[error("failed to read file")]
Io(#[from] IoError),
#[error("failed to read or parse XML structure")]
Xml(#[from] XmlError),
#[error("error parsing XML attribute")]
XmlAttr(#[from] AttrError),
#[error("failed to parse glyph data: {0}")]
Parse(ErrorKind),
#[error("the glyph lib's 'public.objectLibs' value must be a dictionary")]
PublicObjectLibsMustBeDictionary,
#[error("the glyph lib's 'public.objectLibs' entry for the object with identifier '{0}' must be a dictionary")]
ObjectLibMustBeDictionary(String),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FontLoadError {
#[error("cannot access UFO package")]
AccessUfoDir(#[source] IoError),
#[error("failed to load data store")]
DataStore(#[source] StoreEntryError),
#[error("failed to read features.fea file")]
FeatureFile(#[source] IoError),
#[error("failed to load font info data")]
FontInfo(#[source] FontInfoLoadError),
#[error("failed to upgrade old lib.plist to current fontinfo.plist data: {0}")]
FontInfoV1Upconversion(FontInfoErrorKind),
#[error("failed to upconvert groups to the latest supported format")]
GroupsUpconversionFailure(#[source] GroupsValidationError),
#[error("failed to load images store")]
ImagesStore(#[source] StoreEntryError),
#[error("failed to load (kerning) groups")]
InvalidGroups(#[source] GroupsValidationError),
#[error("failed to load layer '{name}' from '{path}'")]
Layer {
name: String,
path: PathBuf,
source: Box<LayerLoadError>,
},
#[error("the lib.plist file must contain a dictionary (<dict>...</dict>)")]
LibFileMustBeDictionary,
#[error("missing the default layer ('glyphs' subdirectory)")]
MissingDefaultLayer,
#[error("cannot find the layercontents.plist file")]
MissingLayerContentsFile,
#[error("cannot find the metainfo.plist file")]
MissingMetaInfoFile,
#[error("failed to parse {name} file")]
ParsePlist {
name: &'static str,
source: PlistError,
},
#[error("only UFO (directory) packages are supported")]
UfoNotADir,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LayerLoadError {
#[error("failed to load glyph '{name}' from '{path}'")]
Glyph {
name: String,
path: PathBuf,
source: GlifLoadError,
},
#[error("cannot find the contents.plist file")]
MissingContentsFile,
#[error("failed to parse {name} file")]
ParsePlist {
name: &'static str,
source: PlistError,
},
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FontInfoLoadError {
#[error("failed to upgrade fontinfo.plist contents to latest UFO version data: {0}")]
FontInfoUpconversion(FontInfoErrorKind),
#[error("the lib.plist file's 'public.objectLibs' entry for the global guideline with identifier '{0}' in the fontinfo.plist file must be a dictionary")]
GlobalGuidelineLibMustBeDictionary(String),
#[error("fontinfo.plist contains invalid data: {0}")]
InvalidData(FontInfoErrorKind),
#[error("failed to parse fontinfo.plist file")]
ParsePlist(#[source] PlistError),
#[error("the lib.plist file's 'public.objectLibs' value must be a dictionary")]
PublicObjectLibsMustBeDictionary,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FontInfoErrorKind {
DisallowedSelectionBits,
DuplicateGuidelineIdentifiers,
EmptyWoffAttribute(&'static str),
InvalidOpenTypeHeadCreatedDate,
InvalidOs2FamilyClass,
InvalidOs2Panose,
InvalidPostscriptListLength {
name: &'static str,
max_len: u8,
len: usize,
},
PostscriptListMustBePairs(&'static str),
UnknownFontStyle(i32),
UnknownMsCharSet(i32),
UnknownStyleMapStyleName,
UnknownWidthClass(String),
UnknownWoffDirection,
UnsortedGaspEntries,
}
impl std::fmt::Display for FontInfoErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use FontInfoErrorKind::*;
match self {
DisallowedSelectionBits => {
write!(f, "openTypeOS2Selection must not contain bits 0, 5 or 6")
}
DuplicateGuidelineIdentifiers => {
write!(f, "guideline identifiers must be unique within the fontinfo.plist file")
}
EmptyWoffAttribute(s) => {
write!(f, "a '{}' element must not be empty", s)
}
InvalidOpenTypeHeadCreatedDate => {
write!(f, "openTypeHeadCreated must be of format 'YYYY/MM/DD HH:MM:SS'")
}
InvalidOs2FamilyClass => {
write!(f, "openTypeOS2FamilyClass must be two numbers in the range 0-14 and 0-15, respectively")
}
InvalidOs2Panose => {
write!(f, "openTypeOS2Panose must have exactly ten elements")
}
InvalidPostscriptListLength { name, max_len, len } => {
write!(
f,
"the Postscript field '{}' must contain at most {} items but found {}",
name, max_len, len
)
}
PostscriptListMustBePairs(name) => {
write!(f, "the Postscript field '{}' must contain pairs", name)
}
UnknownFontStyle(s) => {
write!(f, "unrecognized fontStyle '{}'", s)
}
UnknownMsCharSet(c) => {
write!(f, "unrecognized msCharSet '{}'", c)
}
UnknownStyleMapStyleName => {
write!(f, "unknown value for styleMapStyleName")
}
UnknownWidthClass(w) => {
write!(f, "unrecognized OS/2 width class '{}'", w)
}
UnknownWoffDirection => {
write!(f, "unknown value for the WOFF direction attribute")
}
UnsortedGaspEntries => {
write!(f, "openTypeGaspRangeRecords must be sorted by their rangeMaxPPEM values")
}
}
}
}
#[derive(Debug, Error)]
#[error("store entry '{path}' is invalid")]
pub struct StoreEntryError {
path: PathBuf,
source: StoreError,
}
impl StoreEntryError {
pub(crate) fn new(path: PathBuf, source: StoreError) -> Self {
Self { path, source }
}
}
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum StoreError {
#[error("the parent of the file is a file itself")]
DirUnderFile,
#[error("an empty path cannot be used as a key in the store")]
EmptyPath,
#[error("only plain files and directories are allowed, no symlinks")]
NotPlainFileOrDir,
#[error("the path must be relative")]
PathIsAbsolute,
#[error("only plain files are allowed, no symlinks")]
NotPlainFile,
#[error("subdirectories are not allowed in the image store")]
Subdir,
#[error("an image must be a valid PNG")]
InvalidImage,
#[error("encountered an IO error while trying to load content")]
Io(#[from] std::sync::Arc<std::io::Error>),
}
#[derive(Debug, Error)]
pub enum GroupsValidationError {
#[error("a kerning group name must have at least one character after the common 'public.kernN.' prefix.")]
InvalidName,
#[error("the glyph '{glyph_name}' appears in more than one kerning group. Last found in '{group_name}'")]
OverlappingKerningGroups {
glyph_name: Name,
group_name: Name,
},
}
#[derive(Debug, Error)]
#[error("expected a positive value")]
pub struct ExpectedPositiveValue;
#[cfg(feature = "kurbo")]
#[derive(Debug, Error)]
#[error("failed to convert contour: '{0}'")]
pub struct ConvertContourError(ErrorKind);
#[cfg(feature = "kurbo")]
impl ConvertContourError {
pub(crate) fn new(kind: ErrorKind) -> Self {
ConvertContourError(kind)
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FontWriteError {
#[error("failed to remove target directory before overwriting")]
Cleanup(#[source] IoError),
#[error("failed to create store directory '{path}'")]
CreateStoreDir {
path: PathBuf,
source: IoError,
},
#[error("failed to create target font directory")]
CreateUfoDir(#[source] IoError),
#[error("failed to write {name} file")]
CustomFile {
name: &'static str,
source: CustomSerializationError,
},
#[error("failed to write data file")]
Data {
path: PathBuf,
source: IoError,
},
#[error("downgrading below UFO v3 is not currently supported")]
Downgrade,
#[error("failed to write feature.fea file")]
FeatureFile(#[source] IoError),
#[error("failed to write image file")]
Image {
path: PathBuf,
source: IoError,
},
#[error("font info contains invalid data: {0}")]
InvalidFontInfo(FontInfoErrorKind),
#[error("failed to write (kerning) groups")]
InvalidGroups(#[source] GroupsValidationError),
#[error("store entry '{path}' is invalid")]
InvalidStoreEntry {
path: PathBuf,
source: StoreError,
},
#[error("failed to write layer '{name}' to '{path}'")]
Layer {
name: String,
path: PathBuf,
source: Box<LayerWriteError>,
},
#[error("the `public.objectLibs` lib key is managed by norad and must not be set manually")]
PreexistingPublicObjectLibsKey,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LayerWriteError {
#[error("failed to write contents.plist file")]
Contents(#[source] CustomSerializationError),
#[error("cannot create layer directory")]
CreateDir(#[source] IoError),
#[error("failed to write glyph '{name}' to '{path}'")]
Glyph {
name: String,
path: PathBuf,
source: GlifWriteError,
},
#[error("failed to write layerinfo.plist file")]
LayerInfo(#[source] CustomSerializationError),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GlifWriteError {
#[error("failed to serialize glyph to an internal buffer")]
Buffer(#[source] IoError),
#[error("internal error while writing lib data, please open an issue")]
InternalLibWriteError,
#[error("failed to write .glif file")]
Io(#[source] IoError),
#[error("error serializing glyph lib data internally")]
Plist(#[source] PlistError),
#[error("the `public.objectLibs` lib key is managed by norad and must not be set manually")]
PreexistingPublicObjectLibsKey,
#[error("error serializing glyph to XML")]
Xml(#[source] XmlError),
}
#[derive(Debug, Clone, Copy)]
pub enum ErrorKind {
UnsupportedGlifVersion,
UnknownPointType,
WrongFirstElement,
MissingCloseTag,
BadHexValue,
BadNumber,
BadColor,
BadAnchor,
BadPoint,
BadGuideline,
BadComponent,
BadImage,
BadIdentifier,
InvalidName,
BadLib,
UnexpectedDuplicate,
UnexpectedMove,
UnexpectedSmooth,
UnexpectedElement,
UnexpectedAttribute,
UnexpectedEof,
UnexpectedPointAfterOffCurve,
TooManyOffCurves,
PenPathNotStarted,
TrailingOffCurves,
DuplicateIdentifier,
UnexpectedDrawing,
UnfinishedDrawing,
UnexpectedPointField,
UnexpectedComponentField,
UnexpectedAnchorField,
UnexpectedGuidelineField,
UnexpectedImageField,
DuplicateElement(&'static str),
UnexpectedV1Element(&'static str),
UnexpectedV1Attribute(&'static str),
ComponentEmptyBase,
ComponentMissingBase,
LibMustBeDictionary,
BadAngle,
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use ErrorKind::*;
match self {
UnsupportedGlifVersion => write!(f, "unsupported glif version"),
UnknownPointType => write!(f, "unknown point type"),
WrongFirstElement => write!(f, "wrong first XML element in glif file"),
MissingCloseTag => write!(f, "missing close tag"),
BadHexValue => write!(f, "bad hex value"),
BadNumber => write!(f, "bad number"),
BadColor => write!(f, "bad color"),
BadAnchor => write!(f, "bad anchor"),
BadPoint => write!(f, "bad point"),
BadGuideline => write!(f, "bad guideline"),
BadComponent => write!(f, "bad component"),
BadImage => write!(f, "bad image"),
BadIdentifier => write!(f, "an identifier must be at most 100 characters long and contain only ASCII characters in the range 0x20 to 0x7E"),
InvalidName => write!(f, "name is empty or contains control characters"),
BadLib => write!(f, "bad lib"),
UnexpectedDuplicate => write!(f, "unexpected duplicate"),
UnexpectedMove => {
write!(f, "unexpected move point, can only occur at start of contour")
}
UnexpectedSmooth => write!(f, "unexpected smooth attribute on an off-curve point"),
UnexpectedElement => write!(f, "unexpected element"),
UnexpectedAttribute => write!(f, "unexpected attribute"),
UnexpectedEof => write!(f, "unexpected EOF"),
UnexpectedPointAfterOffCurve => {
write!(f, "an off-curve point must be followed by a curve or qcurve")
}
TooManyOffCurves => write!(f, "at most two off-curve points can precede a curve"),
PenPathNotStarted => {
write!(f, "must call begin_path() before calling add_point() or end_path()")
}
TrailingOffCurves => write!(f, "open contours must not have trailing off-curves"),
DuplicateIdentifier => write!(f, "duplicate identifier"),
UnexpectedDrawing => write!(f, "unexpected drawing without an outline"),
UnfinishedDrawing => write!(f, "unfinished drawing, you must call end_path"),
UnexpectedPointField => write!(f, "unexpected point field"),
UnexpectedComponentField => write!(f, "unexpected component field"),
UnexpectedAnchorField => write!(f, "unexpected anchor field"),
UnexpectedGuidelineField => write!(f, "unexpected guideline field"),
UnexpectedImageField => write!(f, "unexpected image field"),
DuplicateElement(s) => write!(f, "there must be only one '{}' element", s),
UnexpectedV1Element(s) => write!(f, "unexpected element in a format 1 glif: {}", s),
UnexpectedV1Attribute(s) => write!(f, "unexpected attribute in a format 1 glif: {}", s),
ComponentEmptyBase => write!(f, "a 'component' element has an empty 'base' attribute"),
ComponentMissingBase => {
write!(f, "a 'component' element is missing a 'base' attribute")
}
LibMustBeDictionary => write!(f, "the glyph lib must be a dictionary"),
BadAngle => write!(f, "an angle must be between 0 and 360°"),
}
}
}
#[doc(hidden)]
impl From<ErrorKind> for GlifLoadError {
fn from(src: ErrorKind) -> Self {
Self::Parse(src)
}
}
#[doc(hidden)]
impl From<IoError> for StoreError {
fn from(src: IoError) -> StoreError {
StoreError::Io(std::sync::Arc::new(src))
}
}