mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 00:01:34 +11:00
Add kerning support to the font rendering
This commit is contained in:
parent
92c39ecc5d
commit
2833045322
|
@ -3,6 +3,11 @@ use quote::quote;
|
|||
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
struct KerningData {
|
||||
previous_character: char,
|
||||
amount: f32,
|
||||
}
|
||||
|
||||
struct LetterData {
|
||||
character: char,
|
||||
width: usize,
|
||||
|
@ -11,6 +16,7 @@ struct LetterData {
|
|||
ymin: i32,
|
||||
advance_width: f32,
|
||||
rendered: Vec<u8>,
|
||||
kerning_data: Vec<KerningData>,
|
||||
}
|
||||
|
||||
pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
||||
|
@ -31,8 +37,8 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
|||
let mut letters: Vec<_> = font
|
||||
.chars()
|
||||
.iter()
|
||||
.map(|(&c, _)| (c, font.rasterize(c, pixels_per_em)))
|
||||
.map(|(c, (metrics, bitmap))| {
|
||||
.map(|(&c, &index)| (c, index, font.rasterize(c, pixels_per_em)))
|
||||
.map(|(c, index, (metrics, bitmap))| {
|
||||
let width = metrics.width;
|
||||
let height = metrics.height;
|
||||
|
||||
|
@ -50,6 +56,25 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let mut kerning_data: Vec<_> = font
|
||||
.chars()
|
||||
.iter()
|
||||
.filter_map(|(&left_char, &left_index)| {
|
||||
let kerning = font.horizontal_kern_indexed(
|
||||
left_index.into(),
|
||||
index.into(),
|
||||
pixels_per_em,
|
||||
)?;
|
||||
|
||||
Some(KerningData {
|
||||
previous_character: left_char,
|
||||
amount: kerning,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
kerning_data.sort_unstable_by_key(|kd| kd.previous_character);
|
||||
|
||||
LetterData {
|
||||
character: c,
|
||||
width,
|
||||
|
@ -58,6 +83,7 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
|||
xmin: metrics.xmin,
|
||||
ymin: metrics.ymin,
|
||||
advance_width: metrics.advance_width,
|
||||
kerning_data,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
@ -82,6 +108,13 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
|||
let xmin = letter_data.xmin as i8;
|
||||
let ymin = letter_data.ymin as i8;
|
||||
let advance_width = letter_data.advance_width.ceil() as u8;
|
||||
let kerning_amounts = letter_data.kerning_data.iter().map(|kerning_data| {
|
||||
let amount = kerning_data.amount as i8;
|
||||
let c = kerning_data.previous_character;
|
||||
quote! {
|
||||
(#c, #amount)
|
||||
}
|
||||
});
|
||||
|
||||
quote!(
|
||||
display::FontLetter::new(
|
||||
|
@ -92,6 +125,9 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
|||
#xmin,
|
||||
#ymin,
|
||||
#advance_width,
|
||||
&[
|
||||
#(#kerning_amounts),*
|
||||
]
|
||||
)
|
||||
)
|
||||
});
|
||||
|
|
|
@ -17,10 +17,12 @@ pub struct FontLetter {
|
|||
pub(crate) xmin: i8,
|
||||
pub(crate) ymin: i8,
|
||||
pub(crate) advance_width: u8,
|
||||
kerning_amounts: &'static [(char, i8)],
|
||||
}
|
||||
|
||||
impl FontLetter {
|
||||
#[must_use]
|
||||
#[allow(clippy::too_many_arguments)] // only used in macro
|
||||
pub const fn new(
|
||||
character: char,
|
||||
width: u8,
|
||||
|
@ -29,6 +31,7 @@ impl FontLetter {
|
|||
xmin: i8,
|
||||
ymin: i8,
|
||||
advance_width: u8,
|
||||
kerning_amounts: &'static [(char, i8)],
|
||||
) -> Self {
|
||||
Self {
|
||||
character,
|
||||
|
@ -38,6 +41,7 @@ impl FontLetter {
|
|||
xmin,
|
||||
ymin,
|
||||
advance_width,
|
||||
kerning_amounts,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +51,17 @@ impl FontLetter {
|
|||
let bit = position % 8;
|
||||
((byte >> bit) & 1) != 0
|
||||
}
|
||||
|
||||
pub(crate) fn kerning_amount(&self, previous_char: char) -> i32 {
|
||||
if let Ok(index) = self
|
||||
.kerning_amounts
|
||||
.binary_search_by_key(&previous_char, |kerning_data| kerning_data.0)
|
||||
{
|
||||
self.kerning_amounts[index].1 as i32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Font {
|
||||
|
@ -92,6 +107,7 @@ impl Font {
|
|||
TextRenderer {
|
||||
current_x_pos: 0,
|
||||
current_y_pos: 0,
|
||||
previous_character: None,
|
||||
font: self,
|
||||
tile_pos: tile_pos.into(),
|
||||
tiles: Default::default(),
|
||||
|
@ -103,6 +119,7 @@ impl Font {
|
|||
pub struct TextRenderer<'a> {
|
||||
current_x_pos: i32,
|
||||
current_y_pos: i32,
|
||||
previous_character: Option<char>,
|
||||
font: &'a Font,
|
||||
tile_pos: Vector2D<u16>,
|
||||
tiles: HashMap<(i32, i32), DynamicTile<'a>>,
|
||||
|
@ -258,6 +275,12 @@ impl<'a, 'b> TextRenderer<'b> {
|
|||
self.current_x_pos = 0;
|
||||
} else {
|
||||
let letter = self.font.letter(c);
|
||||
|
||||
if let Some(previous_character) = self.previous_character {
|
||||
self.current_x_pos += letter.kerning_amount(previous_character);
|
||||
}
|
||||
self.previous_character = Some(c);
|
||||
|
||||
self.render_letter(letter, vram_manager, foreground_colour, background_colour);
|
||||
self.current_x_pos += i32::from(letter.advance_width);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ pub(crate) struct Preprocessed {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
struct Preprocessor {
|
||||
previous_character: Option<char>,
|
||||
width_in_sprite: i32,
|
||||
}
|
||||
|
||||
|
@ -72,6 +73,10 @@ impl Preprocessor {
|
|||
}
|
||||
letter => {
|
||||
let letter = font.letter(letter);
|
||||
if let Some(previous_character) = self.previous_character {
|
||||
self.width_in_sprite += letter.kerning_amount(previous_character);
|
||||
}
|
||||
|
||||
if self.width_in_sprite + letter.width as i32 > sprite_width {
|
||||
widths.push_back(
|
||||
PreprocessedElement::LetterGroup {
|
||||
|
@ -87,6 +92,8 @@ impl Preprocessor {
|
|||
self.width_in_sprite += letter.advance_width as i32;
|
||||
}
|
||||
}
|
||||
|
||||
self.previous_character = Some(character);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ pub(crate) struct WordRender {
|
|||
working: WorkingLetter,
|
||||
config: Configuration,
|
||||
colour: usize,
|
||||
|
||||
previous_character: Option<char>,
|
||||
}
|
||||
|
||||
impl WordRender {
|
||||
|
@ -56,6 +58,7 @@ impl WordRender {
|
|||
working: WorkingLetter::new(config.sprite_size),
|
||||
config,
|
||||
colour: 1,
|
||||
previous_character: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,6 +85,11 @@ impl WordRender {
|
|||
|
||||
let font_letter: &crate::display::FontLetter = font.letter(c);
|
||||
|
||||
if let Some(previous_character) = self.previous_character {
|
||||
self.working.x_offset += font_letter.kerning_amount(previous_character);
|
||||
}
|
||||
self.previous_character = Some(c);
|
||||
|
||||
// uses more than the sprite can hold
|
||||
let group = if self.working.x_offset + font_letter.width as i32
|
||||
> self.config.sprite_size.to_width_height().0 as i32
|
||||
|
|
Loading…
Reference in a new issue