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 {
|
struct LetterData {
|
||||||
width: usize,
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
xmin: i32,
|
||||||
|
ymin: i32,
|
||||||
|
advance_width: f32,
|
||||||
rendered: Vec<u8>,
|
rendered: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
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)
|
let font = (0..128)
|
||||||
.map(|i| font.rasterize(char::from_u32(i).unwrap(), pixels_per_em))
|
.map(|i| font.rasterize(char::from_u32(i).unwrap(), pixels_per_em))
|
||||||
.map(|(metrics, bitmap)| {
|
.map(|(metrics, bitmap)| {
|
||||||
let width = metrics.width;
|
let width = metrics.width;
|
||||||
|
let height = metrics.height;
|
||||||
LetterData {
|
LetterData {
|
||||||
width,
|
width,
|
||||||
|
height,
|
||||||
rendered: bitmap,
|
rendered: bitmap,
|
||||||
|
xmin: metrics.xmin,
|
||||||
|
ymin: metrics.ymin,
|
||||||
|
advance_width: metrics.advance_width,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|letter_data| {
|
.map(|letter_data| {
|
||||||
let data_raw = ByteString(&letter_data.rendered);
|
let data_raw = ByteString(&letter_data.rendered);
|
||||||
|
let height = letter_data.height as u8;
|
||||||
let width = letter_data.width 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 {
|
quote!(
|
||||||
width: #width,
|
agb::display::FontLetter::new(
|
||||||
data: #data_raw,
|
#width,
|
||||||
})
|
#height,
|
||||||
|
#data_raw,
|
||||||
|
#xmin,
|
||||||
|
#ymin,
|
||||||
|
#advance_width,
|
||||||
|
)
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
quote![
|
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.
|
/// Giving out graphics mode.
|
||||||
pub mod video;
|
pub mod video;
|
||||||
|
|
||||||
|
mod font;
|
||||||
|
pub use font::{Font, FontLetter};
|
||||||
|
|
||||||
const DISPLAY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0000) };
|
const DISPLAY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0000) };
|
||||||
pub(crate) const DISPLAY_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0004) };
|
pub(crate) const DISPLAY_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0004) };
|
||||||
const VCOUNT: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0006) };
|
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 infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
|
||||||
pub use map::{MapLoan, RegularMap};
|
pub use map::{MapLoan, RegularMap};
|
||||||
pub use tiled0::Tiled0;
|
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)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
|
|
@ -120,7 +120,21 @@ impl TileReferenceCount {
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct DynamicTile<'a> {
|
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<'_> {
|
impl DynamicTile<'_> {
|
||||||
|
@ -194,8 +208,15 @@ impl VRamManager {
|
||||||
TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index as u16));
|
TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index as u16));
|
||||||
|
|
||||||
DynamicTile {
|
DynamicTile {
|
||||||
tile_data: &mut tiles
|
tile_data: unsafe {
|
||||||
[index * tile_format.tile_size()..(index + 1) * tile_format.tile_size()],
|
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_aseprite_inner;
|
||||||
|
|
||||||
|
pub use agb_image_converter::include_font;
|
||||||
|
|
||||||
/// This macro declares the entry point to your game written using `agb`.
|
/// 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.
|
/// 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