mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
Render some text
This commit is contained in:
parent
c5cebb9520
commit
a6f5cc9ec1
|
@ -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),*])
|
||||
]
|
||||
}
|
||||
|
|
BIN
agb/examples/RobotoCondensed-Regular.ttf
Normal file
BIN
agb/examples/RobotoCondensed-Regular.ttf
Normal file
Binary file not shown.
46
agb/examples/text_render.rs
Normal file
46
agb/examples/text_render.rs
Normal 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
121
agb/src/display/font.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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) };
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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>(),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue