From c5cebb9520fcb6325a97b62233035d488a581ad1 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Mon, 4 Apr 2022 22:06:08 +0100 Subject: [PATCH] Theory a font renderer --- agb-image-converter/Cargo.toml | 1 + agb-image-converter/src/font_loader.rs | 36 +++++++++++++++++ agb-image-converter/src/lib.rs | 55 ++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 agb-image-converter/src/font_loader.rs diff --git a/agb-image-converter/Cargo.toml b/agb-image-converter/Cargo.toml index 850090a..6f845b2 100644 --- a/agb-image-converter/Cargo.toml +++ b/agb-image-converter/Cargo.toml @@ -17,3 +17,4 @@ syn = "1" proc-macro2 = "1" quote = "1" asefile = "0.3.4" +fontdue = "0.7" diff --git a/agb-image-converter/src/font_loader.rs b/agb-image-converter/src/font_loader.rs new file mode 100644 index 0000000..f3b5047 --- /dev/null +++ b/agb-image-converter/src/font_loader.rs @@ -0,0 +1,36 @@ +use crate::ByteString; +use quote::quote; + +use proc_macro2::TokenStream; + +struct LetterData { + width: usize, + rendered: Vec, +} + +pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream { + let font = fontdue::Font::from_bytes(font_data, Default::default()).expect("Invalid font data"); + + let font = (0..128) + .map(|i| font.rasterize(char::from_u32(i).unwrap(), pixels_per_em)) + .map(|(metrics, bitmap)| { + let width = metrics.width; + LetterData { + width, + rendered: bitmap, + } + }) + .map(|letter_data| { + let data_raw = ByteString(&letter_data.rendered); + let width = letter_data.width as u8; + + quote!(agb::display::FontLetter { + width: #width, + data: #data_raw, + }) + }); + + quote![ + #(#font),* + ] +} diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs index e4d8e95..0d45a06 100644 --- a/agb-image-converter/src/lib.rs +++ b/agb-image-converter/src/lib.rs @@ -3,7 +3,9 @@ use proc_macro::TokenStream; use proc_macro2::Literal; use syn::parse::Parser; use syn::{parse_macro_input, punctuated::Punctuated, LitStr}; +use syn::{Expr, ExprLit, Lit}; +use std::fs::File; use std::path::PathBuf; use std::{iter, path::Path, str}; @@ -12,6 +14,7 @@ use quote::{format_ident, quote, ToTokens}; mod aseprite; mod colour; mod config; +mod font_loader; mod image_loader; mod palette16; mod rust_generator; @@ -309,6 +312,58 @@ fn palete_tile_data( (palette_data, tile_data, assignments) } +#[proc_macro] +pub fn include_font(input: TokenStream) -> TokenStream { + let parser = Punctuated::::parse_separated_nonempty; + let parsed = match parser.parse(input) { + Ok(e) => e, + Err(e) => return e.to_compile_error().into(), + }; + + let all_args: Vec<_> = parsed.into_iter().collect(); + if all_args.len() != 2 { + panic!("Include_font requires 2 arguments, got {}", all_args.len()); + } + + let filename = match &all_args[0] { + Expr::Lit(ExprLit { + lit: Lit::Str(str_lit), + .. + }) => str_lit.value(), + _ => panic!("Expected literal string as first argument to include_font"), + }; + + let font_size = match &all_args[1] { + Expr::Lit(ExprLit { + lit: Lit::Float(value), + .. + }) => value.base10_parse::().expect("Invalid float literal"), + Expr::Lit(ExprLit { + lit: Lit::Int(value), + .. + }) => value + .base10_parse::() + .expect("Invalid integer literal") as f32, + _ => panic!("Expected literal float or integer as second argument to include_font"), + }; + + let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir"); + let path = Path::new(&root).join(&*filename); + + let file_content = std::fs::read(&path).expect("Failed to read ttf file"); + + let rendered = font_loader::load_font(&file_content, font_size); + + let include_path = path.to_string_lossy(); + + quote!({ + let _ = include_bytes!(#include_path); + + #rendered + }) + .into() +} + #[cfg(test)] mod tests { use asefile::AnimationDirection;