mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
Kerning support (#588)
Some fonts look a bit weird if you don't do kerning. @corwinkuiper can you check if I've done the correct thing for object font rendering? I'm not entirely sure... Although it does render correctly in my tests :D - [x] Changelog updated / no changelog update needed
This commit is contained in:
commit
6fdd961b61
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Initial unicode support for font rendering.
|
- Initial unicode support for font rendering.
|
||||||
|
- Kerning support for font rendering.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,11 @@ use quote::quote;
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
|
struct KerningData {
|
||||||
|
previous_character: char,
|
||||||
|
amount: f32,
|
||||||
|
}
|
||||||
|
|
||||||
struct LetterData {
|
struct LetterData {
|
||||||
character: char,
|
character: char,
|
||||||
width: usize,
|
width: usize,
|
||||||
|
@ -11,6 +16,7 @@ struct LetterData {
|
||||||
ymin: i32,
|
ymin: i32,
|
||||||
advance_width: f32,
|
advance_width: f32,
|
||||||
rendered: Vec<u8>,
|
rendered: Vec<u8>,
|
||||||
|
kerning_data: Vec<KerningData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
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
|
let mut letters: Vec<_> = font
|
||||||
.chars()
|
.chars()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(&c, _)| (c, font.rasterize(c, pixels_per_em)))
|
.map(|(&c, &index)| (c, index, font.rasterize(c, pixels_per_em)))
|
||||||
.map(|(c, (metrics, bitmap))| {
|
.map(|(c, index, (metrics, bitmap))| {
|
||||||
let width = metrics.width;
|
let width = metrics.width;
|
||||||
let height = metrics.height;
|
let height = metrics.height;
|
||||||
|
|
||||||
|
@ -50,6 +56,25 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
||||||
})
|
})
|
||||||
.collect();
|
.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 {
|
LetterData {
|
||||||
character: c,
|
character: c,
|
||||||
width,
|
width,
|
||||||
|
@ -58,6 +83,7 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
||||||
xmin: metrics.xmin,
|
xmin: metrics.xmin,
|
||||||
ymin: metrics.ymin,
|
ymin: metrics.ymin,
|
||||||
advance_width: metrics.advance_width,
|
advance_width: metrics.advance_width,
|
||||||
|
kerning_data,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.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 xmin = letter_data.xmin as i8;
|
||||||
let ymin = letter_data.ymin as i8;
|
let ymin = letter_data.ymin as i8;
|
||||||
let advance_width = letter_data.advance_width.ceil() as u8;
|
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!(
|
quote!(
|
||||||
display::FontLetter::new(
|
display::FontLetter::new(
|
||||||
|
@ -92,6 +125,9 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
||||||
#xmin,
|
#xmin,
|
||||||
#ymin,
|
#ymin,
|
||||||
#advance_width,
|
#advance_width,
|
||||||
|
&[
|
||||||
|
#(#kerning_amounts),*
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,10 +17,12 @@ pub struct FontLetter {
|
||||||
pub(crate) xmin: i8,
|
pub(crate) xmin: i8,
|
||||||
pub(crate) ymin: i8,
|
pub(crate) ymin: i8,
|
||||||
pub(crate) advance_width: u8,
|
pub(crate) advance_width: u8,
|
||||||
|
kerning_amounts: &'static [(char, i8)],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontLetter {
|
impl FontLetter {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[allow(clippy::too_many_arguments)] // only used in macro
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
character: char,
|
character: char,
|
||||||
width: u8,
|
width: u8,
|
||||||
|
@ -29,6 +31,7 @@ impl FontLetter {
|
||||||
xmin: i8,
|
xmin: i8,
|
||||||
ymin: i8,
|
ymin: i8,
|
||||||
advance_width: u8,
|
advance_width: u8,
|
||||||
|
kerning_amounts: &'static [(char, i8)],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
character,
|
character,
|
||||||
|
@ -38,6 +41,7 @@ impl FontLetter {
|
||||||
xmin,
|
xmin,
|
||||||
ymin,
|
ymin,
|
||||||
advance_width,
|
advance_width,
|
||||||
|
kerning_amounts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +51,17 @@ impl FontLetter {
|
||||||
let bit = position % 8;
|
let bit = position % 8;
|
||||||
((byte >> bit) & 1) != 0
|
((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 {
|
pub struct Font {
|
||||||
|
@ -92,6 +107,7 @@ impl Font {
|
||||||
TextRenderer {
|
TextRenderer {
|
||||||
current_x_pos: 0,
|
current_x_pos: 0,
|
||||||
current_y_pos: 0,
|
current_y_pos: 0,
|
||||||
|
previous_character: None,
|
||||||
font: self,
|
font: self,
|
||||||
tile_pos: tile_pos.into(),
|
tile_pos: tile_pos.into(),
|
||||||
tiles: Default::default(),
|
tiles: Default::default(),
|
||||||
|
@ -103,6 +119,7 @@ impl Font {
|
||||||
pub struct TextRenderer<'a> {
|
pub struct TextRenderer<'a> {
|
||||||
current_x_pos: i32,
|
current_x_pos: i32,
|
||||||
current_y_pos: i32,
|
current_y_pos: i32,
|
||||||
|
previous_character: Option<char>,
|
||||||
font: &'a Font,
|
font: &'a Font,
|
||||||
tile_pos: Vector2D<u16>,
|
tile_pos: Vector2D<u16>,
|
||||||
tiles: HashMap<(i32, i32), DynamicTile<'a>>,
|
tiles: HashMap<(i32, i32), DynamicTile<'a>>,
|
||||||
|
@ -258,6 +275,12 @@ impl<'a, 'b> TextRenderer<'b> {
|
||||||
self.current_x_pos = 0;
|
self.current_x_pos = 0;
|
||||||
} else {
|
} else {
|
||||||
let letter = self.font.letter(c);
|
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.render_letter(letter, vram_manager, foreground_colour, background_colour);
|
||||||
self.current_x_pos += i32::from(letter.advance_width);
|
self.current_x_pos += i32::from(letter.advance_width);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub(crate) struct Preprocessed {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Preprocessor {
|
struct Preprocessor {
|
||||||
|
previous_character: Option<char>,
|
||||||
width_in_sprite: i32,
|
width_in_sprite: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +73,10 @@ impl Preprocessor {
|
||||||
}
|
}
|
||||||
letter => {
|
letter => {
|
||||||
let letter = font.letter(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 {
|
if self.width_in_sprite + letter.width as i32 > sprite_width {
|
||||||
widths.push_back(
|
widths.push_back(
|
||||||
PreprocessedElement::LetterGroup {
|
PreprocessedElement::LetterGroup {
|
||||||
|
@ -87,6 +92,8 @@ impl Preprocessor {
|
||||||
self.width_in_sprite += letter.advance_width as i32;
|
self.width_in_sprite += letter.advance_width as i32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.previous_character = Some(character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,8 @@ pub(crate) struct WordRender {
|
||||||
working: WorkingLetter,
|
working: WorkingLetter,
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
colour: usize,
|
colour: usize,
|
||||||
|
|
||||||
|
previous_character: Option<char>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WordRender {
|
impl WordRender {
|
||||||
|
@ -56,6 +58,7 @@ impl WordRender {
|
||||||
working: WorkingLetter::new(config.sprite_size),
|
working: WorkingLetter::new(config.sprite_size),
|
||||||
config,
|
config,
|
||||||
colour: 1,
|
colour: 1,
|
||||||
|
previous_character: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +85,11 @@ impl WordRender {
|
||||||
|
|
||||||
let font_letter: &crate::display::FontLetter = font.letter(c);
|
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
|
// uses more than the sprite can hold
|
||||||
let group = if self.working.x_offset + font_letter.width as i32
|
let group = if self.working.x_offset + font_letter.width as i32
|
||||||
> self.config.sprite_size.to_width_height().0 as i32
|
> self.config.sprite_size.to_width_height().0 as i32
|
||||||
|
|
Loading…
Reference in a new issue