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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
//! Error types.

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;

/// An error that occurs while attempting to read a designspace file from disk.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DesignSpaceLoadError {
    /// An [`std::io::Error`].
    #[error("failed to read designspace file: {0}")]
    Io(#[from] IoError),

    /// A parse error.
    #[error("failed to deserialize designspace: {0}")]
    DeError(#[from] DeError),
}

/// An error that occurs while attempting to write a designspace file to disk.
#[derive(Debug, Error)]
pub enum DesignSpaceSaveError {
    /// An [`std::io::Error`].
    #[error("failed to open/write designspace file: {0}")]
    Io(#[from] IoError),

    /// A serialization error.
    #[error("failed to serialize designspace: {0}")]
    SeError(#[from] DeError),
}

/// An error representing a failure to (re)name something.
#[derive(Debug, Error)]
pub enum NamingError {
    /// An error returned when an item is duplicated.
    #[error("item '{0}' already exists")]
    Duplicate(String),
    /// An error returned when an expected item is missing.
    #[error("item '{0}' does not exist")]
    Missing(String),
    /// A name is empty, or contains [control characters].
    ///
    /// [control characters]: https://unifiedfontobject.org/versions/ufo3/conventions/#controls
    #[error("'{0}' is not a valid name")]
    Invalid(String),
    /// An error returned when the name "public.default" is used for a non-default layer.
    #[error("only the default layer may be named 'public.default'")]
    ReservedName,
}

/// An error that occurs while attempting to read a .glif file from disk.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GlifLoadError {
    /// An [`std::io::Error`].
    #[error("failed to read file")]
    Io(#[from] IoError),
    /// A [`quick_xml::Error`].
    #[error("failed to read or parse XML structure")]
    Xml(#[from] XmlError),
    /// An error in an XML attribute
    #[error("error parsing XML attribute")]
    XmlAttr(#[from] AttrError),
    /// The .glif file was malformed.
    #[error("failed to parse glyph data: {0}")]
    Parse(ErrorKind),
    /// The glyph lib's `public.objectLibs` value was something other than a dictionary.
    #[error("the glyph lib's 'public.objectLibs' value must be a dictionary")]
    PublicObjectLibsMustBeDictionary,
    /// The entry with the given identifier within the glyph lib's `public.objectLibs` dictionary was not a dictionary.
    #[error("the glyph lib's 'public.objectLibs' entry for the object with identifier '{0}' must be a dictionary")]
    ObjectLibMustBeDictionary(String),
}

/// An error that occurs while attempting to read a UFO package from disk.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FontLoadError {
    /// The UFO cannot be accessed.
    #[error("cannot access UFO package")]
    AccessUfoDir(#[source] IoError),
    /// Failed to load a file from the data store.
    #[error("failed to load data store")]
    DataStore(#[source] StoreEntryError),
    /// Failed to load the features.fea file.
    #[error("failed to read features.fea file")]
    FeatureFile(#[source] IoError),
    /// Failed to load the fontinfo.plist file.
    #[error("failed to load font info data")]
    FontInfo(#[source] FontInfoLoadError),
    /// The upgrade process failed to move font info data from the old lib.plist schema to the new fontinfo.plist schema.
    #[error("failed to upgrade old lib.plist to current fontinfo.plist data: {0}")]
    FontInfoV1Upconversion(FontInfoErrorKind),
    /// The upgrade process failed to convert kerning groups from old to the new UFO v3 format.
    #[error("failed to upconvert groups to the latest supported format")]
    GroupsUpconversionFailure(#[source] GroupsValidationError),
    /// Failed to load a file from the image store.
    #[error("failed to load images store")]
    ImagesStore(#[source] StoreEntryError),
    /// The (kerning) groups in kerning.plist fail validation.
    #[error("failed to load (kerning) groups")]
    InvalidGroups(#[source] GroupsValidationError),
    /// Failed to load a specific layer.
    #[error("failed to load layer '{name}' from '{path}'")]
    Layer {
        /// The layer name.
        name: String,
        /// The path to the layer.
        path: PathBuf,
        /// The underlying error.
        source: Box<LayerLoadError>,
    },
    /// The lib.plist file was something other than a dictionary.
    #[error("the lib.plist file must contain a dictionary (<dict>...</dict>)")]
    LibFileMustBeDictionary,
    /// The UFO does not have a default layer.
    #[error("missing the default layer ('glyphs' subdirectory)")]
    MissingDefaultLayer,
    /// The UFO does not have a default layer.
    #[error("cannot find the layercontents.plist file")]
    MissingLayerContentsFile,
    /// The UFO does not have a metainfo.plist layer.
    #[error("cannot find the metainfo.plist file")]
    MissingMetaInfoFile,
    /// Failed to parse a .plist file.
    #[error("failed to parse {name} file")]
    ParsePlist {
        /// The name of the file.
        name: &'static str,
        /// The underlying error.
        source: PlistError,
    },
    /// Norad can currently only open UFO (directory) packages.
    #[error("only UFO (directory) packages are supported")]
    UfoNotADir,
}

/// An error that occurs while attempting to read a UFO layer from disk.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LayerLoadError {
    /// Loading a glyph from a path failed.
    #[error("failed to load glyph '{name}' from '{path}'")]
    Glyph {
        /// The glyph name.
        name: String,
        /// The path to the glif file.
        path: PathBuf,
        /// The underlying error.
        source: GlifLoadError,
    },
    /// Could not find the layer's contents.plist.
    #[error("cannot find the contents.plist file")]
    MissingContentsFile,
    /// Failed to parse a .plist file.
    #[error("failed to parse {name} file")]
    ParsePlist {
        /// The name of the file.
        name: &'static str,
        /// The underlying error.
        source: PlistError,
    },
}

/// An error that occurs while attempting to read a UFO fontinfo.plist file from disk.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FontInfoLoadError {
    /// The upgrade process failed to convert an older fontinfo.plist to newer data structures.
    #[error("failed to upgrade fontinfo.plist contents to latest UFO version data: {0}")]
    FontInfoUpconversion(FontInfoErrorKind),
    /// The UFO lib.plist's `public.objectLibs` entry for the given guideline is not a dictionary.
    #[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),
    /// The fontinfo.plist file contains invalid data.
    #[error("fontinfo.plist contains invalid data: {0}")]
    InvalidData(FontInfoErrorKind),
    /// Could not parse the UFO's fontinfo.plist.
    #[error("failed to parse fontinfo.plist file")]
    ParsePlist(#[source] PlistError),
    /// The font lib's `public.objectLibs` value was something other than a dictionary.
    #[error("the lib.plist file's 'public.objectLibs' value must be a dictionary")]
    PublicObjectLibsMustBeDictionary,
}

/// An error pointing to invalid data in the font's info.
#[derive(Debug)]
#[non_exhaustive]
pub enum FontInfoErrorKind {
    /// openTypeOS2Selection contained bits 0, 5 or 6.
    DisallowedSelectionBits,
    /// Guideline identifiers were not unique within the fontinfo.plist file.
    DuplicateGuidelineIdentifiers,
    /// Found an empty WOFF element or record. If you have them, you have to fill them all in.
    EmptyWoffAttribute(&'static str),
    /// The openTypeHeadCreated had the wrong format.
    InvalidOpenTypeHeadCreatedDate,
    /// The openTypeOS2FamilyClass had out of range values.
    InvalidOs2FamilyClass,
    /// The openTypeOS2Panose field did not have exactly ten elements.
    InvalidOs2Panose,
    /// A Postscript data list had more elements than the specification allows.
    InvalidPostscriptListLength {
        /// The name of the property.
        name: &'static str,
        /// The maximum allowed number of elements.
        max_len: u8,
        /// The found number of elements.
        len: usize,
    },
    /// A Postscript data list must contain pairs.
    PostscriptListMustBePairs(&'static str),
    /// Unrecognized UFO v1 fontStyle field.
    UnknownFontStyle(i32),
    /// Unrecognized UFO v1 msCharSet field.
    UnknownMsCharSet(i32),
    /// Unrecognized styleMapStyleName.
    UnknownStyleMapStyleName,
    /// Unrecognized openTypeOS2WidthClass.
    UnknownWidthClass(String),
    /// Unrecognized WOFF writing direction.
    UnknownWoffDirection,
    /// The openTypeGaspRangeRecords field was unsorted.
    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")
            }
        }
    }
}

/// An error representing a failure with a particular [`crate::datastore::Store`] entry.
#[derive(Debug, Error)]
#[error("store entry '{path}' is invalid")]
pub struct StoreEntryError {
    path: PathBuf,
    source: StoreError,
}

impl StoreEntryError {
    /// Returns a new [`StoreEntryError`].
    pub(crate) fn new(path: PathBuf, source: StoreError) -> Self {
        Self { path, source }
    }
}

/// An error representing a failure to insert content into a [`crate::datastore::Store`].
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum StoreError {
    /// Tried to insert a path whose ancestor is in the store already, implying nesting a file under a file.
    #[error("the parent of the file is a file itself")]
    DirUnderFile,
    /// The path was empty.
    #[error("an empty path cannot be used as a key in the store")]
    EmptyPath,
    /// The path was neither plain file nor directory, but e.g. a symlink.
    #[error("only plain files and directories are allowed, no symlinks")]
    NotPlainFileOrDir,
    /// The path was absolute; only relative paths are allowed.
    #[error("the path must be relative")]
    PathIsAbsolute,
    /// The path was not a plain file, but e.g. a directory or symlink.
    #[error("only plain files are allowed, no symlinks")]
    NotPlainFile,
    /// The path contained a subdirectory; `images` is a flat directory.
    #[error("subdirectories are not allowed in the image store")]
    Subdir,
    /// The image did not have a valid PNG header.
    #[error("an image must be a valid PNG")]
    InvalidImage,
    /// Encountered an IO error while trying to load data
    #[error("encountered an IO error while trying to load content")]
    Io(#[from] std::sync::Arc<std::io::Error>),
}

/// An error representing a failure to validate UFO groups.
#[derive(Debug, Error)]
pub enum GroupsValidationError {
    /// An error returned when there is an invalid groups name.
    #[error("a kerning group name must have at least one character after the common 'public.kernN.' prefix.")]
    InvalidName,
    /// An error returned when there are overlapping kerning groups.
    #[error("the glyph '{glyph_name}' appears in more than one kerning group. Last found in '{group_name}'")]
    OverlappingKerningGroups {
        /// The glyph name.
        glyph_name: Name,
        /// The group name.
        group_name: Name,
    },
}

/// An error returned when there is an inappropriate negative sign on a value.
#[derive(Debug, Error)]
#[error("expected a positive value")]
pub struct ExpectedPositiveValue;

/// An error returned when there is a problem with kurbo contour conversion.
#[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)
    }
}

/// An error that occurs while attempting to write a UFO package to disk.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FontWriteError {
    /// Cannot clean up previous UFO package before writing out new one.
    #[error("failed to remove target directory before overwriting")]
    Cleanup(#[source] IoError),
    /// Failed to create the images directory.
    #[error("failed to create store directory '{path}'")]
    CreateStoreDir {
        /// The path to the entry.
        path: PathBuf,
        /// The underlying error.
        source: IoError,
    },
    /// Failed to create the UFO package directory.
    #[error("failed to create target font directory")]
    CreateUfoDir(#[source] IoError),
    /// Failed to write out a customly-serialized file.
    #[error("failed to write {name} file")]
    CustomFile {
        /// The name of the file.
        name: &'static str,
        /// The underlying error.
        source: CustomSerializationError,
    },
    /// Failed to write data entry.
    #[error("failed to write data file")]
    Data {
        /// The path to the entry.
        path: PathBuf,
        /// The underlying error.
        source: IoError,
    },
    /// Norad does not currently support downgrading to older UFO formats.
    #[error("downgrading below UFO v3 is not currently supported")]
    Downgrade,
    /// Failed to write out the feature.fea file.
    #[error("failed to write feature.fea file")]
    FeatureFile(#[source] IoError),
    /// Failed to write out an image file.
    #[error("failed to write image file")]
    Image {
        /// The path to the entry.
        path: PathBuf,
        /// The underlying error.
        source: IoError,
    },
    /// The font info contains invalid data.
    #[error("font info contains invalid data: {0}")]
    InvalidFontInfo(FontInfoErrorKind),
    /// The groups contains invalid data.
    #[error("failed to write (kerning) groups")]
    InvalidGroups(#[source] GroupsValidationError),
    /// The data or images store contains invalid data.
    #[error("store entry '{path}' is invalid")]
    InvalidStoreEntry {
        /// The path to the entry.
        path: PathBuf,
        /// The underlying error.
        source: StoreError,
    },
    /// Failed to write out a layer.
    #[error("failed to write layer '{name}' to '{path}'")]
    Layer {
        /// The name of the layer.
        name: String,
        /// The path to the layer.
        path: PathBuf,
        /// The underlying error.
        source: Box<LayerWriteError>,
    },
    /// There exists a `public.objectLibs` lib key when it should be set only by norad.
    #[error("the `public.objectLibs` lib key is managed by norad and must not be set manually")]
    PreexistingPublicObjectLibsKey,
}

/// An error that occurs while attempting to read a UFO layer from disk.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LayerWriteError {
    /// Failed to write out the contents.plist file
    #[error("failed to write contents.plist file")]
    Contents(#[source] CustomSerializationError),
    /// Failed to create the layer's directory.
    #[error("cannot create layer directory")]
    CreateDir(#[source] IoError),
    /// Failed to write out a glyph.
    #[error("failed to write glyph '{name}' to '{path}'")]
    Glyph {
        /// The name of the glyph.
        name: String,
        /// The path to the glyph.
        path: PathBuf,
        /// The underlying error.
        source: GlifWriteError,
    },
    /// Failed to write out the layerinfo.plist file
    #[error("failed to write layerinfo.plist file")]
    LayerInfo(#[source] CustomSerializationError),
}

/// An error when attempting to write a .glif file.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GlifWriteError {
    /// Failed to serialize a glyph to an internal buffer.
    #[error("failed to serialize glyph to an internal buffer")]
    Buffer(#[source] IoError),
    /// When writing out the 'lib' section, we use the plist crate to generate
    /// the plist xml, and then strip the preface and closing `</plist>` tag.
    ///
    /// If for some reason the implementation of that crate changes, we could
    /// be affected, although this is very unlikely.
    #[error("internal error while writing lib data, please open an issue")]
    InternalLibWriteError,
    /// Failed to write a .glif file to disk.
    #[error("failed to write .glif file")]
    Io(#[source] IoError),
    /// Plist serialization error. Wraps a [PlistError].
    #[error("error serializing glyph lib data internally")]
    Plist(#[source] PlistError),
    /// There exists a `public.objectLibs` glyph lib key when it should be set only by norad.
    #[error("the `public.objectLibs` lib key is managed by norad and must not be set manually")]
    PreexistingPublicObjectLibsKey,
    /// XML serialization error. Wraps a [XmlError].
    #[error("error serializing glyph to XML")]
    Xml(#[source] XmlError),
}

/// The reason for a glif parse failure.
#[derive(Debug, Clone, Copy)]
pub enum ErrorKind {
    /// The glif version is not supported by this library.
    UnsupportedGlifVersion,
    /// An unknown point type.
    UnknownPointType,
    /// The first XML element of a glif file is invalid.
    WrongFirstElement,
    /// Missing a close tag.
    MissingCloseTag,
    /// Has an invalid hexadecimal value.
    BadHexValue,
    /// Has an invalid numeric value.
    BadNumber,
    /// Has an invalid color value.
    BadColor,
    /// Has an invalid anchor definition.
    BadAnchor,
    /// Has an invalid point definition.
    BadPoint,
    /// Has an invalid guideline definition.
    BadGuideline,
    /// Has an invalid component definition.
    BadComponent,
    /// Has an invalid image definition.
    BadImage,
    /// Has an invalid identifier.
    BadIdentifier,
    /// Name is not a valid [`Name`].
    InvalidName,
    /// Has an invalid lib.
    BadLib,
    /// Has an unexected duplicate value.
    UnexpectedDuplicate,
    /// Has an unexpected move definition.
    UnexpectedMove,
    /// Has an unexpected smooth definition.
    UnexpectedSmooth,
    /// Has an unexpected element definition.
    UnexpectedElement,
    /// Has an unexpected attribute definition.
    UnexpectedAttribute,
    /// Has an unexpected end of file definition.
    UnexpectedEof,
    /// Has an unexpected point following an off curve point definition.
    UnexpectedPointAfterOffCurve,
    /// Has too many off curve points in sequence.
    TooManyOffCurves,
    /// The contour pen path was not started
    PenPathNotStarted,
    /// Has trailing off curve points defined.
    TrailingOffCurves,
    /// Has duplicate identifiers.
    DuplicateIdentifier,
    /// Has unexepected drawing data.
    UnexpectedDrawing,
    /// Has incomplete drawing data.
    UnfinishedDrawing,
    /// Has an unexpected point field.
    UnexpectedPointField,
    /// Has an unexpected component field.
    UnexpectedComponentField,
    /// Has an unexpected anchor field.
    UnexpectedAnchorField,
    /// Has an unexpected guideline field.
    UnexpectedGuidelineField,
    /// Has an unexpected image field.
    UnexpectedImageField,
    /// An element that can occur just once occured a second time.
    DuplicateElement(&'static str),
    /// An element that can occur just once occured a second time.
    UnexpectedV1Element(&'static str),
    /// An element that can occur just once occured a second time.
    UnexpectedV1Attribute(&'static str),
    /// A component had an empty `base` attribute.
    ComponentEmptyBase,
    /// A component was missing a `base` attribute.
    ComponentMissingBase,
    /// The glyph 'lib' element must contain a dictionary.
    LibMustBeDictionary,
    /// An angle was out of bounds.
    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))
    }
}