#![allow(warnings)]
#![warn(missing_docs)]
use std::borrow::Cow;
use std::collections::HashMap;
use std::default::Default;
use std::io::Read;
use std::rc::Rc;
use rusttype::{Point, Rect};
#[cfg(feature = "render")]
mod render;
#[cfg(feature = "render")]
pub use render::*;
pub type AtlasCharacterInfos = HashMap<char, CharacterInfos>;
pub struct Texture {
pub data: Vec<u8>,
pub width: u32,
pub height: u32,
}
pub struct FontAtlas {
pub texture: Texture,
pub character_infos: AtlasCharacterInfos,
pub font_size: u32,
}
#[derive(Debug)]
pub enum Error {
NoGlyph(char),
RusttypeError(rusttype::Error),
}
impl From<rusttype::Error> for Error {
fn from(error: rusttype::Error) -> Self {
Error::RusttypeError(error)
}
}
#[derive(Copy, Clone, Debug)]
pub struct CharacterInfos {
pub tex_coords: (f32, f32),
pub tex_size: (f32, f32),
pub size: (f32, f32),
pub height_over_line: f32,
pub left_padding: f32,
pub right_padding: f32,
}
impl CharacterInfos {
pub fn scale(&self, font_size: f32) -> CharacterInfos {
CharacterInfos {
tex_coords: self.tex_coords,
tex_size: self.tex_size,
size: (self.size.0 * font_size, self.size.1 * font_size),
height_over_line: self.height_over_line * font_size,
left_padding: self.left_padding * font_size,
right_padding: self.right_padding * font_size,
}
}
}
impl FontAtlas {
pub fn ascii_character_list() -> Vec<char> {
(0..255).filter_map(::std::char::from_u32).collect()
}
pub fn cyrllic_character_list() -> Vec<char> {
let ranges = [
0x0020u32..0x00FF, 0x0400u32..0x052F, 0x2DE0u32..0x2DFF, 0xA640u32..0xA69F, ];
flatten_ranges(ranges.iter())
}
pub fn thai_character_list() -> Vec<char> {
let ranges = [
0x0020u32..0x00FF, 0x2010u32..0x205E, 0x0E00u32..0x0E7F, ];
flatten_ranges(ranges.iter())
}
pub fn vietnamese_character_list() -> Vec<char> {
let ranges = [
0x0020u32..0x00FF, 0x0102u32..0x0103,
0x0110u32..0x0111,
0x0128u32..0x0129,
0x0168u32..0x0169,
0x01A0u32..0x01A1,
0x01AFu32..0x01B0,
0x1EA0u32..0x1EF9,
];
flatten_ranges(ranges.iter())
}
pub fn new<R, I>(font: R, font_size: u32, characters_list: I) -> Result<FontAtlas, Error>
where
R: Read,
I: IntoIterator<Item = char>,
{
let font: Vec<u8> = font.bytes().map(|c| c.unwrap()).collect();
let collection = ::rusttype::FontCollection::from_bytes(&font[..])?;
let font = collection.into_font().unwrap();
let (texture, chr_infos) = build_font_image(&font, characters_list.into_iter(), font_size)?;
Ok(FontAtlas {
texture,
character_infos: chr_infos,
font_size,
})
}
}
fn flatten_ranges<'a>(ranges: impl Iterator<Item = &'a std::ops::Range<u32>>) -> Vec<char> {
ranges
.cloned()
.flatten()
.map(|c| std::char::from_u32(c).unwrap())
.collect()
}
fn build_font_image<I>(
font: &rusttype::Font,
characters_list: I,
font_size: u32,
) -> Result<(Texture, HashMap<char, CharacterInfos>), Error>
where
I: Iterator<Item = char>,
{
use std::iter;
const MARGIN: u32 = 2;
let invalid_character_width = font_size / 4;
let size_estimation = characters_list.size_hint().1.unwrap_or(0);
let mut texture_data: Vec<u8> =
Vec::with_capacity(size_estimation * font_size as usize * font_size as usize);
let texture_width = get_nearest_po2(std::cmp::max(
font_size * 2 as u32,
((((size_estimation as u32) * font_size * font_size) as f32).sqrt()) as u32,
));
let mut cursor_offset = (0u32, 0u32);
let mut rows_to_skip = 0u32;
let em_pixels = font_size as f32;
let characters_infos = characters_list
.map(|character| {
struct Bitmap {
rows: i32,
width: i32,
buffer: Vec<u8>,
}
let scaled_glyph = font.glyph(character).scaled(::rusttype::Scale {
x: font_size as f32,
y: font_size as f32,
});
let h_metrics = scaled_glyph.h_metrics();
let glyph = scaled_glyph.positioned(::rusttype::Point { x: 0.0, y: 0.0 });
let bb = glyph.pixel_bounding_box();
let bb = if let Some(bb) = bb {
bb
} else {
Rect {
min: Point { x: 0, y: 0 },
max: Point {
x: invalid_character_width as i32,
y: 0,
},
}
};
let mut buffer = vec![0; (bb.height() * bb.width()) as usize];
glyph.draw(|x, y, v| {
let x = x;
let y = y;
buffer[(y * bb.width() as u32 + x) as usize] = (v * 255.0) as u8;
});
let bitmap: Bitmap = Bitmap {
rows: bb.height(),
width: bb.width(),
buffer,
};
cursor_offset.0 += MARGIN;
if cursor_offset.0 + (bitmap.width as u32) + MARGIN >= texture_width {
assert!(bitmap.width as u32 <= texture_width); cursor_offset.0 = 0;
cursor_offset.1 += rows_to_skip;
rows_to_skip = 0;
}
if rows_to_skip < MARGIN + bitmap.rows as u32 {
let diff = MARGIN + (bitmap.rows as u32) - rows_to_skip;
rows_to_skip = MARGIN + bitmap.rows as u32;
texture_data.extend(iter::repeat(0).take((diff * texture_width * 4) as usize));
}
let offset_x_before_copy = cursor_offset.0;
if bitmap.rows >= 1 {
let destination = &mut texture_data
[(cursor_offset.0 * 4 + cursor_offset.1 * texture_width * 4) as usize..];
let source = &bitmap.buffer;
for y in 0..bitmap.rows as u32 {
let source = &source[(y * bitmap.width as u32) as usize..];
let destination = &mut destination[(y * texture_width * 4) as usize..];
for x in 0..bitmap.width {
for channel in 0..4 {
let val: u8 = source[x as usize];
let dest = &mut destination[x as usize * 4 + channel];
*dest = val;
}
}
}
cursor_offset.0 += bitmap.width as u32;
debug_assert!(cursor_offset.0 <= texture_width);
}
Ok((
character,
CharacterInfos {
tex_size: (bitmap.width as f32, bitmap.rows as f32),
tex_coords: (offset_x_before_copy as f32, cursor_offset.1 as f32),
size: (bitmap.width as f32, bitmap.rows as f32),
left_padding: h_metrics.left_side_bearing as f32,
right_padding: (h_metrics.advance_width
- bitmap.width as f32
- h_metrics.left_side_bearing as f32)
as f32,
height_over_line: -bb.min.y as f32,
},
))
})
.collect::<Result<Vec<_>, Error>>()?;
{
let current_height = texture_data.len() as u32 / texture_width;
let requested_height = get_nearest_po2(current_height);
texture_data.extend(
iter::repeat(0)
.take((texture_width * 4 * (requested_height - current_height)) as usize),
);
}
assert!((texture_data.len() as u32 % (texture_width * 4)) == 0);
let texture_height = (texture_data.len() as u32 / texture_width / 4) as f32;
let float_texture_width = texture_width as f32;
let mut characters_infos = characters_infos
.into_iter()
.map(|mut chr| {
chr.1.tex_size.0 /= float_texture_width;
chr.1.tex_size.1 /= texture_height;
chr.1.tex_coords.0 /= float_texture_width;
chr.1.tex_coords.1 /= texture_height;
chr.1.size.0 /= em_pixels;
chr.1.size.1 /= em_pixels;
chr.1.left_padding /= em_pixels;
chr.1.right_padding /= em_pixels;
chr.1.height_over_line /= em_pixels;
chr
})
.collect::<HashMap<_, _>>();
characters_infos.shrink_to_fit();
Ok((
Texture {
data: texture_data,
width: texture_width,
height: texture_height as u32,
},
characters_infos,
))
}
fn get_nearest_po2(mut x: u32) -> u32 {
assert!(x > 0);
x -= 1;
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
x + 1
}