aboutsummaryrefslogblamecommitdiffstats
path: root/build.rs
blob: a6ec300e14c904c5689c6b58ed3873cdd2c711ba (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                                         

                           
 

                                                                           



                            
                   
                          
 


                                                                            




























                                                                              

                                              
 
                                        
 







                                                      
       
     

                 
                                                  

           
 




                                                                   


                                                                                                     
                                                                                    
 


                                                 
                                                                        

       




                                                           
 













                                                                                              
       







                                       
       
     
     
   

 
                                               
                                
                                                     








                                                                                    

                                          
                                                                             
                                          
                                                            

        


                                                                                   
                                                         


                    


                                                               

                                  


                             
                                        

                                                                                          


                                   












                                                       

    

                                   









                                                                                       

                                               

                                                                             


              

                                                     
                                       

                                                      
                           

    



                                                                                     
                                                                            
                                  



                                                                                         
                           
                                  


                     




                             


                                                                        

        
use rusttype::{Font, Point, Rect, 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 scale: Scale,
  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 positioned_glyph = glyph.clone().positioned(Point { x: 0.0, y: 0.0 });
    let pixel_bounding_box = {
      let bounding_box = positioned_glyph.pixel_bounding_box();
      if bounding_box.is_none() {
        Rect {
          min: Point { x: 0, y: 0 },
          max: Point {
            x: glyph.h_metrics().advance_width as i32,
            y: 0,
          },
        }
      } else {
        bounding_box.unwrap()
      }
    };
    let bounding_box = {
      let bounding_box = glyph.exact_bounding_box();
      if bounding_box.is_none() {
        Rect {
          min: Point { x: 0.0, y: 0.0 },
          max: Point {
            x: pixel_bounding_box.width() as f32,
            y: 0.0,
          },
        }
      } else {
        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,
      14 + ((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!");
    }
    // TODO: do we *really* need i32 values here? wouldn't i16 be sufficient?
    image[2..6].copy_from_slice(&pixel_bounding_box.min.x.to_le_bytes());
    image[6..10].copy_from_slice(&(pixel_bounding_box.min.y + (scale.y / 2.0) as i32).to_le_bytes());
    image[10..14].copy_from_slice(&(glyph.h_metrics().advance_width).to_le_bytes());

    positioned_glyph.draw(|gx: u32, gy: u32, v| {
      let bit = (v * 255.0) as u8;
      if gx < width {
        define_item(&mut image, (14 + (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<()> {
  for c in font.unique_chars() {
    let image = font.render_character(font.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 font_scale_y() -> f32 {{
    {}
  }}
  fn has_char(&self, c: char) -> bool {{
    match c as u8 {{",
    format!("{}{}",font.scale.y.to_string(),if font.scale.y % 1.0 == 0.0 {".0"} else {""})
  );
  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: ",
      scale: Scale::uniform(20.0),
    },
    FontMetadata {
      name: "CherryBombOne",
      font: { Font::try_from_vec(fs::read("assets/fonts/CherryBombOne.ttf")?).unwrap() },
      charset: "UwUSpace ",
      scale: Scale::uniform(20.0),
    },
  ];
  for font in fonts {
    modrs = format!(
      "{modrs}{}
",
      generate_struct(&font)?
    );
    exec(font)?;
  }
  File::create(&"src/generated/fonts.rs")?.write_all(modrs.as_bytes())?;
  Ok(())
}