Render some text

This commit is contained in:
Gwilym Kuiper 2022-04-05 22:32:11 +01:00
parent c5cebb9520
commit a6f5cc9ec1
8 changed files with 229 additions and 10 deletions

View file

@ -5,32 +5,58 @@ use proc_macro2::TokenStream;
struct LetterData {
width: usize,
height: usize,
xmin: i32,
ymin: i32,
advance_width: f32,
rendered: Vec<u8>,
}
pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
let font = fontdue::Font::from_bytes(font_data, Default::default()).expect("Invalid font data");
let font = fontdue::Font::from_bytes(
font_data,
fontdue::FontSettings {
collection_index: 0,
scale: pixels_per_em,
},
)
.expect("Invalid font data");
let font = (0..128)
.map(|i| font.rasterize(char::from_u32(i).unwrap(), pixels_per_em))
.map(|(metrics, bitmap)| {
let width = metrics.width;
let height = metrics.height;
LetterData {
width,
height,
rendered: bitmap,
xmin: metrics.xmin,
ymin: metrics.ymin,
advance_width: metrics.advance_width,
}
})
.map(|letter_data| {
let data_raw = ByteString(&letter_data.rendered);
let height = letter_data.height as u8;
let width = letter_data.width as u8;
let xmin = letter_data.xmin as i8;
let ymin = letter_data.ymin as i8;
let advance_width = letter_data.advance_width as u8;
quote!(agb::display::FontLetter {
width: #width,
data: #data_raw,
})
quote!(
agb::display::FontLetter::new(
#width,
#height,
#data_raw,
#xmin,
#ymin,
#advance_width,
)
)
});
quote![
#(#font),*
agb::display::Font::new(&[#(#font),*])
]
}

Binary file not shown.

View file

@ -0,0 +1,46 @@
#![no_std]
#![no_main]
use agb::{
display::{palette16::Palette16, tiled::TileSetting, Font, Priority},
include_font,
};
const FONT: Font = include_font!("examples/RobotoCondensed-Regular.ttf", 14);
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let vblank = agb::interrupt::VBlank::get();
vram.set_background_palettes(&[Palette16::new([
0x0000, 0x0ff0, 0x00ff, 0xf00f, 0xf0f0, 0x0f0f, 0xaaaa, 0x5555, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
])]);
let background_tile = vram.new_dynamic_tile().fill_with(0);
let mut bg = gfx.background(Priority::P0);
for y in 0..20u16 {
for x in 0..30u16 {
bg.set_tile(
&mut vram,
(x, y).into(),
&background_tile.tile_set(),
TileSetting::from_raw(background_tile.tile_index()),
);
}
}
vram.remove_dynamic_tile(background_tile);
FONT.render_text(3, 3, "Hello, World!", 1, 2, 100, &mut bg, &mut vram);
bg.commit();
bg.show();
loop {
vblank.wait_for_vblank();
}
}

121
agb/src/display/font.rs Normal file
View file

@ -0,0 +1,121 @@
use super::tiled::{DynamicTile, RegularMap, TileSetting, VRamManager};
use alloc::{vec, vec::Vec};
pub struct FontLetter {
width: u8,
height: u8,
data: &'static [u8],
xmin: i8,
ymin: i8,
advance_width: u8,
}
impl FontLetter {
pub const fn new(
width: u8,
height: u8,
data: &'static [u8],
xmin: i8,
ymin: i8,
advance_width: u8,
) -> Self {
Self {
width,
height,
data,
xmin,
ymin,
advance_width,
}
}
}
pub struct Font {
letters: &'static [FontLetter],
}
impl Font {
pub const fn new(letters: &'static [FontLetter]) -> Self {
Self { letters }
}
fn letter(&self, letter: char) -> &'static FontLetter {
&self.letters[letter as usize]
}
}
impl Font {
pub fn render_text(
&self,
tile_x: u16,
tile_y: u16,
text: &str,
foreground_colour: u8,
background_colour: u8,
_max_width: i32,
bg: &mut RegularMap,
vram_manager: &mut VRamManager,
) -> i32 {
let mut tiles: Vec<Vec<DynamicTile>> = vec![];
let mut render_pixel = |x: u16, y: u16| {
let tile_x = (x / 8) as usize;
let tile_y = (y / 8) as usize;
let inner_x = x % 8;
let inner_y = y % 8;
if tiles.len() <= tile_x {
tiles.resize_with(tile_x + 1, || vec![]);
}
let x_dynamic_tiles = &mut tiles[tile_x];
if x_dynamic_tiles.len() <= tile_y {
x_dynamic_tiles.resize_with(tile_y + 1, || {
vram_manager.new_dynamic_tile().fill_with(background_colour)
});
}
let colour = foreground_colour as u32;
let index = (inner_x + inner_y * 8) as usize;
tiles[tile_x][tile_y].tile_data[index / 8] |= colour << ((index % 8) * 4);
};
let mut current_x_pos = 0i32;
let current_y_pos = 0i32;
for c in text.chars() {
let letter = self.letter(c);
for letter_y in 0..(letter.height as i32) {
for letter_x in 0..(letter.width as i32) {
let x = current_x_pos + letter_x;
let y = current_y_pos + letter_y;
let px = letter.data[(letter_x + letter_y * letter.width as i32) as usize];
if px > 100 {
render_pixel(x as u16, y as u16);
}
}
}
current_x_pos += letter.advance_width as i32;
}
for (x, x_tiles) in tiles.into_iter().enumerate() {
for (y, tile) in x_tiles.into_iter().enumerate() {
bg.set_tile(
vram_manager,
(tile_x + x as u16, tile_y + y as u16).into(),
&tile.tile_set(),
TileSetting::from_raw(tile.tile_index()),
);
vram_manager.remove_dynamic_tile(tile);
}
}
1
}
}

View file

@ -23,6 +23,9 @@ pub mod tiled;
/// Giving out graphics mode.
pub mod video;
mod font;
pub use font::{Font, FontLetter};
const DISPLAY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0000) };
pub(crate) const DISPLAY_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0004) };
const VCOUNT: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0006) };

View file

@ -6,7 +6,7 @@ mod vram_manager;
pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
pub use map::{MapLoan, RegularMap};
pub use tiled0::Tiled0;
pub use vram_manager::{TileFormat, TileIndex, TileSet, VRamManager};
pub use vram_manager::{DynamicTile, TileFormat, TileIndex, TileSet, VRamManager};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]

View file

@ -120,7 +120,21 @@ impl TileReferenceCount {
#[non_exhaustive]
pub struct DynamicTile<'a> {
pub tile_data: &'a mut [u8],
pub tile_data: &'a mut [u32],
}
impl DynamicTile<'_> {
pub fn fill_with(self, colour_index: u8) -> Self {
let colour_index = colour_index as u32;
let mut value = 0;
for i in 0..8 {
value |= colour_index << (i * 4);
}
self.tile_data.fill(value);
self
}
}
impl DynamicTile<'_> {
@ -194,8 +208,15 @@ impl VRamManager {
TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index as u16));
DynamicTile {
tile_data: &mut tiles
[index * tile_format.tile_size()..(index + 1) * tile_format.tile_size()],
tile_data: unsafe {
slice::from_raw_parts_mut(
tiles
.as_mut_ptr()
.add((index * tile_format.tile_size()) as usize)
.cast(),
tile_format.tile_size() / core::mem::size_of::<u32>(),
)
},
}
}

View file

@ -114,6 +114,8 @@ pub use agb_image_converter::include_gfx;
pub use agb_image_converter::include_aseprite_inner;
pub use agb_image_converter::include_font;
/// This macro declares the entry point to your game written using `agb`.
///
/// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, takes 1 argument and never returns.