font_map/
lib.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! # font-map
//! ## Font parser / enumerator with support for code generation
//!
//! [![Crates.io](https://img.shields.io/crates/v/font-map.svg)](https://crates.io/crates/font-map/)
//! [![Build Status](https://github.com/rscarson/font-map/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/rscarson/font-map/actions?query=branch%3Amaster)
//! [![docs.rs](https://img.shields.io/docsrs/font-map)](https://docs.rs/font-map/latest/)
//! [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rscarson/font-map/master/LICENSE)
//!
//! This crate provides functionality for parsing font files and enumerating the glyphs they contain.
//!
//! The base usecase for this crate is to create an enum of all the glyphs in a font file,  
//! for use in fontend projects, where you want to refer to glyphs by name rather than by codepoint:
//!
//! ```rust
//! use font_map::font;
//!
//! font!(Icon, "google_material_symbols/font.ttf");
//!
//! const DELETE: Icon = Icon::Delete;
//! ```
//!
//! The generated code includes information for each glyph, such as:
//! - codepoint, and postfix-name
//! - Plus a generated SVG preview image visible on hover
//!
//! You can also access `Icon::FONT_FAMILY` to simplify font usage in your frontend.
//!
//! -----
//!
//! Another use is to use it for introspection of font files:
//!
//! ```rust
//! use font_map::font::Font;
//!
//! # use font_map::error::ParseError;
//! # fn main() -> Result<(), ParseError> {
//! let font = Font::from_file("google_material_symbols/font.ttf")?;
//! if let Some(glyph) = font.glyph_named("delete") {
//!     let codepoint = glyph.codepoint();
//!     let svg = glyph.svg_preview();
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ## Features
//! - `macros` - Enables the `font!` macro for code generation
//! - `codegen` - Enables the `FontCodegenExt` trait for runtime code generation
//! - `extended-svg` - Enables compressed and base64 encoded SVG data in the generated code (Needed for image previews)
//!
//! ## Known Limitations
//! This crate was made for a very specific use-case, and as such currently has a few limitations:
//! - Only supports TTF fonts
//! - And even then, only a subset of the spec, namely:
//! - Only some formats of the `cmap` table
//! - Only Unicode, or MS encoding 1 and 10, and `Macintosh::0` of the `name` table
//! - Only formats 2.5 or below of the `post` table
//!
#![warn(missing_docs)]
#![warn(clippy::pedantic)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use font_map_core::*;

#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use font_map_macros::*;

/// **Only designed to be used inside `build.rs`**
///
/// This macro is used to generate the code for a font file, and set up the build script to rerun
/// if the font file changes.
///
/// The generated code will include an enum with all the glyphs in the font, optionally split by
/// category
///
/// To include the generated code, see `[font_map::include_font]`
///
/// # Example
/// ```no_run
/// use font_map::build_font;
///
/// fn main() {
///     build_font!(
///         path = "../examples/slick.ttf",
///         name = SlickFont,
///         skip_categories = false, /* Can be omitted - if `true`, generate one giant enum instead of a set of categories */
///     );
/// }
/// ```
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[allow(clippy::needless_doctest_main)]
#[macro_export]
macro_rules! build_font {
    (
        path = $path:literal,
        name = $name:ident,
        skip_categories = $skip_categories:literal $(,)?
    ) => {
        const FONT_BYTES: &[u8] = include_bytes!($path);
        println!(concat!("cargo:rerun-if-changed=", $path));

        let target_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
        let target_path = std::path::Path::new(&target_dir)
            .join($path)
            .display()
            .to_string();

        //
        // Load the font and perform code generation
        let font = font_map::font::Font::new(FONT_BYTES).expect("Bundled font was invalid!");
        let generator =
            font_map::codegen::FontDesc::from_font(stringify!($name), &font, $skip_categories);
        let code = generator
            .codegen(Some(font_map::codegen::quote! {
                /// The raw bytes of the font file
                pub const FONT_BYTES: &[u8] = include_bytes!(#target_path);
            }))
            .to_string();

        //
        // Create the target file
        let dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
        let target =
            std::path::Path::new(&dir).join(&format!("font_generated_{}.rs", stringify!($name)));
        std::fs::write(&target, code).expect("Failed to write generated icon-enum");

        //
        // Provide an ENV var with the path to the generated file
        println!(
            concat!("cargo:rustc-env=FONT_GEN_", stringify!($name), "={}"),
            target.display()
        );
    };

    (
        path = $path:literal,
        name = $name:ident $(,)?
    ) => {
        $crate::build_font! {
            path = $path,
            name = $name,
            skip_categories = false
        }
    };
}

/// Includes a font file generated by the [`build_font!`] macro
///
/// **NOTE:** Due to existing issues with rust-analyzer you may need to restart the RA server (left side of bottom toolbar)
/// after adding a new font file
///
/// This macro will include the generated code for the font's symbols, and provide:
/// - `FONT_BYTES`: The raw bytes of the font file
/// - `load_font()`: A function that returns a `font_map::font::Font` instance describing the font and its symbols
///
/// # Example
/// ```ignore
/// use font_map::include_font;
///
/// include_font!(GoogleMaterialSymbols);
///
/// const DELETE: GoogleMaterialSymbols = GoogleMaterialSymbols::Delete;
/// ```
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
#[macro_export]
macro_rules! include_font {
    ($name:ident) => {
        //
        // Generated font bindings
        include!(env!(concat!("FONT_GEN_", stringify!($name))));

        /// Returning a `font_map::Font` instance describing the font and its symbols
        #[allow(
            clippy::missing_panics_doc,
            reason = "The panic message is clear enough"
        )]
        #[must_use]
        pub fn load_font() -> font_map::font::Font {
            font_map::font::Font::new($name::FONT_BYTES).expect("Bundled font was invalid!")
        }
    };
}