use alloc::vec::Vec;
use super::LetterGroup;
use crate::{
display::{
tiled::{DynamicTile16, RegularBackground, TileEffect},
utils::blit_16_colour,
},
fixnum::{Vector2D, vec2},
};
/// The background tile based renderer backend for [`LetterGroup`]s. A simple
/// use of the renderer is
///
/// ```rust
/// # #![no_std]
/// # #![no_main]
/// use agb::display::{
/// Palette16, Rgb15, Priority,
/// font::{Font, Layout, LayoutSettings, RegularBackgroundTextRenderer},
/// tiled::{RegularBackground, VRAM_MANAGER, RegularBackgroundSize, TileFormat},
/// };
///
/// static SIMPLE_PALETTE: &Palette16 = {
/// let mut palette = [Rgb15::BLACK; 16];
/// palette[1] = Rgb15::WHITE;
/// &Palette16::new(palette)
/// };
/// static FONT: Font = agb::include_font!("examples/font/pixelated.ttf", 8);
///
/// # #[agb::doctest]
/// # fn test(mut gba: agb::Gba) {
/// VRAM_MANAGER.set_background_palette(0, SIMPLE_PALETTE);
/// let mut bg = RegularBackground::new(
/// Priority::P0,
/// RegularBackgroundSize::Background32x32,
/// TileFormat::FourBpp,
/// );
///
/// // the actual text rendering
///
/// let layout = Layout::new("Hello, world!", &FONT, &LayoutSettings::new().with_max_line_length(200));
/// // Start rendering at (0, 0) using the 1st background palette
/// let mut text_renderer = RegularBackgroundTextRenderer::new((0, 0), 0);
///
/// for letter_group in layout {
/// text_renderer.show(&mut bg, &letter_group);
/// }
///
/// // display the background in the usual means
///
/// let mut gfx = gba.graphics.get();
/// let mut frame = gfx.frame();
///
/// bg.show(&mut frame);
/// # }
/// ```
pub struct RegularBackgroundTextRenderer {
tiles: Vec<Vec<Option<DynamicTile16>>>,
origin: Vector2D<i32>,
palette_id: u8,
}
impl RegularBackgroundTextRenderer {
/// Creates a new background renderer with a given origin. All text is
/// rendered with respect to this origin.
///
/// Uses palette at index `palette` for the drawn tiles.
pub fn new(origin: impl Into<Vector2D<i32>>, palette_id: u8) -> Self {
Self {
tiles: Vec::new(),
origin: origin.into(),
palette_id,
}
}
/// Displays the given letter group on the given background.
pub fn show(&mut self, bg: &mut RegularBackground, group: &LetterGroup) {
self.ensure_drawing_space(bg, group);
let dynamic_origin = vec2(self.origin.x.rem_euclid(8), self.origin.y.rem_euclid(8));
for (px_start, px) in group.pixels_packed() {
let pos = px_start + dynamic_origin + group.position();
let x = pos.x as usize / 8;
let y = pos.y as usize / 8;
let row = &mut self.tiles[y];
let x_in_tile = pos.x.rem_euclid(8) * 4;
let tile_left = row[x].as_mut().expect("should have ensured space");
let y_index = pos.y.rem_euclid(8) as usize;
blit_16_colour(
&mut tile_left.data_mut()[y_index..y_index + 1],
&[px << x_in_tile],
);
if x_in_tile > 0 {
let tile_right = row[x + 1].as_mut().expect("should have ensured space");
blit_16_colour(
&mut tile_right.data_mut()[y_index..y_index + 1],
&[px >> (32 - x_in_tile)],
);
}
}
}
fn ensure_drawing_space(&mut self, bg: &mut RegularBackground, group: &LetterGroup) {
let dynamic_origin = vec2(self.origin.x.rem_euclid(8), self.origin.y.rem_euclid(8));
let tile_offset = vec2(self.origin.x / 8, self.origin.y / 8);
let bounds = group.bounds();
let top_left_tile = group.position() / 8;
let bottom_right_tile = (dynamic_origin + bounds + group.position()) / 8 + vec2(1, 0);
if self.tiles.len() <= bottom_right_tile.y as usize {
self.tiles
.resize_with(bottom_right_tile.y as usize + 1, Vec::new);
}
for row_idx in top_left_tile.y..(bottom_right_tile.y + 1) {
let row = &mut self.tiles[row_idx as usize];
if row.len() <= bottom_right_tile.x as usize {
row.resize_with(bottom_right_tile.x as usize + 1, || None);
}
for column_idx in top_left_tile.x..(bottom_right_tile.x + 1) {
if row[column_idx as usize].is_none() {
let tile_pos = vec2(column_idx, row_idx) + tile_offset;
let tile = DynamicTile16::new().fill_with(0);
bg.set_tile_dynamic16(
tile_pos,
&tile,
TileEffect::default().palette(self.palette_id),
);
row[column_idx as usize] = Some(tile);
}
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
Gba,
display::{
Priority, Rgb15,
font::{ChangeColour, Font, Layout, layout::LayoutSettings},
palette16::Palette16,
tiled::{RegularBackgroundSize, TileFormat, VRAM_MANAGER},
},
test_runner::assert_image_output,
timer::Divider,
};
use alloc::format;
static FONT: Font = include_font!("fnt/ark-pixel-10px-proportional-latin.ttf", 10);
#[test_case]
fn background_text_render_english(gba: &mut Gba) {
let mut gfx = gba.graphics.get();
static PALETTE: Palette16 = const {
let mut palette = [Rgb15::BLACK; 16];
palette[1] = Rgb15::WHITE;
palette[2] = Rgb15(0x10_7C);
Palette16::new(palette)
};
VRAM_MANAGER.set_background_palette(0, &PALETTE);
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp,
);
const CHANGE1: ChangeColour = ChangeColour::new(1);
const CHANGE2: ChangeColour = ChangeColour::new(2);
let layout = Layout::new(
&format!("Hello, world! {CHANGE2}This is in red{CHANGE1} and back to white"),
&FONT,
&LayoutSettings::new()
.with_max_line_length(200)
.with_max_group_width(128),
);
let mut bg_text_render = RegularBackgroundTextRenderer::new((20, 20), 0);
for lg in layout {
bg_text_render.show(&mut bg, &lg);
}
let mut frame = gfx.frame();
bg.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/bg_font_render_simple.png");
}
#[test_case]
fn background_text_render_japanese(gba: &mut Gba) {
let mut gfx = gba.graphics.get();
static PALETTE: Palette16 = const {
let mut palette = [Rgb15::BLACK; 16];
palette[1] = Rgb15::WHITE;
palette[2] = Rgb15(0x10_7C);
Palette16::new(palette)
};
VRAM_MANAGER.set_background_palette(0, &PALETTE);
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x64,
TileFormat::FourBpp,
);
let layout = Layout::new(
"現代社会において、情報技術の進化は目覚ましい。それは、私たちの生活様式だけでなく、思考様式にも大きな影響を与えている。例えば、スマートフォンやタブレット端末の普及により、いつでもどこでも情報にアクセスできるようになった。これにより、知識の共有やコミュニケーションが容易になり、新しい文化や価値観が生まれている。しかし、一方で、情報過多やプライバシーの問題など、新たな課題も浮上している。私たちは、これらの課題にどのように向き合い、情報技術をどのように活用していくべきだろうか。それは、私たち一人ひとりが真剣に考えるべき重要なテーマである。",
&FONT,
&LayoutSettings::new().with_max_line_length(200),
);
let mut bg_text_render = RegularBackgroundTextRenderer::new((20, 20), 0);
for lg in layout {
bg_text_render.show(&mut bg, &lg);
}
let mut frame = gfx.frame();
bg.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/bg_font_render_japanese.png");
}
#[test_case]
fn background_text_single_group(gba: &mut Gba) {
static PALETTE: Palette16 = const {
let mut palette = [Rgb15::BLACK; 16];
palette[1] = Rgb15::WHITE;
palette[2] = Rgb15(0x10_7C);
Palette16::new(palette)
};
VRAM_MANAGER.set_background_palette(0, &PALETTE);
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x64,
TileFormat::FourBpp,
);
let mut layout = Layout::new(
"現代社会において、情報技術の進化は目覚ましい。それは、私たちの生活様式だけでなく、思考様式にも大きな影響を与えている。例えば、スマートフォンやタブレット端末の普及により、いつでもどこでも情報にアクセスできるようになった。これにより、知識の共有やコミュニケーションが容易になり、新しい文化や価値観が生まれている。しかし、一方で、情報過多やプライバシーの問題など、新たな課題も浮上している。私たちは、これらの課題にどのように向き合い、情報技術をどのように活用していくべきだろうか。それは、私たち一人ひとりが真剣に考えるべき重要なテーマである。",
&FONT,
&LayoutSettings::new()
.with_max_line_length(200)
.with_max_group_width(32),
);
let mut bg_text_render = RegularBackgroundTextRenderer::new((20, 20), 0);
let letter_group = layout.next().unwrap();
let mut timer = gba.timers.timers().timer2;
timer
.set_divider(Divider::Divider256)
.set_overflow_amount(u16::MAX)
.set_cascade(false)
.set_enabled(true);
let before_show = timer.value();
bg_text_render.show(&mut bg, &core::hint::black_box(letter_group));
let after_show = timer.value();
let total = u32::from(after_show.wrapping_sub(before_show)) * 256;
crate::println!("rendering time: {total}");
}
}