font_map/lib.rs
1//! # font-map
2//! ## Font parser / enumerator with support for code generation
3//!
4//! [](https://crates.io/crates/font-map/)
5//! [](https://github.com/rscarson/font-map/actions?query=branch%3Amaster)
6//! [](https://docs.rs/font-map/latest/)
7//! [](https://raw.githubusercontent.com/rscarson/font-map/master/LICENSE)
8//!
9//! This crate provides functionality for parsing font files and enumerating the glyphs they contain.
10//!
11//! The base usecase for this crate is to create an enum of all the glyphs in a font file,
12//! for use in fontend projects, where you want to refer to glyphs by name rather than by codepoint:
13//!
14//! ```rust
15//! use font_map::font;
16//!
17//! font!(Icon, "google_material_symbols/font.ttf");
18//!
19//! const DELETE: Icon = Icon::Delete;
20//! ```
21//!
22//! The generated code includes information for each glyph, such as:
23//! - codepoint, and postfix-name
24//! - Plus a generated SVG preview image visible on hover
25//!
26//! You can also access `Icon::FONT_FAMILY` to simplify font usage in your frontend.
27//!
28//! -----
29//!
30//! Another use is to use it for introspection of font files:
31//!
32//! ```rust
33//! use font_map::font::Font;
34//!
35//! # use font_map::error::ParseError;
36//! # fn main() -> Result<(), ParseError> {
37//! let font = Font::from_file("google_material_symbols/font.ttf")?;
38//! if let Some(glyph) = font.glyph_named("delete") {
39//! let codepoint = glyph.codepoint();
40//! let svg = glyph.svg_preview();
41//! }
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! ## Features
47//! - `macros` - Enables the `font!` macro for code generation
48//! - `codegen` - Enables the `FontCodegenExt` trait for runtime code generation
49//! - `extended-svg` - Enables compressed and base64 encoded SVG data in the generated code (Needed for image previews)
50//!
51//! ## Known Limitations
52//! This crate was made for a very specific use-case, and as such currently has a few limitations:
53//! - Only supports TTF fonts
54//! - And even then, only a subset of the spec, namely:
55//! - Only some formats of the `cmap` table
56//! - Only Unicode, or MS encoding 1 and 10, and `Macintosh::0` of the `name` table
57//! - Only formats 2.5 or below of the `post` table
58//!
59#![warn(missing_docs)]
60#![warn(clippy::pedantic)]
61#![allow(clippy::doc_comment_double_space_linebreaks)]
62#![cfg_attr(docsrs, feature(doc_cfg))]
63pub use font_map_core::*;
64
65#[cfg(feature = "macros")]
66#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
67pub use font_map_macros::*;
68
69/// **Only designed to be used inside `build.rs`**
70///
71/// This macro is used to generate the code for a font file, and set up the build script to rerun
72/// if the font file changes.
73///
74/// The generated code will include an enum with all the glyphs in the font, optionally split by
75/// category
76///
77/// To include the generated code, see `[font_map::include_font]`
78///
79/// # Example
80/// ```no_run
81/// use font_map::build_font;
82///
83/// fn main() {
84/// build_font!(
85/// path = "../examples/slick.ttf",
86/// name = SlickFont,
87/// skip_categories = false, /* Can be omitted - if `true`, generate one giant enum instead of a set of categories */
88/// );
89/// }
90/// ```
91#[cfg(feature = "macros")]
92#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
93#[allow(clippy::needless_doctest_main)]
94#[macro_export]
95macro_rules! build_font {
96 (
97 path = $path:literal,
98 name = $name:ident,
99 skip_categories = $skip_categories:literal $(,)?
100 ) => {
101 const FONT_BYTES: &[u8] = include_bytes!($path);
102 println!(concat!("cargo:rerun-if-changed=", $path));
103
104 let target_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
105 let target_path = std::path::Path::new(&target_dir)
106 .join($path)
107 .display()
108 .to_string();
109
110 //
111 // Load the font and perform code generation
112 let font = font_map::font::Font::new(FONT_BYTES).expect("Bundled font was invalid!");
113 let generator =
114 font_map::codegen::FontDesc::from_font(stringify!($name), &font, $skip_categories);
115 let code = generator
116 .codegen(Some(font_map::codegen::quote! {
117 /// The raw bytes of the font file
118 pub const FONT_BYTES: &[u8] = include_bytes!(#target_path);
119 }))
120 .to_string();
121
122 //
123 // Create the target file
124 let dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
125 let target =
126 std::path::Path::new(&dir).join(&format!("font_generated_{}.rs", stringify!($name)));
127 std::fs::write(&target, code).expect("Failed to write generated icon-enum");
128
129 //
130 // Manually run rustfmt on the generated file
131 let _ = std::process::Command::new("rustfmt")
132 .arg(&target)
133 .status()
134 .expect("Failed to run rustfmt on generated icon-enum");
135
136 //
137 // Provide an ENV var with the path to the generated file
138 println!(
139 concat!("cargo:rustc-env=FONT_GEN_", stringify!($name), "={}"),
140 target.display()
141 );
142 };
143
144 (
145 path = $path:literal,
146 name = $name:ident $(,)?
147 ) => {
148 $crate::build_font! {
149 path = $path,
150 name = $name,
151 skip_categories = false
152 }
153 };
154}
155
156/// Includes a font file generated by the [`build_font!`] macro
157///
158/// **NOTE:** Due to existing issues with rust-analyzer you may need to restart the RA server (left side of bottom toolbar)
159/// after adding a new font file
160///
161/// This macro will include the generated code for the font's symbols, and provide:
162/// - `FONT_BYTES`: The raw bytes of the font file
163/// - `load_font()`: A function that returns a `font_map::font::Font` instance describing the font and its symbols
164///
165/// # Example
166/// ```ignore
167/// use font_map::include_font;
168///
169/// include_font!(GoogleMaterialSymbols);
170///
171/// const DELETE: GoogleMaterialSymbols = GoogleMaterialSymbols::Delete;
172/// ```
173#[cfg(feature = "macros")]
174#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
175#[macro_export]
176macro_rules! include_font {
177 ($name:ident) => {
178 //
179 // Generated font bindings
180 include!(env!(concat!("FONT_GEN_", stringify!($name))));
181
182 /// Returning a `font_map::Font` instance describing the font and its symbols
183 #[allow(
184 clippy::missing_panics_doc,
185 reason = "The panic message is clear enough"
186 )]
187 #[must_use]
188 pub fn load_font() -> font_map::font::Font {
189 font_map::font::Font::new($name::FONT_BYTES).expect("Bundled font was invalid!")
190 }
191 };
192}