diff options
| author | Polesznyák Márk <contact@pml68.dev> | 2025-11-14 13:16:22 +0100 |
|---|---|---|
| committer | Polesznyák Márk <contact@pml68.dev> | 2025-11-14 13:16:22 +0100 |
| commit | f987fc87b906d22e668f636d73c24aa42780b38d (patch) | |
| tree | 868b949c3cac2a177569341d76ce73ef5b0d0828 /iced_fontello/src/lib.rs | |
| parent | chore: update deps (diff) | |
| download | iced-builder-f987fc87b906d22e668f636d73c24aa42780b38d.tar.gz | |
chore: update deps
Diffstat (limited to 'iced_fontello/src/lib.rs')
| -rw-r--r-- | iced_fontello/src/lib.rs | 394 |
1 files changed, 0 insertions, 394 deletions
diff --git a/iced_fontello/src/lib.rs b/iced_fontello/src/lib.rs deleted file mode 100644 index 2b39647..0000000 --- a/iced_fontello/src/lib.rs +++ /dev/null @@ -1,394 +0,0 @@ -#![allow(clippy::needless_doctest_main)] -//! A compile-time, type-safe icon font generator for [`iced`]. -//! Powered by [Fontello]. -//! -//! [`iced`]: https://github.com/iced-rs/iced -//! [Fontello]: https://github.com/fontello/fontello -//! -//! # Usage -//! Create a `.toml` file somewhere in your crate with the font definition: -//! -//! ```toml -//! # fonts/example-icons.toml -//! module = "icon" -//! -//! [glyphs] -//! edit = "fontawesome-pencil" -//! save = "entypo-floppy" -//! trash = "typicons-trash" -//! ``` -//! -//! The `module` value defines the Rust module that will be generated in your `src` -//! directory containing a type-safe API to use the font. -//! -//! Each entry in the `[glyphs]` section corresponds to an icon. The keys will be -//! used as names for the functions of the module of the font; while the values -//! specify the glyph for that key using the format: `<font>-<glyph>`. You can browse -//! the available glyphs in [Fontello] or [the `fonts.json` file](fonts.json). -//! -//! Next, add `iced_fontello` to your `build-dependencies`: -//! -//! ```toml -//! [build-dependencies] -//! iced_fontello = "0.13" -//! ``` -//! -//! Then, call `iced_fontello::build` in your [build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html), -//! passing the path of your font definition: -//! -//! ```rust,no_run -//! pub fn main() { -//! println!("cargo::rerun-if-changed=fonts/example-icons.toml"); -//! iced_fontello::build("fonts/example-icons.toml").expect("Build example-icons font"); -//! } -//! ``` -//! -//! The library will generate the font and save its `.ttf` file right next to its definition. -//! In this example, the library would generate `fonts/example-icons.ttf`. -//! -//! Finally, it will generate a type-safe `iced` API that lets you use the font. In our example: -//! -//! ```rust,ignore -//! // Generated automatically by iced_fontello at build time. -//! // Do not edit manually. -//! // d24460a00249b2acd0ccc64c3176452c546ad12d1038974e974d7bdb4cdb4a8f -//! use iced::widget::{text, Text}; -//! use iced::Font; -//! -//! pub const FONT: &[u8] = include_bytes!("../fonts/example-icons.ttf"); -//! -//! pub fn edit<'a>() -> Text<'a> { -//! icon("\u{270E}") -//! } -//! -//! pub fn save<'a>() -> Text<'a> { -//! icon("\u{1F4BE}") -//! } -//! -//! pub fn trash<'a>() -> Text<'a> { -//! icon("\u{E10A}") -//! } -//! -//! fn icon<'a>(codepoint: &'a str) -> Text<'a> { -//! text(codepoint).font(Font::with_name("example-icons")) -//! } -//! ``` -//! -//! Now you can simply add `mod icon;` to your `lib.rs` or `main.rs` file and enjoy your new font: -//! -//! ```rust,ignore -//! mod icon; -//! -//! use iced::widget::row; -//! -//! // ... -//! -//! row![icon::edit(), icon::save(), icon::trash()].spacing(10) -//! -//! // ... -//! ``` -//! -//! Check out [the full example](example) to see it all in action. -//! -//! # Packaging -//! If you plan to package your crate, you must make sure you include the generated module -//! and font file in the final package. `build` is effectively a no-op when the module and -//! the font already exist and are up-to-date. -use std::collections::BTreeMap; -use std::path::{Path, PathBuf}; -use std::{fs, io}; - -use reqwest::blocking as reqwest; -use serde::{Deserialize, Serialize}; - -pub fn build(path: impl AsRef<Path>) -> Result<(), Error> { - let path = path.as_ref(); - - let definition: Definition = { - let contents = fs::read_to_string(path).unwrap_or_else(|error| { - panic!( - "Font definition {path} could not be read: {error}", - path = path.display() - ) - }); - - toml::from_str(&contents).unwrap_or_else(|error| { - panic!( - "Font definition {path} is invalid: {error}", - path = path.display() - ) - }) - }; - - let fonts = parse_fonts(); - - let glyphs: BTreeMap<String, ChosenGlyph> = definition - .glyphs - .into_iter() - .map(|(name, id)| { - let Some((font_name, glyph)) = id.split_once('-') else { - panic!( - "Invalid glyph identifier: \"{id}\"\n\ - Glyph identifier must have \"<font>-<name>\" format" - ) - }; - - let Some(font) = fonts.get(font_name) else { - panic!( - "Font \"{font_name}\" was not found. Available fonts are:\n{}", - fonts - .keys() - .map(|name| format!("- {name}")) - .collect::<Vec<_>>() - .join("\n") - ); - }; - - let Some(glyph) = font.glyphs.get(glyph) else { - // TODO: Display similarly named candidates - panic!( - "Glyph \"{glyph}\" was not found. Available glyphs are:\n{}", - font.glyphs - .keys() - .map(|name| format!("- {name}")) - .collect::<Vec<_>>() - .join("\n") - ); - }; - - ( - name, - ChosenGlyph { - uid: glyph.uid.clone(), - css: glyph.name.clone(), - code: glyph.code, - src: font.name.clone(), - }, - ) - }) - .collect(); - - #[derive(Serialize)] - struct Config { - name: String, - css_prefix_text: &'static str, - css_use_suffix: bool, - hinting: bool, - units_per_em: u32, - ascent: u32, - glyphs: Vec<ChosenGlyph>, - } - - #[derive(Clone, Serialize)] - struct ChosenGlyph { - uid: Id, - css: String, - code: u64, - src: String, - } - - let file_name = path - .file_stem() - .expect("Get file stem from definition path") - .to_string_lossy() - .into_owned(); - - let config = Config { - name: file_name.clone(), - css_prefix_text: "icon-", - css_use_suffix: false, - hinting: true, - units_per_em: 1000, - ascent: 850, - glyphs: glyphs.values().cloned().collect(), - }; - - let hash = { - use sha2::Digest as _; - - let mut hasher = sha2::Sha256::new(); - hasher.update( - serde_json::to_string(&config).expect("Serialize config as JSON"), - ); - - format!("{:x}", hasher.finalize()) - }; - - let module_target = PathBuf::new() - .join("src") - .join(definition.module.replace("::", "/")) - .with_extension("rs"); - - let module_contents = - fs::read_to_string(&module_target).unwrap_or_default(); - let module_hash = module_contents - .lines() - .nth(2) - .unwrap_or_default() - .trim_start_matches("// "); - - if hash != module_hash || !path.with_extension("ttf").exists() { - let client = reqwest::Client::new(); - let session = client - .post("https://fontello.com/") - .multipart( - reqwest::multipart::Form::new().part( - "config", - reqwest::multipart::Part::text( - serde_json::to_string(&config) - .expect("Serialize Fontello config"), - ) - .file_name("config.json"), - ), - ) - .send() - .and_then(reqwest::Response::error_for_status) - .and_then(reqwest::Response::text) - .expect("Create Fontello session"); - - let font = client - .get(format!("https://fontello.com/{session}/get")) - .send() - .and_then(reqwest::Response::error_for_status) - .and_then(reqwest::Response::bytes) - .expect("Download Fontello font"); - - let mut archive = zip::ZipArchive::new(io::Cursor::new(font)) - .expect("Parse compressed font"); - - let mut font_file = (0..archive.len()) - .find(|i| { - let file = - archive.by_index(*i).expect("Access zip archive by index"); - - file.name().ends_with(&format!("{file_name}.ttf")) - }) - .and_then(|i| archive.by_index(i).ok()) - .expect("Find font file in zipped archive"); - - io::copy( - &mut font_file, - &mut fs::File::create(path.with_extension("ttf")) - .expect("Create font file"), - ) - .expect("Extract font file"); - } - - let relative_path = PathBuf::from( - std::iter::repeat("../") - .take(definition.module.split("::").count()) - .collect::<String>(), - ); - - let mut module = String::new(); - - module.push_str(&format!( - "// Generated automatically by iced_fontello at build time.\n\ - // Do not edit manually. Source: {source}\n\ - // {hash}\n\ - use iced::Font;\n\ - use iced::widget::text;\n\n\ - use crate::widget::Text;\n\n\ - pub const FONT: &[u8] = include_bytes!(\"{path}\");\n\n", - source = relative_path.join(path.with_extension("toml")).display(), - path = relative_path.join(path.with_extension("ttf")).display() - )); - - for (name, glyph) in glyphs { - module.push_str(&format!( - "\ -pub fn {name}<'a>() -> Text<'a> {{ - icon(\"\\u{{{code:X}}}\") -}}\n\n", - code = glyph.code - )); - } - - module.push_str(&format!( - "\ -fn icon(codepoint: &str) -> Text<'_> {{ - text(codepoint).font(Font::with_name(\"{file_name}\")) -}}\n" - )); - - if module != module_contents { - if let Some(directory) = module_target.parent() { - fs::create_dir_all(directory) - .expect("Create parent directory of font module"); - } - - fs::write(module_target, module).expect("Write font module"); - } - - Ok(()) -} - -#[derive(Debug, Clone)] -pub enum Error {} - -#[derive(Debug, Clone, Deserialize)] -struct Definition { - module: String, - glyphs: BTreeMap<String, String>, -} - -#[derive(Debug, Clone)] -struct Font { - name: String, - glyphs: BTreeMap<String, Glyph>, -} - -#[derive(Debug, Clone, Deserialize)] -struct Glyph { - uid: Id, - code: u64, - #[serde(rename = "css")] - name: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -struct Id(String); - -fn parse_fonts() -> BTreeMap<String, Font> { - #[derive(Deserialize)] - struct ItemSchema { - font: FontSchema, - glyphs: Vec<Glyph>, - } - - #[derive(Deserialize)] - struct FontSchema { - fontname: String, - } - - let items: Vec<ItemSchema> = - serde_json::from_str(include_str!("../fonts.json")) - .expect("Deserialize fonts"); - - items - .into_iter() - .map(|item| { - ( - item.font.fontname.clone(), - Font { - name: item.font.fontname, - glyphs: item - .glyphs - .into_iter() - .map(|glyph| (glyph.name.clone(), glyph)) - .collect(), - }, - ) - }) - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_parses_fonts() { - assert!(!parse_fonts().is_empty()); - } -} |
