Skip to main content

klirr_core/models/
font_identifier.rs

1use crate::prelude::*;
2
3/// Regular weight of Computer Modern font. For more info see [`FontIdentifier::ComputerModern`].
4const FONT_COMPUTER_MODERN_REGULAR: &[u8] = include_bytes!("../../assets/cmunrm.ttf");
5/// Bold weight of Computer Modern font. For more info see [`FontIdentifier::ComputerModern`].
6const FONT_COMPUTER_MODERN_BOLD: &[u8] = include_bytes!("../../assets/cmunbx.ttf");
7
8/// An identifier for a font used in typst layouts.
9#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, Hash)]
10#[display("{}", self.family_name())]
11pub enum FontIdentifier {
12    /// Font data for Computer Modern font, which is the default font used in
13    /// [`Layout::Aioo`]. It is not a default system font so we include it in the
14    /// klirr binary.
15    ///
16    /// Computer Modern is a family of typefaces designed by Donald Knuth for use in the
17    /// TeX typesetting system. It is widely used in academic and scientific documents.
18    /// The font is available under the SIL Open Font License, which allows for both
19    /// personal and commercial use, as well as modification and redistribution.
20    ComputerModern(FontWeight),
21}
22
23impl FontIdentifier {
24    /// MUST match the `family_name` in the font definition, e.g. as shown in Fonts app on macOS.
25    pub fn family_name(&self) -> String {
26        match self {
27            Self::ComputerModern(_) => "CMU Serif".to_owned(),
28        }
29    }
30
31    /// The raw bytes of the font data, can be used by Typst to load the font into
32    /// a Typst::Font, used by the Typst typesetting engine, this allows us to
33    /// vendor the font data directly in the binary.
34    pub fn font_bytes(&self) -> &'static [u8] {
35        let unsupported = |weight: &str, typst_cmd: &str| {
36            panic!(
37                "Computer Modern {} is not supported (use of '{}' in Typst), it can easily be added if needed, create an Issue on GitHub: https://github.com/Sajjon/klirr/issues/new",
38                weight, typst_cmd
39            )
40        };
41        match self {
42            Self::ComputerModern(FontWeight::Regular) => FONT_COMPUTER_MODERN_REGULAR,
43            Self::ComputerModern(FontWeight::Bold) => FONT_COMPUTER_MODERN_BOLD,
44            Self::ComputerModern(FontWeight::Italic) => unsupported("Italic", "emph"),
45            Self::ComputerModern(FontWeight::BoldItalic) => {
46                unsupported("Bold Italic", "strong[emph]")
47            }
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use test_log::test;
56    use ttf_parser::{Face, name_id};
57
58    #[test]
59    fn test_font_identifier() {
60        [FontWeight::Regular, FontWeight::Bold]
61            .into_iter()
62            .for_each(|weight| {
63                let font = FontIdentifier::ComputerModern(weight);
64
65                fn get_family_name(face: &Face) -> Option<String> {
66                    face.names()
67                        .into_iter()
68                        .find(|name| name.name_id == name_id::FAMILY && name.is_unicode())
69                        .and_then(|name| name.to_string())
70                }
71                let parsed = ttf_parser::Face::parse(font.font_bytes(), 0).unwrap();
72                let family_name_of_font_parsed_from_bytes =
73                    get_family_name(&parsed).unwrap_or_default();
74                assert_eq!(family_name_of_font_parsed_from_bytes, "CMU Serif");
75                assert_eq!(font.family_name(), family_name_of_font_parsed_from_bytes);
76            });
77    }
78
79    #[test]
80    #[should_panic(expected = "Computer Modern Italic is not supported")]
81    fn test_italic_panics() {
82        let font = FontIdentifier::ComputerModern(FontWeight::Italic);
83        font.font_bytes();
84    }
85
86    #[test]
87    #[should_panic(expected = "Computer Modern Bold Italic is not supported")]
88    fn test_bold_italic_panics() {
89        let font = FontIdentifier::ComputerModern(FontWeight::BoldItalic);
90        font.font_bytes();
91    }
92}