1use proc_macro2::TokenStream;
3use quote::format_ident;
4use std::{collections::HashMap, vec};
5
6use crate::font::{Font, StringKind};
7
8mod docstring;
9use docstring::DocstringExt;
10
11mod to_ident;
12use to_ident::{to_categories, to_identifiers, ToIdentExt};
13
14mod category;
15use category::FontCategoryDesc;
16
17mod glyph;
18pub use glyph::GlyphDesc;
19
20#[cfg(feature = "codegen")]
21#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
22pub use quote::quote;
23
24#[derive(Debug, Clone)]
26pub struct FontDesc {
27 identifier: String,
28 family: Option<String>,
29 comments: Vec<String>,
30 categories: Vec<FontCategoryDesc>,
31}
32impl FontDesc {
33 pub fn from_font(identifier: &str, font: &Font, skip_categories: bool) -> Self {
35 let identifier = identifier.to_string();
36 let family = font.string(StringKind::FontFamily).map(ToString::to_string);
37 let mut comments = font.gen_docblock();
38
39 let mut categories = if skip_categories {
42 let glyphs = to_identifiers(font.glyphs());
44 vec![FontCategoryDesc::new(&identifier, glyphs)]
45 } else {
46 let raw_categories = to_categories(font.glyphs());
48 let mut categories = Vec::with_capacity(raw_categories.len());
49 for (name, glyphs) in raw_categories {
50 categories.push(FontCategoryDesc::new(&name, glyphs));
51 }
52
53 categories
54 };
55
56 if categories.len() == 1 {
59 let category = &mut categories[0];
60 category.set_name(identifier.clone());
61 category.set_comments(comments.drain(..));
62 category.sort();
63
64 return Self {
65 identifier,
66 family,
67 comments,
68 categories,
69 };
70 }
71
72 let mut other = categories
75 .iter()
76 .position(|c| c.name() == "Other")
77 .map_or_else(
78 || FontCategoryDesc::new("Other", HashMap::default()),
79 |idx| categories.swap_remove(idx),
80 );
81
82 categories = categories
85 .drain(..)
86 .filter_map(|category| {
87 if category.glyphs().len() > 2 {
88 return Some(category);
89 }
90
91 let (name, glyphs) = category.into_inner();
92 for mut glyph in glyphs {
93 let identifier = name.merge_identifiers(glyph.identifier());
94 glyph.set_identifier(identifier);
95 other.insert(glyph);
96 }
97 None
98 })
99 .collect();
100
101 other.update_comments();
104 categories.push(other);
105
106 categories.sort_by(|a, b| a.name().cmp(b.name()));
109 categories.iter_mut().for_each(FontCategoryDesc::sort);
110
111 Self {
112 identifier,
113 family,
114 comments,
115 categories,
116 }
117 }
118
119 #[must_use]
121 pub fn is_single_category(&self) -> bool {
122 self.categories.len() == 1
123 }
124
125 #[allow(clippy::needless_pass_by_value)]
129 #[allow(clippy::too_many_lines)]
130 #[must_use]
131 pub fn codegen(&self, extra_impl: Option<TokenStream>) -> TokenStream {
132 let identifier = format_ident!("{}", &self.identifier);
133 let outer_comments = &self.comments;
134 let font_family = self.family.iter();
135 let injection = extra_impl.iter();
136
137 if self.is_single_category() {
138 let category = &self.categories[0];
139
140 category.codegen(Some(quote! {
141 #(
142 pub const FONT_FAMILY: &str = #font_family;
144 )*
145
146 #(
147 #injection
148 )*
149 }))
150 } else {
151 let mut categories = Vec::with_capacity(self.categories.len());
154 for category in &self.categories {
155 categories.push(category.codegen(None));
156 }
157
158 let mut variant_names = Vec::with_capacity(categories.len());
159 let mut variants = Vec::with_capacity(categories.len());
160 for category in &self.categories {
161 let name = format_ident!("{}", category.name());
162 let comments = category.comments();
163 let variant = quote! {
164 #( #[doc = #comments] )*
165 #name(categories :: #name),
166 };
167
168 variant_names.push(name);
169 variants.push(variant);
170 }
171
172 quote! {
173 pub mod categories {
175 #( #categories )*
176 }
177
178 #[allow(rustdoc::bare_urls)]
179 #( #[doc = #outer_comments] )*
180 #[doc = ""]
181 #[doc = "See the [`categories`] module for more information."]
182 #[derive(Debug, Clone, Copy)]
183 #[rustfmt::skip]
184 pub enum #identifier {
185 #( #variants )*
186 }
187
188 #[rustfmt::skip]
189 #[allow(dead_code)]
190 impl #identifier {
191 #(
192 pub const FONT_FAMILY: &str = #font_family;
194 )*
195
196 #[allow(clippy::too_many_lines)]
198 #[allow(clippy::match_same_arms)]
199 #[must_use]
200 pub fn name(&self) -> &'static str {
201 match self {
202 #( Self :: #variant_names(inner) => inner.name(), )*
203 }
204 }
205
206 #(
207 #injection
208 )*
209 }
210
211 #(
212 impl From<categories :: #variant_names> for #identifier {
213 fn from(value: categories :: #variant_names) -> Self {
214 Self :: #variant_names(value)
215 }
216 }
217 )*
218
219 impl From<#identifier> for char {
220 fn from(value: #identifier) -> Self {
221 match value {
222 #( #identifier :: #variant_names(inner) => char::from(inner), )*
223 }
224 }
225 }
226
227 impl From<&#identifier> for char {
228 fn from(value: &#identifier) -> Self {
229 (*value).into()
230 }
231 }
232
233 impl From<#identifier> for u32 {
234 fn from(value: #identifier) -> Self {
235 match value {
236 #( #identifier :: #variant_names(inner) => inner as u32, )*
237 }
238 }
239 }
240
241 impl From<&#identifier> for u32 {
242 fn from(value: &#identifier) -> Self {
243 (*value).into()
244 }
245 }
246
247 impl std::fmt::Display for #identifier {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 match self {
250 #( #identifier :: #variant_names(inner) => inner.fmt(f), )*
251 }
252 }
253 }
254 }
255 }
256 }
257}
258
259impl From<&FontDesc> for TokenStream {
260 fn from(value: &FontDesc) -> Self {
261 value.codegen(None)
262 }
263}