mirror of
https://github.com/italicsjenga/agb.git
synced 2025-02-02 04:26:40 +11:00
text rendering that supports different alignments
This commit is contained in:
parent
d5d3d1a658
commit
f947d82049
4 changed files with 556 additions and 108 deletions
|
@ -4,7 +4,7 @@
|
|||
use agb::{
|
||||
display::{
|
||||
object::{
|
||||
font::{BufferedWordRender, Configuration},
|
||||
font::{BufferedRender, TextAlignment},
|
||||
PaletteVram, Size,
|
||||
},
|
||||
palette16::Palette16,
|
||||
|
@ -15,6 +15,9 @@ use agb::{
|
|||
};
|
||||
use agb_fixnum::Rect;
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use core::fmt::Write;
|
||||
|
||||
const FONT: Font = include_font!("examples/font/pixelated.ttf", 8);
|
||||
|
@ -32,9 +35,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
let palette = Palette16::new(palette);
|
||||
let palette = PaletteVram::new(&palette).unwrap();
|
||||
|
||||
let config = Configuration::new(Size::S16x8, palette);
|
||||
|
||||
let mut wr = BufferedWordRender::new(&FONT, config);
|
||||
let mut wr = BufferedRender::new(&FONT, Size::S16x8, palette);
|
||||
let _ = writeln!(
|
||||
wr,
|
||||
"{}",
|
||||
|
@ -54,22 +55,40 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
let mut num_letters = 0;
|
||||
let mut frame = 0;
|
||||
|
||||
let mut alignment = TextAlignment::Left;
|
||||
|
||||
let mut text = Vec::new();
|
||||
|
||||
loop {
|
||||
vblank.wait_for_vblank();
|
||||
input.update();
|
||||
let oam = &mut unmanaged.iter();
|
||||
wr.commit(oam);
|
||||
for (letter, slot) in text.iter().zip(oam) {
|
||||
slot.set(letter);
|
||||
}
|
||||
|
||||
let start = timer.value();
|
||||
wr.update(
|
||||
Rect::new((WIDTH / 8, 0).into(), (80, 100).into()),
|
||||
num_letters,
|
||||
);
|
||||
wr.process();
|
||||
text = wr.layout(
|
||||
Rect::new((WIDTH / 8, 0).into(), (80, 100).into()),
|
||||
alignment,
|
||||
num_letters,
|
||||
2,
|
||||
);
|
||||
let end = timer.value();
|
||||
|
||||
agb::println!("Took {} cycles", 256 * (end.wrapping_sub(start) as u32));
|
||||
|
||||
if input.is_just_pressed(Button::LEFT) {
|
||||
alignment = TextAlignment::Left;
|
||||
}
|
||||
if input.is_just_pressed(Button::RIGHT) {
|
||||
alignment = TextAlignment::Right;
|
||||
}
|
||||
if input.is_just_pressed(Button::UP | Button::DOWN) {
|
||||
alignment = TextAlignment::Center;
|
||||
}
|
||||
|
||||
frame += 1;
|
||||
|
||||
// if frame % 2 == 0 {
|
||||
|
|
|
@ -1,127 +1,230 @@
|
|||
use core::fmt::Write;
|
||||
|
||||
use agb_fixnum::{Rect, Vector2D};
|
||||
use alloc::{collections::VecDeque, vec::Vec};
|
||||
|
||||
use crate::display::Font;
|
||||
|
||||
use super::{DynamicSprite, PaletteVram, Size, SpriteVram};
|
||||
use self::{
|
||||
preprocess::{Line, Preprocessed, PreprocessedElement},
|
||||
renderer::{Configuration, WordRender},
|
||||
};
|
||||
|
||||
struct LetterGroup {
|
||||
use super::{DynamicSprite, ObjectUnmanaged, PaletteVram, Size, SpriteVram};
|
||||
|
||||
mod preprocess;
|
||||
mod renderer;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub(crate) enum WhiteSpace {
|
||||
NewLine,
|
||||
Space,
|
||||
}
|
||||
|
||||
impl WhiteSpace {
|
||||
pub(crate) fn from_char(c: char) -> Self {
|
||||
match c {
|
||||
' ' => WhiteSpace::Space,
|
||||
'\n' => WhiteSpace::NewLine,
|
||||
_ => panic!("char not supported whitespace"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LetterGroup {
|
||||
sprite: SpriteVram,
|
||||
// the width of the letter group
|
||||
width: u16,
|
||||
left: i16,
|
||||
}
|
||||
|
||||
struct WorkingLetter {
|
||||
dynamic: DynamicSprite,
|
||||
// the x offset of the current letter with respect to the start of the current letter group
|
||||
x_position: i32,
|
||||
// where to render the letter from x_min to x_max
|
||||
x_offset: i32,
|
||||
}
|
||||
|
||||
impl WorkingLetter {
|
||||
fn new(size: Size) -> Self {
|
||||
Self {
|
||||
dynamic: DynamicSprite::new(size),
|
||||
x_position: 0,
|
||||
x_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.x_position = 0;
|
||||
self.x_offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Configuration {
|
||||
sprite_size: Size,
|
||||
palette: PaletteVram,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
#[must_use]
|
||||
pub fn new(sprite_size: Size, palette: PaletteVram) -> Self {
|
||||
Self {
|
||||
sprite_size,
|
||||
palette,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WordRender<'font> {
|
||||
pub struct BufferedRender<'font> {
|
||||
char_render: WordRender,
|
||||
preprocessor: Preprocessed,
|
||||
buffered_chars: VecDeque<char>,
|
||||
letters: Letters,
|
||||
font: &'font Font,
|
||||
working: WorkingLetter,
|
||||
config: Configuration,
|
||||
}
|
||||
|
||||
impl<'font> WordRender<'font> {
|
||||
#[derive(Debug, Default)]
|
||||
struct Letters {
|
||||
letters: Vec<LetterGroup>,
|
||||
word_lengths: Vec<u8>,
|
||||
current_word_length: usize,
|
||||
number_of_groups: usize,
|
||||
}
|
||||
|
||||
impl Write for BufferedRender<'_> {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
for c in s.chars() {
|
||||
self.input_character(c);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum TextAlignment {
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
}
|
||||
|
||||
struct TextAlignmentSettings {
|
||||
space_width: i32,
|
||||
start_x: i32,
|
||||
}
|
||||
|
||||
impl TextAlignment {
|
||||
fn settings(
|
||||
self,
|
||||
line: &Line,
|
||||
minimum_space_width: i32,
|
||||
size: Vector2D<i32>,
|
||||
) -> TextAlignmentSettings {
|
||||
match self {
|
||||
TextAlignment::Left => TextAlignmentSettings {
|
||||
space_width: minimum_space_width,
|
||||
start_x: 0,
|
||||
},
|
||||
TextAlignment::Right => TextAlignmentSettings {
|
||||
space_width: minimum_space_width,
|
||||
start_x: size.x - line.width(),
|
||||
},
|
||||
TextAlignment::Center => TextAlignmentSettings {
|
||||
space_width: minimum_space_width,
|
||||
start_x: (size.x - line.width()) / 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'font> BufferedRender<'font> {
|
||||
#[must_use]
|
||||
fn new(font: &'font Font, config: Configuration) -> Self {
|
||||
WordRender {
|
||||
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
||||
let config = Configuration::new(sprite_size, palette);
|
||||
BufferedRender {
|
||||
char_render: WordRender::new(config),
|
||||
preprocessor: Preprocessed::new(),
|
||||
buffered_chars: VecDeque::new(),
|
||||
letters: Default::default(),
|
||||
font,
|
||||
working: WorkingLetter::new(config.sprite_size),
|
||||
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WordRender<'_> {
|
||||
#[must_use]
|
||||
fn finalise_letter(&mut self) -> Option<LetterGroup> {
|
||||
if self.working.x_offset == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut new_sprite = DynamicSprite::new(self.config.sprite_size);
|
||||
core::mem::swap(&mut self.working.dynamic, &mut new_sprite);
|
||||
let sprite = new_sprite.to_vram(self.config.palette.clone());
|
||||
|
||||
let group = LetterGroup {
|
||||
sprite,
|
||||
width: self.working.x_offset as u16,
|
||||
left: self.working.x_position as i16,
|
||||
};
|
||||
self.working.reset();
|
||||
|
||||
Some(group)
|
||||
impl BufferedRender<'_> {
|
||||
fn input_character(&mut self, character: char) {
|
||||
self.preprocessor.add_character(self.font, character);
|
||||
self.buffered_chars.push_back(character);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn render_char(&mut self, c: char) -> Option<LetterGroup> {
|
||||
let font_letter = self.font.letter(c);
|
||||
|
||||
// uses more than the sprite can hold
|
||||
let group = if self.working.x_offset + font_letter.width as i32
|
||||
> self.config.sprite_size.to_width_height().0 as i32
|
||||
{
|
||||
self.finalise_letter()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.working.x_offset == 0 {
|
||||
self.working.x_position = font_letter.xmin as i32;
|
||||
} else {
|
||||
self.working.x_offset += font_letter.xmin as i32;
|
||||
}
|
||||
|
||||
let y_position = self.font.ascent() - font_letter.height as i32 - font_letter.ymin as i32;
|
||||
|
||||
for y in 0..font_letter.height as usize {
|
||||
for x in 0..font_letter.width as usize {
|
||||
let rendered = font_letter.bit_absolute(x, y);
|
||||
if rendered {
|
||||
self.working.dynamic.set_pixel(
|
||||
x + self.working.x_offset as usize,
|
||||
(y_position + y as i32) as usize,
|
||||
1,
|
||||
pub fn process(&mut self) {
|
||||
let Some(c) = self.buffered_chars.pop_front() else { return };
|
||||
match c {
|
||||
' ' | '\n' => {
|
||||
if let Some(group) = self.char_render.finalise_letter() {
|
||||
self.letters.letters.push(group);
|
||||
self.letters.current_word_length += 1;
|
||||
self.letters.number_of_groups += 1;
|
||||
}
|
||||
if self.letters.current_word_length != 0 {
|
||||
self.letters.word_lengths.push(
|
||||
self.letters
|
||||
.current_word_length
|
||||
.try_into()
|
||||
.expect("word is too big"),
|
||||
);
|
||||
}
|
||||
self.letters.current_word_length = 0;
|
||||
self.letters.number_of_groups += 1;
|
||||
}
|
||||
letter => {
|
||||
if let Some(group) = self.char_render.render_char(self.font, letter) {
|
||||
self.letters.letters.push(group);
|
||||
self.letters.current_word_length += 1;
|
||||
self.letters.number_of_groups += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.working.x_offset += font_letter.advance_width as i32;
|
||||
#[must_use]
|
||||
pub fn layout(
|
||||
&mut self,
|
||||
area: Rect<i32>,
|
||||
alignment: TextAlignment,
|
||||
number_of_groups: usize,
|
||||
paragraph_spacing: i32,
|
||||
) -> Vec<ObjectUnmanaged> {
|
||||
let mut objects = Vec::new();
|
||||
|
||||
group
|
||||
while !self.buffered_chars.is_empty() && self.letters.number_of_groups <= number_of_groups {
|
||||
self.process();
|
||||
}
|
||||
|
||||
let minimum_space_width = self.font.letter(' ').advance_width as i32;
|
||||
|
||||
let lines = self.preprocessor.lines(area.size.x, minimum_space_width);
|
||||
let mut head_position = area.position;
|
||||
|
||||
let mut processed_depth = 0;
|
||||
let mut group_depth = 0;
|
||||
let mut word_depth = 0;
|
||||
let mut rendered_groups = 0;
|
||||
|
||||
'outer: for line in lines {
|
||||
let settings = alignment.settings(&line, minimum_space_width, area.size);
|
||||
head_position.x += settings.start_x;
|
||||
|
||||
for idx in 0..line.number_of_text_elements() {
|
||||
let element = self.preprocessor.get(processed_depth + idx);
|
||||
match element {
|
||||
PreprocessedElement::Word(_) => {
|
||||
for _ in 0..self
|
||||
.letters
|
||||
.word_lengths
|
||||
.get(word_depth)
|
||||
.copied()
|
||||
.unwrap_or(u8::MAX)
|
||||
{
|
||||
let letter_group = &self.letters.letters[group_depth];
|
||||
let mut object = ObjectUnmanaged::new(letter_group.sprite.clone());
|
||||
head_position.x += letter_group.left as i32;
|
||||
object.set_position(head_position);
|
||||
head_position.x += letter_group.width as i32;
|
||||
object.show();
|
||||
objects.push(object);
|
||||
group_depth += 1;
|
||||
rendered_groups += 1;
|
||||
if rendered_groups >= number_of_groups {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
word_depth += 1;
|
||||
}
|
||||
PreprocessedElement::WhiteSpace(space_type) => {
|
||||
if space_type == WhiteSpace::NewLine {
|
||||
head_position.y += paragraph_spacing;
|
||||
}
|
||||
head_position.x += settings.space_width;
|
||||
rendered_groups += 1;
|
||||
if rendered_groups >= number_of_groups {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processed_depth += line.number_of_text_elements();
|
||||
head_position.x = area.position.x;
|
||||
head_position.y += 9;
|
||||
}
|
||||
|
||||
objects
|
||||
}
|
||||
}
|
||||
|
|
206
agb/src/display/object/font/preprocess.rs
Normal file
206
agb/src/display/object/font/preprocess.rs
Normal file
|
@ -0,0 +1,206 @@
|
|||
use alloc::vec::Vec;
|
||||
|
||||
use crate::display::Font;
|
||||
|
||||
use super::WhiteSpace;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum PreprocessedElement {
|
||||
Word(u8),
|
||||
WhiteSpace(WhiteSpace),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
struct PreprocessedElementStored(u8);
|
||||
|
||||
impl core::fmt::Debug for PreprocessedElementStored {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_tuple("PreprocessedElementStored")
|
||||
.field(&self.parse())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PreprocessedElementStored {
|
||||
fn parse(self) -> PreprocessedElement {
|
||||
match self.0 {
|
||||
255 => PreprocessedElement::WhiteSpace(WhiteSpace::NewLine),
|
||||
254 => PreprocessedElement::WhiteSpace(WhiteSpace::Space),
|
||||
length => PreprocessedElement::Word(length),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_element(x: PreprocessedElement) -> Self {
|
||||
match x {
|
||||
PreprocessedElement::Word(length) => PreprocessedElementStored(length),
|
||||
PreprocessedElement::WhiteSpace(space) => PreprocessedElementStored(match space {
|
||||
WhiteSpace::NewLine => 255,
|
||||
WhiteSpace::Space => 254,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Preprocessed {
|
||||
widths: Vec<PreprocessedElementStored>,
|
||||
preprocessor: Preprocessor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Preprocessor {
|
||||
current_word_width: i32,
|
||||
}
|
||||
|
||||
impl Preprocessor {
|
||||
fn add_character(
|
||||
&mut self,
|
||||
font: &Font,
|
||||
character: char,
|
||||
widths: &mut Vec<PreprocessedElementStored>,
|
||||
) {
|
||||
match character {
|
||||
space @ (' ' | '\n') => {
|
||||
if self.current_word_width != 0 {
|
||||
widths.push(PreprocessedElementStored::from_element(
|
||||
PreprocessedElement::Word(
|
||||
self.current_word_width
|
||||
.try_into()
|
||||
.expect("word should be small and positive"),
|
||||
),
|
||||
));
|
||||
self.current_word_width = 0;
|
||||
}
|
||||
widths.push(PreprocessedElementStored::from_element(
|
||||
PreprocessedElement::WhiteSpace(WhiteSpace::from_char(space)),
|
||||
));
|
||||
}
|
||||
letter => {
|
||||
let letter = font.letter(letter);
|
||||
self.current_word_width += letter.advance_width as i32 + letter.xmin as i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Lines<'preprocess> {
|
||||
minimum_space_width: i32,
|
||||
layout_width: i32,
|
||||
data: &'preprocess [PreprocessedElementStored],
|
||||
current_start_idx: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct Line {
|
||||
width: i32,
|
||||
number_of_text_elements: usize,
|
||||
number_of_spaces: usize,
|
||||
number_of_words: usize,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
#[inline(always)]
|
||||
pub(crate) fn width(&self) -> i32 {
|
||||
self.width
|
||||
}
|
||||
#[inline(always)]
|
||||
pub(crate) fn number_of_text_elements(&self) -> usize {
|
||||
self.number_of_text_elements
|
||||
}
|
||||
#[inline(always)]
|
||||
pub(crate) fn number_of_spaces(&self) -> usize {
|
||||
self.number_of_spaces
|
||||
}
|
||||
#[inline(always)]
|
||||
pub(crate) fn number_of_words(&self) -> usize {
|
||||
self.number_of_words
|
||||
}
|
||||
}
|
||||
|
||||
impl<'pre> Iterator for Lines<'pre> {
|
||||
type Item = Line;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current_start_idx >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut line_idx_length = 0;
|
||||
let mut current_line_width = 0;
|
||||
let mut additional_space_count = 0;
|
||||
let mut number_of_spaces = 0;
|
||||
let mut number_of_words = 0;
|
||||
|
||||
while let Some(next) = self.data.get(self.current_start_idx + line_idx_length) {
|
||||
match next.parse() {
|
||||
PreprocessedElement::Word(pixels) => {
|
||||
let additional_space_width =
|
||||
additional_space_count as i32 * self.minimum_space_width;
|
||||
let width = pixels as i32;
|
||||
if width + current_line_width + additional_space_width > self.layout_width {
|
||||
break;
|
||||
}
|
||||
number_of_words += 1;
|
||||
current_line_width += width + additional_space_width;
|
||||
number_of_spaces += additional_space_count;
|
||||
}
|
||||
PreprocessedElement::WhiteSpace(space) => match space {
|
||||
WhiteSpace::NewLine => {
|
||||
line_idx_length += 1;
|
||||
break;
|
||||
}
|
||||
WhiteSpace::Space => {
|
||||
additional_space_count += 1;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
line_idx_length += 1;
|
||||
}
|
||||
|
||||
self.current_start_idx += line_idx_length;
|
||||
|
||||
Some(Line {
|
||||
width: current_line_width,
|
||||
number_of_text_elements: line_idx_length,
|
||||
number_of_spaces,
|
||||
number_of_words,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Preprocessed {
|
||||
pub(crate) fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, idx: usize) -> PreprocessedElement {
|
||||
self.widths[idx].parse()
|
||||
}
|
||||
|
||||
pub(crate) fn add_character(&mut self, font: &Font, c: char) {
|
||||
self.preprocessor.add_character(font, c, &mut self.widths);
|
||||
}
|
||||
|
||||
pub(crate) fn lines(&self, layout_width: i32, minimum_space_width: i32) -> Lines<'_> {
|
||||
Lines {
|
||||
minimum_space_width,
|
||||
layout_width,
|
||||
data: &self.widths,
|
||||
current_start_idx: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn lines_element(
|
||||
&self,
|
||||
layout_width: i32,
|
||||
minimum_space_width: i32,
|
||||
) -> impl Iterator<Item = &[PreprocessedElementStored]> {
|
||||
let mut idx = 0;
|
||||
self.lines(layout_width, minimum_space_width).map(move |x| {
|
||||
let length = x.number_of_text_elements;
|
||||
let d = &self.widths[idx..(idx + length)];
|
||||
idx += length;
|
||||
d
|
||||
})
|
||||
}
|
||||
}
|
120
agb/src/display/object/font/renderer.rs
Normal file
120
agb/src/display/object/font/renderer.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use crate::display::{
|
||||
object::{DynamicSprite, PaletteVram, Size},
|
||||
Font,
|
||||
};
|
||||
|
||||
use super::LetterGroup;
|
||||
|
||||
struct WorkingLetter {
|
||||
dynamic: DynamicSprite,
|
||||
// the x offset of the current letter with respect to the start of the current letter group
|
||||
x_position: i32,
|
||||
// where to render the letter from x_min to x_max
|
||||
x_offset: i32,
|
||||
}
|
||||
|
||||
impl WorkingLetter {
|
||||
fn new(size: Size) -> Self {
|
||||
Self {
|
||||
dynamic: DynamicSprite::new(size),
|
||||
x_position: 0,
|
||||
x_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.x_position = 0;
|
||||
self.x_offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Configuration {
|
||||
sprite_size: Size,
|
||||
palette: PaletteVram,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
#[must_use]
|
||||
pub fn new(sprite_size: Size, palette: PaletteVram) -> Self {
|
||||
Self {
|
||||
sprite_size,
|
||||
palette,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WordRender {
|
||||
working: WorkingLetter,
|
||||
config: Configuration,
|
||||
colour: usize,
|
||||
}
|
||||
|
||||
impl WordRender {
|
||||
#[must_use]
|
||||
pub(crate) fn new(config: Configuration) -> Self {
|
||||
WordRender {
|
||||
working: WorkingLetter::new(config.sprite_size),
|
||||
config,
|
||||
colour: 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn finalise_letter(&mut self) -> Option<LetterGroup> {
|
||||
if self.working.x_offset == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut new_sprite = DynamicSprite::new(self.config.sprite_size);
|
||||
core::mem::swap(&mut self.working.dynamic, &mut new_sprite);
|
||||
let sprite = new_sprite.to_vram(self.config.palette.clone());
|
||||
|
||||
let group = LetterGroup {
|
||||
sprite,
|
||||
width: self.working.x_offset as u16,
|
||||
left: self.working.x_position as i16,
|
||||
};
|
||||
self.working.reset();
|
||||
|
||||
Some(group)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn render_char(&mut self, font: &Font, c: char) -> Option<LetterGroup> {
|
||||
let font_letter: &crate::display::FontLetter = font.letter(c);
|
||||
|
||||
// uses more than the sprite can hold
|
||||
let group = if self.working.x_offset + font_letter.width as i32
|
||||
> self.config.sprite_size.to_width_height().0 as i32
|
||||
{
|
||||
self.finalise_letter()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.working.x_offset == 0 {
|
||||
self.working.x_position = font_letter.xmin as i32;
|
||||
} else {
|
||||
self.working.x_offset += font_letter.xmin as i32;
|
||||
}
|
||||
|
||||
let y_position = font.ascent() - font_letter.height as i32 - font_letter.ymin as i32;
|
||||
|
||||
for y in 0..font_letter.height as usize {
|
||||
for x in 0..font_letter.width as usize {
|
||||
let rendered = font_letter.bit_absolute(x, y);
|
||||
if rendered {
|
||||
self.working.dynamic.set_pixel(
|
||||
x + self.working.x_offset as usize,
|
||||
(y_position + y as i32) as usize,
|
||||
self.colour,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.working.x_offset += font_letter.advance_width as i32;
|
||||
|
||||
group
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue