use bmfont_rs::{Char, Common, Font, Packing};
use image::{self, GrayImage, ImageFormat};
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::result::Result;
const FONT_DIR: &str = "data/examples";
const FONT: &str = "anton_latin.fnt";
const SURFACE_WIDTH: i32 = 600;
const SURFACE_HEIGHT: i32 = 300;
#[derive(Clone, Copy, Debug, Default)]
pub struct Rec2 {
top_left: Vec2,
bottom_right: Vec2,
}
impl Rec2 {
pub fn new(top_left: Vec2, bottom_right: Vec2) -> Self {
Self { top_left, bottom_right }
}
pub fn with_size(top_left: Vec2, size: Vec2) -> Self {
Self::new(top_left, Vec2::new(top_left.x + size.x, top_left.y + size.y))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Vec2 {
x: i32,
y: i32,
}
impl Vec2 {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
pub struct RenderSurface {
dst: GrayImage,
pos: Vec2,
last: Option<char>,
}
impl RenderSurface {
pub fn new(res: Vec2) -> Self {
Self { dst: GrayImage::new(res.x as u32, res.y as u32), pos: Vec2::default(), last: None }
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
self.dst.save(path)?;
Ok(())
}
pub fn println(&mut self, render_font: &RenderFont, str: &str) {
self.print(render_font, str);
self.pos.x = 0;
self.pos.y += render_font.common.line_height as i32;
self.last = None;
}
pub fn print(&mut self, render_font: &RenderFont, str: &str) {
str.chars().for_each(|character| self.print_character(render_font, character))
}
pub fn print_character(&mut self, render_font: &RenderFont, character: char) {
if let Some(char) = render_font.chars.get(&(character as u32)) {
let src_rect = Rec2::with_size(
Vec2::new(char.x as i32, char.y as i32),
Vec2::new(char.width as i32, char.height as i32),
);
let dst_pos =
Vec2::new(self.pos.x + char.xoffset as i32, self.pos.y + char.yoffset as i32);
self.pos.x += char.xadvance as i32;
if let Some(last) = self.last {
let kerning_pair = (last as u32, character as u32);
if let Some(amount) = render_font.kernings.get(&kerning_pair) {
self.pos.x += *amount as i32;
}
}
self.last = Some(character);
let src = &render_font.bitmaps[char.page as usize];
render(src, src_rect, &mut self.dst, dst_pos);
} else {
eprintln!("cannot render character: {:08X}", character as u32);
}
}
}
pub struct RenderFont {
common: Common,
bitmaps: Vec<GrayImage>,
chars: HashMap<u32, Char>,
kernings: HashMap<(u32, u32), i16>,
}
impl RenderFont {
pub fn new(font: Font, bitmaps: Vec<GrayImage>) -> Result<Self, Box<dyn Error>> {
font.validate_references()?;
let Font { common, mut chars, mut kernings, .. } = font;
let chars = chars.drain(..).map(|u| (u.id, u)).collect();
let kernings = kernings.drain(..).map(|u| ((u.first, u.second), u.amount)).collect();
Ok(Self { common, bitmaps, chars, kernings })
}
}
fn load_bitmap_font(
folder: impl AsRef<Path>,
font: impl AsRef<Path>,
) -> Result<(Font, Vec<GrayImage>), Box<dyn Error>> {
let folder: &Path = folder.as_ref();
let font: &Path = font.as_ref();
let rdr = File::open(folder.join(font))?;
let font = bmfont_rs::text::from_reader(rdr)?;
if !font.info.unicode || font.common.packed || font.common.alpha_chnl != Packing::Outline {
return Err(
format!("unsupported font descriptor: {:?}, {:?}", font.info, font.common).into()
);
}
let mut bitmaps = Vec::with_capacity(font.pages.len());
for page in &font.pages {
let rdr = BufReader::new(File::open(folder.join(page))?);
let bitmap = image::load(rdr, ImageFormat::Png).map(|u| u.into_luma8())?;
bitmaps.push(bitmap);
}
Ok((font, bitmaps))
}
fn render(src: &GrayImage, src_rect: Rec2, dst: &mut GrayImage, dst_pos: Vec2) {
let src_pos = src_rect.top_left;
let src_width = src_rect.bottom_right.x - src_pos.x;
let src_height = src_rect.bottom_right.y - src_pos.y;
let dst_width = dst.width() as i32 - dst_pos.x;
let dst_height = dst.height() as i32 - dst_pos.y;
let width = src_width.min(dst_width);
let height = src_height.min(dst_height);
for x in 0..width {
for y in 0..height {
let pixel = src.get_pixel((src_pos.x + x) as u32, (src_pos.y + y) as u32);
dst.put_pixel((dst_pos.x + x) as u32, (dst_pos.y + y) as u32, *pixel);
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
println!("missing image output filename: png or jpg");
println!("try: cargo run --example render lorem_ipsum.jpg");
return Ok(());
}
let file = &args[1];
let (font, bitmaps) = load_bitmap_font(FONT_DIR, FONT)?;
let render_font = RenderFont::new(font, bitmaps)?;
let mut render_surface = RenderSurface::new(Vec2::new(SURFACE_WIDTH, SURFACE_HEIGHT));
render_surface.println(&render_font, " Lorem ipsum dolor sit amet,");
render_surface.println(&render_font, " consectetur adipiscing elit,");
render_surface.println(&render_font, " sed do eiusmod tempor incididunt");
render_surface.println(&render_font, " ut labore et dolore magna aliqua.");
render_surface.save(file)?;
Ok(())
}