font_map_core/
codegen.rs

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
use crate::{
    codegen::to_ident::ToIdentExt,
    font::{Font, Glyph, StringKind},
};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::str::FromStr;
use syn::Ident;

mod to_ident;

/// Trait used to perform code generation for a font
pub trait FontCodegenExt {
    /// Generates an enum representing the glyphs in the font
    fn gen_enum(&self, name: &str) -> TokenStream;

    /// Generates a single entry in the enum for a glyph
    fn gen_enum_entry(glyph: &Glyph) -> TokenStream;

    /// Generates the comments for the enum
    fn gen_enum_comments(&self) -> Vec<String>;
}
impl FontCodegenExt for Font {
    fn gen_enum(&self, name: &str) -> TokenStream {
        let identifier = Ident::new(name, Span::call_site());
        let comments = self.gen_enum_comments();
        let variants: Vec<_> = self.glyphs().iter().map(Self::gen_enum_entry).collect();

        let codepoints: Vec<_> = self.glyphs().iter().map(Glyph::codepoint).collect();
        let names: Vec<_> = self.glyphs().iter().map(Glyph::name).collect();

        let font_family = self.string(StringKind::FontFamily);
        let font_family: Vec<_> = font_family.iter().collect();

        let codepoints_len = self.glyphs().len();

        quote! {
            #( #[doc = #comments] )*
            #[derive(Debug, Clone, Copy)]
            #[rustfmt::skip]
            #[repr(u32)]
            pub enum #identifier {
                #( #variants )*
            }

            #[rustfmt::skip]
            impl #identifier {
                #(
                    /// The font family of the glyph
                    #[allow(dead_code)]
                    pub const FONT_FAMILY: &str = #font_family;
                )*

                /// The number of glyphs in the font
                #[allow(dead_code)]
                pub const GLYPHS: usize = #codepoints_len;

                /// Returns the postscript name of the glyph
                #[allow(clippy::too_many_lines)]
                #[allow(clippy::match_same_arms)]
                #[must_use]
                pub fn name(&self) -> &'static str {
                    match *self as u32 {
                        #( #codepoints => #names, )*
                        _ => ".notdef",
                    }
                }
            }

            impl From<#identifier> for char {
                fn from(value: #identifier) -> Self {
                    std::char::from_u32(value as u32).unwrap_or(char::REPLACEMENT_CHARACTER)
                }
            }

            impl From<&#identifier> for char {
                fn from(value: &#identifier) -> Self {
                    (*value).into()
                }
            }

            impl std::fmt::Display for #identifier {
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    write!(f, "{}", char::from(*self))
                }
            }
        }
    }

    fn gen_enum_comments(&self) -> Vec<String> {
        let name = self.string(StringKind::FullFontName);
        let copyright = self.string(StringKind::CopyrightNotice);
        let description = self.string(StringKind::Description);

        let mut comments = Vec::new();

        if let Some(name) = name {
            comments.push(format!("{name}  "));
        }

        if let Some(copy) = copyright {
            comments.push(format!("{copy}  "));
        }

        if let Some(desc) = description {
            comments.push(format!("{desc}  "));
        }

        if !comments.is_empty() {
            comments.push(String::new());
        }

        comments.push(format!(
            "Contains the complete set of {} named glyphs for this font  ",
            self.glyphs().len()
        ));
        comments.push("Glyphs can be converted to their respective codepoints using `as u32`, or to `char` and `String` using `.into()`  ".to_string());
        comments
            .push("The postscript name for each glyph can be accessed using `.name()`".to_string());

        comments
    }

    fn gen_enum_entry(glyph: &Glyph) -> TokenStream {
        let identifier = glyph.name().to_identifier();
        let identifier = Ident::new(&identifier, Span::call_site());

        let name = glyph.name();
        let codepoint = glyph.codepoint();

        let comments = [
            format!("`{name} (U+{codepoint:04X})`  "),
            format!("Unicode range: {}", glyph.unicode_range()),
        ];

        #[cfg(not(feature = "extended-svg"))]
        let extended_svg = quote! {};
        #[cfg(feature = "extended-svg")]
        let extended_svg = {
            if let Ok(url) = glyph.svg_dataimage_url() {
                let link = format!("![Preview Glyph]({url})");
                quote! {
                    #[doc = ""]
                    #[doc = #link]
                }
            } else {
                quote! {}
            }
        };

        let codepoint = format!("{codepoint:#x}");
        let codepoint = TokenStream::from_str(&codepoint).unwrap();

        quote! {
            #( #[doc = #comments] )*
            #extended_svg
            #identifier = #codepoint,
        }
    }
}