use rusttype::{Font, Point, Scale};
use std::fs::{self, File};
use std::io::{self, Write};
// TODO: Build a Rust Macro that does what this does but without a build.rs
#[derive(Clone)]
struct FontMetadata {
pub charset: &'static str,
pub name: &'static str,
pub font: Font<'static>,
}
impl FontMetadata {
pub fn render_character(&self, scale: Scale, character: char) -> Vec<u8> {
let glyph = self.font.glyph(character).scaled(scale);
let bounding_box = glyph.exact_bounding_box().unwrap();
let width = bounding_box.width() as u32;
let height = bounding_box.height() as u32;
let mut image: Vec<u8> = Vec::new();
fn define_item(a: &mut Vec<u8>, i: usize, v: u8) {
if a.len() <= i {
while a.len() < i {
a.push(0x00);
}
a.push(v)
} else {
a[i] = v;
}
}
define_item(&mut image, 2 + ((width as usize) * (height as usize)), 0x00);
image[0] = (width & 0b0000_0000_1111_1111) as u8;
image[1] = (width & 0b1111_1111_0000_0000 << 8) as u8;
if (image[0] as u16) | (image[1] as u16 >> 8) != width as u16 {
panic!("Width missmatch!");
}
glyph
.positioned(Point { x: 0.0, y: 0.0 })
.draw(|gx: u32, gy: u32, v| {
let bit = (v * 255.0) as u8;
if gx < width {
define_item(&mut image, (2 + (gy * width) + gx) as usize, bit);
}
});
image
}
pub fn img_to_hex(image: Vec<u8>) -> Vec<u8> {
let mut image2: Vec<u8> = Vec::new();
let width = (image[0] as u16) | (image[1] as u16 >> 8);
let mut i: i32 = -3;
for bit in image {
i += 1;
if i >= 0 {
if i as u16 == width {
i = 0;
image2.push('\n' as u8);
}
let v = format!("{:x?}", bit);
let v = if v.len() == 1 { format!("0{}", v) } else { v };
for char in v.chars() {
image2.push(char as u8);
}
// image2.push(if bit < 80 {' ' as u8} else if bit < 150 {'-' as u8} else {'#' as u8})
}
}
image2
}
fn unique_chars(&self) -> Vec<char> {
let mut v: Vec<char> = Vec::new();
for char in self.charset.chars() {
if !v.contains(&char) {
v.push(char);
}
}
v
}
}
fn exec(font: FontMetadata) -> io::Result<()> {
let scale = Scale::uniform(32.0); // Set the font size
for c in font.unique_chars() {
let image = font.render_character(scale, c);
save_bits_to_file(&font, c as u8, &image)?;
}
Ok(())
}
fn save_bits_to_file(font: &FontMetadata, char: u8, bits: &[u8]) -> io::Result<()> {
fs::create_dir_all(format!("assets/computed-fonts/{}/", font.name))?;
let filename = format!("assets/computed-fonts/{}/{}.bin", font.name, char);
let mut file = File::create(&filename)?;
file.write_all(bits)?;
let filename = format!("assets/computed-fonts/{}/{}.txt", font.name, char);
let mut file = File::create(&filename)?;
file.write_all(&FontMetadata::img_to_hex(bits.to_vec()))?;
Ok(())
}
fn to_upper_snake_with_first_upper_preceeded_by_underscore(str: String) -> String {
let mut str = str;
for char in "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars() {
str = str.replace(char, format!("_{char}").as_str());
}
str.to_uppercase()
}
fn generate_struct(font: &FontMetadata) -> io::Result<String> {
let name = font.name;
let mut contents = format!(
"pub struct {name}Struct {{}}
impl BakedFont for {name}Struct {{
fn has_char(&self, c: char) -> bool {{
match c as u8 {{"
);
for char in font.unique_chars() {
contents = format!(
"{contents}
| {}",
char as u8,
);
}
contents = format!(
"{contents}
=> true,
_ => false
}}
}}
fn get_char_bytes(&self, c: char) -> &'static [u8] {{
match c as u8 {{
"
);
for char in font.unique_chars() {
contents = format!(
"{contents} {} => include_bytes!(\"../../assets/computed-fonts/{}/{}.bin\"),
",
char as u8, font.name, char as u8
);
}
contents = format!(
"{contents} _ => panic!(\"Glyph {{}} not included in precomputed fonts\", c)
}}
}}
}}
pub const FONT{}: {name}Struct = {name}Struct{{
}};
",
to_upper_snake_with_first_upper_preceeded_by_underscore(name.to_string())
);
Ok(contents)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
fs::create_dir_all("src/generated")?;
let mut modrs = format!(
"// Copyright is a sham this is a @generated file.
use crate::font::BakedFont;
"
);
let fonts = [
FontMetadata {
name: "Galmuri",
font: { Font::try_from_vec(fs::read("assets/fonts/Galmuri11.ttf")?).unwrap() },
charset: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz2053:",
},
FontMetadata {
name: "CherryBombOne",
font: { Font::try_from_vec(fs::read("assets/fonts/CherryBombOne.ttf")?).unwrap() },
charset: "UwUSpace",
},
];
for font in fonts {
modrs = format!(
"{modrs}{}
",
generate_struct(&font)?
);
exec(font)?;
}
File::create(&"src/generated/fonts.rs")?.write_all(modrs.as_bytes())?;
Ok(())
}