babelfont 0.1.1-pre

A universal font format converter and processor
Documentation
use serde::{Deserialize, Serialize};

/// Type of font metric
#[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize)]
pub enum MetricType {
    /// X height
    XHeight,
    /// Cap height
    CapHeight,
    /// Ascender (design-time ascender for the master)
    Ascender,
    /// Descender (design-time descender for the master)
    Descender,
    /// Italic angle (in degrees, negative for right slant)
    ItalicAngle,
    /// Ascender (to be placed in the `hhea` table)
    HheaAscender,
    /// Descender (to be placed in the `hhea` table)
    HheaDescender,
    /// Line gap (to be placed in the `hhea` table)
    HheaLineGap,
    /// Windows Ascender (to be placed in the `OS/2` table)
    WinAscent,
    /// Windows Descender (to be placed in the `OS/2` table)
    WinDescent,
    /// Typographic Ascender (to be placed in the `OS/2` table)
    TypoAscender,
    /// Typographic Descender (to be placed in the `OS/2` table)
    TypoDescender,
    /// Typographic Line Gap (to be placed in the `OS/2` table)
    TypoLineGap,
    /// Subscript horizontal font size
    SubscriptXSize,
    /// Subscript vertical font size
    SubscriptYSize,
    /// Subscript horizontal offset
    SubscriptXOffset,
    /// Subscript vertical offset
    SubscriptYOffset,
    /// Superscript horizontal font size
    SuperscriptXSize,
    /// Superscript vertical font size
    SuperscriptYSize,
    /// Superscript horizontal offset
    SuperscriptXOffset,
    /// Superscript vertical offset
    SuperscriptYOffset,
    /// Strikeout size
    StrikeoutSize,
    /// Strikeout position
    StrikeoutPosition,
    /// Underline position
    UnderlinePosition,
    /// Underline thickness
    UnderlineThickness,
    /// Caret slope rise (for the `hhea` table)
    HheaCaretSlopeRise,
    /// Caret slope run (for the `hhea` table)
    HheaCaretSlopeRun,
    /// Caret offset (for the `hhea` table)
    HheaCaretOffset,
    /// Custom metric type
    #[serde(untagged)]
    Custom(String),
}

impl From<&str> for MetricType {
    fn from(s: &str) -> Self {
        match s {
            "xHeight" => MetricType::XHeight,
            "capHeight" => MetricType::CapHeight,
            "ascender" => MetricType::Ascender,
            "descender" => MetricType::Descender,
            "italicAngle" => MetricType::ItalicAngle,
            "hheaAscender" => MetricType::HheaAscender,
            "hheaDescender" => MetricType::HheaDescender,
            "hheaLineGap" => MetricType::HheaLineGap,
            "winAscent" => MetricType::WinAscent,
            "winDescent" => MetricType::WinDescent,
            "typoAscender" => MetricType::TypoAscender,
            "typoDescender" => MetricType::TypoDescender,
            "typoLineGap" => MetricType::TypoLineGap,
            "subscriptXSize" => MetricType::SubscriptXSize,
            "subscriptYSize" => MetricType::SubscriptYSize,
            "subscriptXOffset" => MetricType::SubscriptXOffset,
            "subscriptYOffset" => MetricType::SubscriptYOffset,
            "superscriptXSize" => MetricType::SuperscriptXSize,
            "superscriptYSize" => MetricType::SuperscriptYSize,
            "superscriptXOffset" => MetricType::SuperscriptXOffset,
            "superscriptYOffset" => MetricType::SuperscriptYOffset,
            "strikeoutSize" => MetricType::StrikeoutSize,
            "strikeoutPosition" => MetricType::StrikeoutPosition,
            "underlinePosition" => MetricType::UnderlinePosition,
            "underlineThickness" => MetricType::UnderlineThickness,
            "hheaCaretSlopeRise" => MetricType::HheaCaretSlopeRise,
            "hheaCaretSlopeRun" => MetricType::HheaCaretSlopeRun,
            "hheaCaretOffset" => MetricType::HheaCaretOffset,
            custom => MetricType::Custom(custom.to_string()),
        }
    }
}

impl MetricType {
    /// Get the name of the MetricType
    pub fn as_str(&self) -> &str {
        match self {
            MetricType::XHeight => "xHeight",
            MetricType::CapHeight => "capHeight",
            MetricType::Ascender => "ascender",
            MetricType::Descender => "descender",
            MetricType::ItalicAngle => "italicAngle",
            MetricType::HheaAscender => "hheaAscender",
            MetricType::HheaDescender => "hheaDescender",
            MetricType::HheaLineGap => "hheaLineGap",
            MetricType::WinAscent => "winAscent",
            MetricType::WinDescent => "winDescent",
            MetricType::TypoAscender => "typoAscender",
            MetricType::TypoDescender => "typoDescender",
            MetricType::TypoLineGap => "typoLineGap",
            MetricType::SubscriptXSize => "subscriptXSize",
            MetricType::SubscriptYSize => "subscriptYSize",
            MetricType::SubscriptXOffset => "subscriptXOffset",
            MetricType::SubscriptYOffset => "subscriptYOffset",
            MetricType::SuperscriptXSize => "superscriptXSize",
            MetricType::SuperscriptYSize => "superscriptYSize",
            MetricType::SuperscriptXOffset => "superscriptXOffset",
            MetricType::SuperscriptYOffset => "superscriptYOffset",
            MetricType::StrikeoutSize => "strikeoutSize",
            MetricType::StrikeoutPosition => "strikeoutPosition",
            MetricType::UnderlinePosition => "underlinePosition",
            MetricType::UnderlineThickness => "underlineThickness",
            MetricType::HheaCaretSlopeRise => "hheaCaretSlopeRise",
            MetricType::HheaCaretSlopeRun => "hheaCaretSlopeRun",
            MetricType::HheaCaretOffset => "hheaCaretOffset",
            MetricType::Custom(s) => s,
        }
    }
}

#[cfg(feature = "glyphs")]
mod glyphs {
    use super::MetricType;
    use glyphslib::glyphs3::{Metric as G3Metric, MetricType as G3MetricType};
    impl From<&G3MetricType> for MetricType {
        fn from(value: &G3MetricType) -> Self {
            match value {
                G3MetricType::Ascender => MetricType::Ascender,
                G3MetricType::CapHeight => MetricType::CapHeight,
                G3MetricType::SlantHeight => MetricType::Custom("slantHeight".to_string()),
                G3MetricType::XHeight => MetricType::XHeight,
                G3MetricType::MidHeight => MetricType::Custom("midHeight".to_string()),
                G3MetricType::TopHeight => MetricType::Custom("topHeight".to_string()),
                G3MetricType::BodyHeight => MetricType::Custom("bodyHeight".to_string()),
                G3MetricType::Descender => MetricType::Descender,
                G3MetricType::Baseline => MetricType::Custom("baseline".to_string()),
                G3MetricType::ItalicAngle => MetricType::ItalicAngle,
            }
        }
    }

    impl TryFrom<&MetricType> for G3MetricType {
        type Error = ();

        fn try_from(value: &MetricType) -> Result<Self, Self::Error> {
            match value.as_str() {
                "ascender" => Ok(G3MetricType::Ascender),
                "capHeight" => Ok(G3MetricType::CapHeight),
                "slantHeight" => Ok(G3MetricType::SlantHeight),
                "xHeight" => Ok(G3MetricType::XHeight),
                "midHeight" => Ok(G3MetricType::MidHeight),
                "topHeight" => Ok(G3MetricType::TopHeight),
                "bodyHeight" => Ok(G3MetricType::BodyHeight),
                "descender" => Ok(G3MetricType::Descender),
                "baseline" => Ok(G3MetricType::Baseline),
                "italicAngle" => Ok(G3MetricType::ItalicAngle),

                _ => Err(()),
            }
        }
    }

    impl From<&MetricType> for G3Metric {
        fn from(value: &MetricType) -> Self {
            let metric_type = G3MetricType::try_from(value).ok();
            G3Metric {
                filter: None,
                name: if metric_type.is_some() {
                    ""
                } else {
                    value.as_str()
                }
                .to_string(),
                metric_type,
            }
        }
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use indexmap::IndexMap;

    use super::*;

    #[test]
    fn test_serialize() {
        let metric = MetricType::XHeight;
        let serialized = serde_json::to_string(&metric).unwrap();
        assert_eq!(serialized, r#""XHeight""#);

        let custom_metric = MetricType::Custom("myCustomMetric".to_string());
        let serialized_custom = serde_json::to_string(&custom_metric).unwrap();
        assert_eq!(serialized_custom, r#""myCustomMetric""#);
    }

    #[test]
    fn test_serialize_indexmap() {
        let metrics = IndexMap::from([
            (MetricType::XHeight, 500.0),
            (MetricType::Custom("myCustomMetric".to_string()), 300.0),
        ]);
        let serialized = serde_json::to_string(&metrics).unwrap();
        assert_eq!(serialized, r#"{"XHeight":500.0,"myCustomMetric":300.0}"#);
    }
}