glifparser/
glif.rs

1//! [`Glif`] (`<glif>` toplevel), read/write modules, + [`Lib`]
2
3use std::path;
4
5use crate::anchor::Anchor;
6use crate::component::GlifComponents;
7use crate::error::GlifParserError;
8use crate::guideline::Guideline;
9#[cfg(feature = "glifimage")]
10use crate::image::GlifImage;
11use crate::point::PointData;
12use crate::outline::Outline;
13
14mod conv;
15mod lib;
16pub use lib::Lib;
17mod read;
18pub use self::read::read_ufo_glif as read;
19pub use self::read::read_ufo_glif_pedantic as read_pedantic;
20pub use self::read::read_ufo_glif_from_filename as read_from_filename;
21pub use self::read::read_ufo_glif_from_filename_pedantic as read_from_filename_pedantic;
22mod write;
23pub use self::write::write_ufo_glif as write;
24pub use self::write::write_ufo_glif_to_filename as write_to_filename;
25#[cfg(feature = "mfek")]
26pub mod mfek;
27pub mod xml;
28pub use self::{read::FromXML, write::IntoXML, xml::XMLConversion};
29
30#[cfg(feature = "glifserde")]
31use serde::{Deserialize, Serialize};
32
33#[cfg(feature = "mfek")]
34pub use mfek::*;
35
36/// A UFO .glif
37///
38/// TODO: use different generic types on Anchor and Guideline, making this declaration
39/// `Glif<PD,GD,AD>`
40#[cfg_attr(feature = "glifserde", derive(Serialize, Deserialize))]
41#[derive(Clone, Debug, Default, PartialEq)]
42pub struct Glif<PD: PointData> {
43    pub outline: Option<Outline<PD>>,
44    pub anchors: Vec<Anchor<PD>>,
45    /// Note that these components are not yet parsed or checked for infinite loops. You need to
46    /// call either ``GlifComponent::to_component_of`` on each of these, or ``Glif::flatten``.
47    pub components: GlifComponents,
48    /// .glif guidelines. Note: glif may have more guidelines, not listed here. It will also have
49    /// an asecender and a descender, not listed here. You can get this info from `norad`, reading
50    /// the parent UFO and telling it not to read glif's (via UfoDataRequest) since you're using
51    /// this for that.
52    // Command line MFEK programs can also get it from MFEKmetadata.
53    pub guidelines: Vec<Guideline<PD>>,
54    /// glifparser does support reading the data of images and guessing their format, but in order
55    /// to allow you to handle possibly erroneous files we don't do so by default. You need to call
56    /// ``GlifImage::to_image_of`` to get an ``Image`` with data.
57    #[cfg(feature = "glifimage")]
58    pub images: Vec<GlifImage>,
59    pub width: Option<u64>,
60    pub unicode: Vec<char>,
61    pub name: String,
62    /// This is an arbitrary glyph comment, exactly like the comment field in FontForge SFD.
63    pub note: Option<String>,
64    /// It's up to the API consumer to set this.
65    #[cfg_attr(feature = "glifserde", serde(skip_serializing, skip_deserializing))]
66    pub filename: Option<path::PathBuf>,
67    /// glif private library
68    pub lib: Lib,
69}
70
71impl<PD: PointData> Glif<PD> {
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    pub fn name_to_filename(&self) -> String {
77        name_to_filename(&self.name, true)
78    }
79
80    pub fn filename_is_sane(&self) -> Result<bool, GlifParserError> {
81        match &self.filename {
82            Some(gfn) => {
83                let gfn_fn = match gfn.file_name() {
84                    Some(gfn_fn) => gfn_fn,
85                    None => { return Err(GlifParserError::GlifFilenameInsane("Glif file name is directory".to_string())) }
86                };
87
88                Ok(self.name_to_filename() == gfn_fn.to_str().ok_or(GlifParserError::GlifFilenameInsane("Glif file name has unknown encoding".to_string()))?)
89            }
90            None => Err(GlifParserError::GlifFilenameInsane("Glif file name is not set".to_string()))
91        }
92    }
93
94}
95
96pub trait GlifLike {
97    fn name(&self) -> &String;
98    fn filename(&self) -> &Option<path::PathBuf>;
99}
100
101impl<PD: PointData> GlifLike for Glif<PD> {
102    fn name(&self) -> &String {
103        &self.name
104    }
105    fn filename(&self) -> &Option<path::PathBuf> {
106        &self.filename
107    }
108}
109
110#[inline]
111pub fn name_to_filename(name: &str, append_extension: bool) -> String {
112    let mut ret = String::new();
113    let chars: Vec<char> = name.chars().collect();
114    for c in chars {
115        ret.push(c);
116        if ('A'..'Z').contains(&c) {
117            ret.push('_');
118        }
119    }
120    if append_extension {
121        ret.push_str(".glif");
122    }
123    ret
124}