mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
the proper nice okay working text rendering
This commit is contained in:
parent
ec3003c81d
commit
5f12040752
|
@ -25,9 +25,9 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
||||||
let line_metrics = font.horizontal_line_metrics(pixels_per_em).unwrap();
|
let line_metrics = font.horizontal_line_metrics(pixels_per_em).unwrap();
|
||||||
|
|
||||||
let line_height = line_metrics.new_line_size as i32;
|
let line_height = line_metrics.new_line_size as i32;
|
||||||
let ascent = line_metrics.ascent as i32;
|
let mut ascent = line_metrics.ascent as i32;
|
||||||
|
|
||||||
let font = (0..128)
|
let letters: Vec<_> = (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;
|
||||||
|
@ -56,7 +56,19 @@ pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
|
||||||
advance_width: metrics.advance_width,
|
advance_width: metrics.advance_width,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|letter_data| {
|
.collect();
|
||||||
|
|
||||||
|
let maximum_above_line = letters
|
||||||
|
.iter()
|
||||||
|
.map(|x| (x.height as i32 + x.ymin))
|
||||||
|
.max()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if (ascent - maximum_above_line) < 0 {
|
||||||
|
ascent = maximum_above_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
let font = letters.iter().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 height = letter_data.height as u8;
|
||||||
let width = letter_data.width as u8;
|
let width = letter_data.width as u8;
|
||||||
|
|
|
@ -8,19 +8,17 @@ use agb::{
|
||||||
PaletteVram, Size,
|
PaletteVram, Size,
|
||||||
},
|
},
|
||||||
palette16::Palette16,
|
palette16::Palette16,
|
||||||
Font, WIDTH,
|
Font, HEIGHT, WIDTH,
|
||||||
},
|
},
|
||||||
include_font,
|
include_font,
|
||||||
input::Button,
|
input::Button,
|
||||||
};
|
};
|
||||||
use agb_fixnum::Rect;
|
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
use alloc::vec::Vec;
|
|
||||||
|
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
const FONT: Font = include_font!("examples/font/pixelated.ttf", 8);
|
const FONT: Font = include_font!("examples/font/yoster.ttf", 12);
|
||||||
#[agb::entry]
|
#[agb::entry]
|
||||||
fn entry(gba: agb::Gba) -> ! {
|
fn entry(gba: agb::Gba) -> ! {
|
||||||
main(gba);
|
main(gba);
|
||||||
|
@ -35,12 +33,11 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
let palette = Palette16::new(palette);
|
let palette = Palette16::new(palette);
|
||||||
let palette = PaletteVram::new(&palette).unwrap();
|
let palette = PaletteVram::new(&palette).unwrap();
|
||||||
|
|
||||||
let mut wr = ObjectTextRender::new(&FONT, Size::S16x8, palette);
|
let mut wr = ObjectTextRender::new(&FONT, Size::S16x16, palette);
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
wr,
|
wr,
|
||||||
"{}",
|
"Woah! Hey there! I have a bunch of text I want to show you. However, you will find that the amount of text I can display is limited. Who'd have thought! Good thing that my text system supports scrolling! It only took around 20 jank versions to get here!"
|
||||||
"counts for three shoot dice for damage calculation\nmalfunctions all dice after use"
|
|
||||||
.to_ascii_uppercase()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let vblank = agb::interrupt::VBlank::get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
|
@ -52,25 +49,32 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
timer.set_enabled(true);
|
timer.set_enabled(true);
|
||||||
timer.set_divider(agb::timer::Divider::Divider256);
|
timer.set_divider(agb::timer::Divider::Divider256);
|
||||||
|
|
||||||
wr.set_alignment(TextAlignment::Left);
|
wr.layout((WIDTH, 40).into(), TextAlignment::Left, 2);
|
||||||
wr.set_size((WIDTH / 3, 20).into());
|
|
||||||
wr.set_paragraph_spacing(2);
|
let mut line_done = false;
|
||||||
wr.layout();
|
let mut frame = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
input.update();
|
input.update();
|
||||||
let oam = &mut unmanaged.iter();
|
let oam = &mut unmanaged.iter();
|
||||||
wr.commit(oam, (WIDTH / 3, 0).into());
|
wr.commit(oam);
|
||||||
|
|
||||||
let start = timer.value();
|
let start = timer.value();
|
||||||
let line_done = !wr.next_letter_group();
|
if frame % 4 == 0 {
|
||||||
if line_done && input.is_just_pressed(Button::A) {
|
line_done = !wr.next_letter_group();
|
||||||
|
}
|
||||||
|
if line_done
|
||||||
|
&& input.is_just_pressed(Button::A)
|
||||||
|
{
|
||||||
|
line_done = false;
|
||||||
wr.pop_line();
|
wr.pop_line();
|
||||||
}
|
}
|
||||||
wr.layout();
|
wr.update((0, HEIGHT - 40).into());
|
||||||
let end = timer.value();
|
let end = timer.value();
|
||||||
|
|
||||||
|
frame += 1;
|
||||||
|
|
||||||
agb::println!(
|
agb::println!(
|
||||||
"Took {} cycles, line done {}",
|
"Took {} cycles, line done {}",
|
||||||
256 * (end.wrapping_sub(start) as u32),
|
256 * (end.wrapping_sub(start) as u32),
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
use agb_fixnum::{Rect, Vector2D};
|
use agb_fixnum::Vector2D;
|
||||||
use alloc::{collections::VecDeque, vec::Vec};
|
use alloc::{collections::VecDeque, vec::Vec};
|
||||||
|
|
||||||
use crate::display::{object::font::preprocess::Word, Font};
|
use crate::display::Font;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
preprocess::{Line, Preprocessed, PreprocessedElement},
|
preprocess::{Line, Preprocessed, PreprocessedElement},
|
||||||
|
@ -32,14 +32,6 @@ impl WhiteSpace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct LetterGroup {
|
|
||||||
sprite: SpriteVram,
|
|
||||||
// the width of the letter group
|
|
||||||
width: u16,
|
|
||||||
left: i16,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BufferedRender<'font> {
|
pub struct BufferedRender<'font> {
|
||||||
char_render: WordRender,
|
char_render: WordRender,
|
||||||
preprocessor: Preprocessed,
|
preprocessor: Preprocessed,
|
||||||
|
@ -50,7 +42,7 @@ pub struct BufferedRender<'font> {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Letters {
|
struct Letters {
|
||||||
letters: VecDeque<LetterGroup>,
|
letters: VecDeque<SpriteVram>,
|
||||||
number_of_groups: usize,
|
number_of_groups: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,12 +61,7 @@ struct TextAlignmentSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextAlignment {
|
impl TextAlignment {
|
||||||
fn settings(
|
fn settings(self, line: &Line, minimum_space_width: i32, width: i32) -> TextAlignmentSettings {
|
||||||
self,
|
|
||||||
line: &Line,
|
|
||||||
minimum_space_width: i32,
|
|
||||||
size: Vector2D<i32>,
|
|
||||||
) -> TextAlignmentSettings {
|
|
||||||
match self {
|
match self {
|
||||||
TextAlignment::Left => TextAlignmentSettings {
|
TextAlignment::Left => TextAlignmentSettings {
|
||||||
space_width: minimum_space_width,
|
space_width: minimum_space_width,
|
||||||
|
@ -82,11 +69,11 @@ impl TextAlignment {
|
||||||
},
|
},
|
||||||
TextAlignment::Right => TextAlignmentSettings {
|
TextAlignment::Right => TextAlignmentSettings {
|
||||||
space_width: minimum_space_width,
|
space_width: minimum_space_width,
|
||||||
start_x: size.x - line.width(),
|
start_x: width - line.width(),
|
||||||
},
|
},
|
||||||
TextAlignment::Center => TextAlignmentSettings {
|
TextAlignment::Center => TextAlignmentSettings {
|
||||||
space_width: minimum_space_width,
|
space_width: minimum_space_width,
|
||||||
start_x: (size.x - line.width()) / 2,
|
start_x: (width - line.width()) / 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +130,7 @@ impl BufferedRender<'_> {
|
||||||
pub struct ObjectTextRender<'font> {
|
pub struct ObjectTextRender<'font> {
|
||||||
buffer: BufferedRender<'font>,
|
buffer: BufferedRender<'font>,
|
||||||
layout: LayoutCache,
|
layout: LayoutCache,
|
||||||
settings: LayoutSettings,
|
number_of_objects: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'font> ObjectTextRender<'font> {
|
impl<'font> ObjectTextRender<'font> {
|
||||||
|
@ -151,8 +138,14 @@ impl<'font> ObjectTextRender<'font> {
|
||||||
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer: BufferedRender::new(font, sprite_size, palette),
|
buffer: BufferedRender::new(font, sprite_size, palette),
|
||||||
layout: LayoutCache::new(),
|
number_of_objects: 0,
|
||||||
settings: Default::default(),
|
layout: LayoutCache {
|
||||||
|
positions: VecDeque::new(),
|
||||||
|
line_capacity: VecDeque::new(),
|
||||||
|
objects: Vec::new(),
|
||||||
|
objects_are_at_origin: (0, 0).into(),
|
||||||
|
area: (0, 0).into(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,267 +161,222 @@ impl Write for ObjectTextRender<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectTextRender<'_> {
|
impl ObjectTextRender<'_> {
|
||||||
/// Remove a line from the render and shift everything up one line.
|
|
||||||
/// A full complete line must be rendered for this to do anything, incomplete lines won't be popped. Returns whether a line could be popped.
|
|
||||||
pub fn pop_line(&mut self) -> bool {
|
|
||||||
let width = self.layout.settings.area.x;
|
|
||||||
let space = self.buffer.font.letter(' ').advance_width as i32;
|
|
||||||
let Some(line) = self.buffer.preprocessor.lines(width, space).next() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let number_of_elements = line.number_of_letter_groups();
|
|
||||||
if self.layout.state.line_depth >= 1 && self.layout.objects.len() >= number_of_elements {
|
|
||||||
for _ in 0..number_of_elements {
|
|
||||||
// self.buffer.letters.letters.pop_front();
|
|
||||||
self.layout.objects.pop_front();
|
|
||||||
}
|
|
||||||
self.buffer.preprocessor.pop(&line);
|
|
||||||
|
|
||||||
self.layout.state.head_offset.y -= self.buffer.font.line_height();
|
|
||||||
for obj in self.layout.objects.iter_mut() {
|
|
||||||
obj.offset.y -= self.buffer.font.line_height() as i16;
|
|
||||||
let object_offset = obj.offset.change_base();
|
|
||||||
obj.object
|
|
||||||
.set_position(self.layout.position + object_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.layout.state.line_depth -= 1;
|
|
||||||
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// On next update, the next unit of letters will be rendered. Returns whether the next element could be added.
|
|
||||||
/// Can only be called once per layout.
|
|
||||||
pub fn next_letter_group(&mut self) -> bool {
|
|
||||||
self.layout.next_letter_group(&self.buffer)
|
|
||||||
}
|
|
||||||
/// Commits work already done to screen. You can commit to multiple places in the same frame.
|
/// Commits work already done to screen. You can commit to multiple places in the same frame.
|
||||||
pub fn commit(&mut self, oam: &mut OamIterator, position: Vector2D<i32>) {
|
pub fn commit(&mut self, oam: &mut OamIterator) {
|
||||||
self.layout.commit(oam, position);
|
for (object, slot) in self.layout.objects.iter().zip(oam) {
|
||||||
}
|
slot.set(object);
|
||||||
/// Updates the internal state based on the chosen render settings. Best
|
|
||||||
/// effort is made to reuse previous layouts, but a full rerender may be
|
|
||||||
/// required if certain settings are changed.
|
|
||||||
pub fn layout(&mut self) {
|
|
||||||
self.layout.update(
|
|
||||||
&mut self.buffer,
|
|
||||||
self.settings.area,
|
|
||||||
self.settings.alignment,
|
|
||||||
self.settings.paragraph_spacing,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/// Causes a change to the area that text is rendered. This will cause a relayout.
|
|
||||||
pub fn set_size(&mut self, size: Vector2D<i32>) {
|
|
||||||
self.settings.area = size;
|
|
||||||
}
|
|
||||||
/// Causes a change to the text alignment. This will cause a relayout.
|
|
||||||
pub fn set_alignment(&mut self, alignment: TextAlignment) {
|
|
||||||
self.settings.alignment = alignment;
|
|
||||||
}
|
|
||||||
/// Sets the paragraph spacing. This will cause a relayout.
|
|
||||||
pub fn set_paragraph_spacing(&mut self, paragraph_spacing: i32) {
|
|
||||||
self.settings.paragraph_spacing = paragraph_spacing;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LayoutObject {
|
/// Force a relayout, must be called after writing.
|
||||||
object: ObjectUnmanaged,
|
pub fn layout(
|
||||||
offset: Vector2D<i16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LayoutCache {
|
|
||||||
objects: VecDeque<LayoutObject>,
|
|
||||||
state: LayoutCacheState,
|
|
||||||
settings: LayoutSettings,
|
|
||||||
desired_number_of_groups: usize,
|
|
||||||
position: Vector2D<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutCache {
|
|
||||||
fn next_letter_group(&mut self, buffer: &BufferedRender) -> bool {
|
|
||||||
let width = self.settings.area.x;
|
|
||||||
let space = buffer.font.letter(' ').advance_width as i32;
|
|
||||||
let line_height = buffer.font.line_height();
|
|
||||||
|
|
||||||
if self.state.head_offset.y + line_height > self.settings.area.y {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((_line, mut line_elements)) = buffer
|
|
||||||
.preprocessor
|
|
||||||
.lines_element(width, space)
|
|
||||||
.nth(self.state.line_depth)
|
|
||||||
{
|
|
||||||
match line_elements.nth(self.state.line_element_depth) {
|
|
||||||
Some(PreprocessedElement::Word(_)) => {
|
|
||||||
self.desired_number_of_groups += 1;
|
|
||||||
}
|
|
||||||
Some(PreprocessedElement::WhiteSpace(WhiteSpace::Space)) => {
|
|
||||||
self.desired_number_of_groups += 1;
|
|
||||||
}
|
|
||||||
Some(PreprocessedElement::WhiteSpace(WhiteSpace::NewLine)) => {
|
|
||||||
self.desired_number_of_groups += 1;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if self.state.head_offset.y + line_height * 2 > self.settings.area.y {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.desired_number_of_groups += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_cache(&mut self, render: &BufferedRender) {
|
|
||||||
if self.state.rendered_groups >= self.desired_number_of_groups {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let minimum_space_width = render.font.letter(' ').advance_width as i32;
|
|
||||||
|
|
||||||
let lines = render
|
|
||||||
.preprocessor
|
|
||||||
.lines_element(self.settings.area.x, minimum_space_width);
|
|
||||||
|
|
||||||
'outer: for (line, line_elements) in lines.skip(self.state.line_depth) {
|
|
||||||
let settings =
|
|
||||||
self.settings
|
|
||||||
.alignment
|
|
||||||
.settings(&line, minimum_space_width, self.settings.area);
|
|
||||||
|
|
||||||
if self.state.line_element_depth == 0 {
|
|
||||||
self.state.head_offset.x += settings.start_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
for element in line_elements.skip(self.state.line_element_depth) {
|
|
||||||
match element {
|
|
||||||
PreprocessedElement::Word(word) => {
|
|
||||||
for letter in (word.sprite_index()
|
|
||||||
..(word.sprite_index() + word.number_of_sprites()))
|
|
||||||
.skip(self.state.word_depth)
|
|
||||||
.map(|x| &render.letters.letters[x])
|
|
||||||
{
|
|
||||||
let mut object = ObjectUnmanaged::new(letter.sprite.clone());
|
|
||||||
self.state.head_offset.x += letter.left as i32;
|
|
||||||
object
|
|
||||||
.set_position(self.state.head_offset + self.position)
|
|
||||||
.show();
|
|
||||||
|
|
||||||
let layout_object = LayoutObject {
|
|
||||||
object,
|
|
||||||
offset: (
|
|
||||||
self.state.head_offset.x as i16,
|
|
||||||
self.state.head_offset.y as i16,
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
};
|
|
||||||
self.state.head_offset.x += letter.width as i32;
|
|
||||||
self.objects.push_back(layout_object);
|
|
||||||
self.state.rendered_groups += 1;
|
|
||||||
self.state.word_depth += 1;
|
|
||||||
if self.state.rendered_groups >= self.desired_number_of_groups {
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state.word_depth = 0;
|
|
||||||
self.state.line_element_depth += 1;
|
|
||||||
}
|
|
||||||
PreprocessedElement::WhiteSpace(space_type) => {
|
|
||||||
if space_type == WhiteSpace::NewLine {
|
|
||||||
self.state.head_offset.y += self.settings.paragraph_spacing;
|
|
||||||
}
|
|
||||||
self.state.head_offset.x += settings.space_width;
|
|
||||||
self.state.rendered_groups += 1;
|
|
||||||
self.state.line_element_depth += 1;
|
|
||||||
if self.state.rendered_groups >= self.desired_number_of_groups {
|
|
||||||
break 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state.head_offset.y += render.font.line_height();
|
|
||||||
self.state.head_offset.x = 0;
|
|
||||||
|
|
||||||
self.state.line_element_depth = 0;
|
|
||||||
self.state.line_depth += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
r: &mut BufferedRender<'_>,
|
|
||||||
area: Vector2D<i32>,
|
area: Vector2D<i32>,
|
||||||
alignment: TextAlignment,
|
alignment: TextAlignment,
|
||||||
paragraph_spacing: i32,
|
paragraph_spacing: i32,
|
||||||
) {
|
) {
|
||||||
r.process();
|
self.layout.create_positions(
|
||||||
|
self.buffer.font,
|
||||||
while !r.buffered_chars.is_empty()
|
&self.buffer.preprocessor,
|
||||||
&& r.letters.number_of_groups <= self.desired_number_of_groups
|
&LayoutSettings {
|
||||||
{
|
|
||||||
r.process();
|
|
||||||
}
|
|
||||||
|
|
||||||
let settings = LayoutSettings {
|
|
||||||
area,
|
area,
|
||||||
alignment,
|
alignment,
|
||||||
paragraph_spacing,
|
paragraph_spacing,
|
||||||
};
|
|
||||||
if settings != self.settings {
|
|
||||||
self.reset(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.update_cache(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commit(&mut self, oam: &mut OamIterator, position: Vector2D<i32>) {
|
|
||||||
if self.position != position {
|
|
||||||
for (object, slot) in self.objects.iter_mut().zip(oam) {
|
|
||||||
let object_offset = object.offset.change_base();
|
|
||||||
object.object.set_position(position + object_offset);
|
|
||||||
slot.set(&object.object);
|
|
||||||
}
|
|
||||||
self.position = position;
|
|
||||||
} else {
|
|
||||||
for (object, slot) in self.objects.iter().zip(oam) {
|
|
||||||
slot.set(&object.object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
objects: VecDeque::new(),
|
|
||||||
state: Default::default(),
|
|
||||||
settings: LayoutSettings {
|
|
||||||
area: (0, 0).into(),
|
|
||||||
alignment: TextAlignment::Right,
|
|
||||||
paragraph_spacing: -100,
|
|
||||||
},
|
},
|
||||||
desired_number_of_groups: 0,
|
);
|
||||||
position: (0, 0).into(),
|
}
|
||||||
|
|
||||||
|
/// Removes one complete line.
|
||||||
|
pub fn pop_line(&mut self) -> bool {
|
||||||
|
let width = self.layout.area.x;
|
||||||
|
let space = self.buffer.font.letter(' ').advance_width as i32;
|
||||||
|
let line_height = self.buffer.font.line_height();
|
||||||
|
if let Some(line) = self.buffer.preprocessor.lines(width, space).next() {
|
||||||
|
// there is a line
|
||||||
|
if self.layout.objects.len() >= line.number_of_letter_groups() {
|
||||||
|
// we have enough rendered letter groups to count
|
||||||
|
self.number_of_objects -= line.number_of_letter_groups();
|
||||||
|
for _ in 0..line.number_of_letter_groups() {
|
||||||
|
self.buffer.letters.letters.pop_front();
|
||||||
|
self.layout.positions.pop_front();
|
||||||
|
}
|
||||||
|
self.layout.line_capacity.pop_front();
|
||||||
|
self.layout.objects.clear();
|
||||||
|
self.buffer.preprocessor.pop(&line);
|
||||||
|
for position in self.layout.positions.iter_mut() {
|
||||||
|
position.y -= line_height as i16;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, position: Vector2D<i32>) {
|
||||||
|
if !self.buffer.buffered_chars.is_empty()
|
||||||
|
&& self.buffer.letters.letters.len() <= self.number_of_objects + 5
|
||||||
|
{
|
||||||
|
self.buffer.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layout.update_objects_to_display_at_position(
|
||||||
|
position,
|
||||||
|
self.buffer.letters.letters.iter(),
|
||||||
|
self.number_of_objects,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_letter_group(&mut self) -> bool {
|
||||||
|
if !self.can_render_another_element() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.number_of_objects += 1;
|
||||||
|
self.at_least_n_letter_groups(self.number_of_objects);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_render_another_element(&self) -> bool {
|
||||||
|
let max_number_of_lines = (self.layout.area.y / self.buffer.font.line_height()) as usize;
|
||||||
|
|
||||||
|
let max_number_of_objects = self
|
||||||
|
.layout
|
||||||
|
.line_capacity
|
||||||
|
.iter()
|
||||||
|
.take(max_number_of_lines)
|
||||||
|
.sum::<usize>();
|
||||||
|
|
||||||
|
max_number_of_objects > self.number_of_objects
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_line(&mut self) -> bool {
|
||||||
|
let max_number_of_lines = (self.layout.area.y / self.buffer.font.line_height()) as usize;
|
||||||
|
|
||||||
|
// find current line
|
||||||
|
|
||||||
|
for (start, end) in self
|
||||||
|
.layout
|
||||||
|
.line_capacity
|
||||||
|
.iter()
|
||||||
|
.scan(0, |count, line_size| {
|
||||||
|
let start = *count;
|
||||||
|
*count += line_size;
|
||||||
|
Some((start, *count))
|
||||||
|
})
|
||||||
|
.take(max_number_of_lines)
|
||||||
|
{
|
||||||
|
if self.number_of_objects >= start && self.number_of_objects < end {
|
||||||
|
self.number_of_objects = end;
|
||||||
|
self.at_least_n_letter_groups(end);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self, settings: LayoutSettings) {
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn at_least_n_letter_groups(&mut self, n: usize) {
|
||||||
|
while !self.buffer.buffered_chars.is_empty() && self.buffer.letters.letters.len() <= n {
|
||||||
|
self.buffer.process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LayoutCache {
|
||||||
|
positions: VecDeque<Vector2D<i16>>,
|
||||||
|
line_capacity: VecDeque<usize>,
|
||||||
|
objects: Vec<ObjectUnmanaged>,
|
||||||
|
objects_are_at_origin: Vector2D<i32>,
|
||||||
|
area: Vector2D<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutCache {
|
||||||
|
fn update_objects_to_display_at_position<'a>(
|
||||||
|
&mut self,
|
||||||
|
position: Vector2D<i32>,
|
||||||
|
letters: impl Iterator<Item = &'a SpriteVram>,
|
||||||
|
number_of_objects: usize,
|
||||||
|
) {
|
||||||
|
let already_done = if position == self.objects_are_at_origin {
|
||||||
|
self.objects.len()
|
||||||
|
} else {
|
||||||
self.objects.clear();
|
self.objects.clear();
|
||||||
self.state = LayoutCacheState {
|
0
|
||||||
head_offset: (0, 0).into(),
|
|
||||||
word_depth: 0,
|
|
||||||
rendered_groups: 0,
|
|
||||||
line_depth: 0,
|
|
||||||
line_element_depth: 0,
|
|
||||||
};
|
};
|
||||||
self.settings = settings;
|
self.objects.extend(
|
||||||
|
self.positions
|
||||||
|
.iter()
|
||||||
|
.zip(letters)
|
||||||
|
.take(number_of_objects)
|
||||||
|
.skip(already_done)
|
||||||
|
.map(|(offset, letter)| {
|
||||||
|
let position = offset.change_base() + position;
|
||||||
|
let mut object = ObjectUnmanaged::new(letter.clone());
|
||||||
|
object.show().set_position(position);
|
||||||
|
object
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
self.objects.truncate(number_of_objects);
|
||||||
|
self.objects_are_at_origin = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_positions(
|
||||||
|
&mut self,
|
||||||
|
font: &Font,
|
||||||
|
preprocessed: &Preprocessed,
|
||||||
|
settings: &LayoutSettings,
|
||||||
|
) {
|
||||||
|
self.area = settings.area;
|
||||||
|
self.line_capacity.clear();
|
||||||
|
self.positions.clear();
|
||||||
|
for (line, line_positions) in Self::create_layout(font, preprocessed, settings) {
|
||||||
|
self.line_capacity.push_back(line.number_of_letter_groups());
|
||||||
|
self.positions
|
||||||
|
.extend(line_positions.map(|x| Vector2D::new(x.x as i16, x.y as i16)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_layout<'a>(
|
||||||
|
font: &Font,
|
||||||
|
preprocessed: &'a Preprocessed,
|
||||||
|
settings: &'a LayoutSettings,
|
||||||
|
) -> impl Iterator<Item = (Line, impl Iterator<Item = Vector2D<i32>> + 'a)> + 'a {
|
||||||
|
let minimum_space_width = font.letter(' ').advance_width as i32;
|
||||||
|
let width = settings.area.x;
|
||||||
|
let line_height = font.line_height();
|
||||||
|
|
||||||
|
let mut head_position: Vector2D<i32> = (0, -line_height).into();
|
||||||
|
|
||||||
|
preprocessed
|
||||||
|
.lines_element(width, minimum_space_width)
|
||||||
|
.map(move |(line, line_elements)| {
|
||||||
|
let line_settings = settings
|
||||||
|
.alignment
|
||||||
|
.settings(&line, minimum_space_width, width);
|
||||||
|
|
||||||
|
head_position.y += line_height;
|
||||||
|
head_position.x = line_settings.start_x;
|
||||||
|
|
||||||
|
(
|
||||||
|
line,
|
||||||
|
line_elements.filter_map(move |element| match element.decode() {
|
||||||
|
PreprocessedElement::LetterGroup { width } => {
|
||||||
|
let this_position = head_position;
|
||||||
|
head_position.x += width as i32;
|
||||||
|
Some(this_position)
|
||||||
|
}
|
||||||
|
PreprocessedElement::WhiteSpace(space) => {
|
||||||
|
match space {
|
||||||
|
WhiteSpace::NewLine => {
|
||||||
|
head_position.y += settings.paragraph_spacing;
|
||||||
|
}
|
||||||
|
WhiteSpace::Space => head_position.x += line_settings.space_width,
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,12 +386,3 @@ struct LayoutSettings {
|
||||||
alignment: TextAlignment,
|
alignment: TextAlignment,
|
||||||
paragraph_spacing: i32,
|
paragraph_spacing: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct LayoutCacheState {
|
|
||||||
head_offset: Vector2D<i32>,
|
|
||||||
word_depth: usize,
|
|
||||||
rendered_groups: usize,
|
|
||||||
line_depth: usize,
|
|
||||||
line_element_depth: usize,
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,52 +6,47 @@ use crate::display::Font;
|
||||||
|
|
||||||
use super::WhiteSpace;
|
use super::WhiteSpace;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) struct PreprocessedElementEncoded(u8);
|
||||||
|
|
||||||
|
impl PreprocessedElementEncoded {
|
||||||
|
pub(crate) fn decode(self) -> PreprocessedElement {
|
||||||
|
match self.0 {
|
||||||
|
255 => PreprocessedElement::WhiteSpace(WhiteSpace::NewLine),
|
||||||
|
254 => PreprocessedElement::WhiteSpace(WhiteSpace::Space),
|
||||||
|
width => PreprocessedElement::LetterGroup { width },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
|
||||||
pub(crate) enum PreprocessedElement {
|
pub(crate) enum PreprocessedElement {
|
||||||
Word(Word),
|
LetterGroup { width: u8 },
|
||||||
WhiteSpace(WhiteSpace),
|
WhiteSpace(WhiteSpace),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case]
|
impl PreprocessedElement {
|
||||||
fn check_size_of_preprocessed_element_is_correct(_: &mut crate::Gba) {
|
fn encode(self) -> PreprocessedElementEncoded {
|
||||||
assert_eq!(
|
PreprocessedElementEncoded(match self {
|
||||||
core::mem::size_of::<PreprocessedElement>(),
|
PreprocessedElement::LetterGroup { width } => width,
|
||||||
core::mem::size_of::<Word>()
|
PreprocessedElement::WhiteSpace(space) => match space {
|
||||||
);
|
WhiteSpace::NewLine => 255,
|
||||||
}
|
WhiteSpace::Space => 254,
|
||||||
|
},
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
})
|
||||||
#[repr(align(4))]
|
|
||||||
pub(crate) struct Word {
|
|
||||||
pixels: u8,
|
|
||||||
number_of_sprites: NonZeroU8,
|
|
||||||
index: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Word {
|
|
||||||
pub fn pixels(self) -> i32 {
|
|
||||||
self.pixels.into()
|
|
||||||
}
|
|
||||||
pub fn number_of_sprites(self) -> usize {
|
|
||||||
self.number_of_sprites.get().into()
|
|
||||||
}
|
|
||||||
pub fn sprite_index(self) -> usize {
|
|
||||||
self.index.into()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub(crate) struct Preprocessed {
|
pub(crate) struct Preprocessed {
|
||||||
widths: VecDeque<PreprocessedElement>,
|
widths: VecDeque<PreprocessedElementEncoded>,
|
||||||
preprocessor: Preprocessor,
|
preprocessor: Preprocessor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Preprocessor {
|
struct Preprocessor {
|
||||||
current_word_width: i32,
|
|
||||||
number_of_sprites: usize,
|
|
||||||
width_in_sprite: i32,
|
width_in_sprite: i32,
|
||||||
total_number_of_sprites: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Preprocessor {
|
impl Preprocessor {
|
||||||
|
@ -60,37 +55,32 @@ impl Preprocessor {
|
||||||
font: &Font,
|
font: &Font,
|
||||||
character: char,
|
character: char,
|
||||||
sprite_width: i32,
|
sprite_width: i32,
|
||||||
widths: &mut VecDeque<PreprocessedElement>,
|
widths: &mut VecDeque<PreprocessedElementEncoded>,
|
||||||
) {
|
) {
|
||||||
match character {
|
match character {
|
||||||
space @ (' ' | '\n') => {
|
space @ (' ' | '\n') => {
|
||||||
if self.current_word_width != 0 {
|
if self.width_in_sprite != 0 {
|
||||||
self.number_of_sprites += 1;
|
widths.push_back(
|
||||||
self.total_number_of_sprites += 1;
|
PreprocessedElement::LetterGroup {
|
||||||
widths.push_back(PreprocessedElement::Word(Word {
|
width: self.width_in_sprite as u8,
|
||||||
pixels: self.current_word_width.try_into().expect("word too wide"),
|
}
|
||||||
number_of_sprites: NonZeroU8::new(
|
.encode(),
|
||||||
self.number_of_sprites.try_into().expect("word too wide"),
|
);
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
index: (self.total_number_of_sprites - self.number_of_sprites)
|
|
||||||
.try_into()
|
|
||||||
.expect("out of range"),
|
|
||||||
}));
|
|
||||||
self.current_word_width = 0;
|
|
||||||
self.number_of_sprites = 0;
|
|
||||||
self.width_in_sprite = 0;
|
self.width_in_sprite = 0;
|
||||||
}
|
}
|
||||||
widths.push_back(PreprocessedElement::WhiteSpace(WhiteSpace::from_char(
|
widths.push_back(
|
||||||
space,
|
PreprocessedElement::WhiteSpace(WhiteSpace::from_char(space)).encode(),
|
||||||
)));
|
);
|
||||||
}
|
}
|
||||||
letter => {
|
letter => {
|
||||||
let letter = font.letter(letter);
|
let letter = font.letter(letter);
|
||||||
self.current_word_width += letter.advance_width as i32 + letter.xmin as i32;
|
|
||||||
if self.width_in_sprite + letter.width as i32 > sprite_width {
|
if self.width_in_sprite + letter.width as i32 > sprite_width {
|
||||||
self.number_of_sprites += 1;
|
widths.push_back(
|
||||||
self.total_number_of_sprites += 1;
|
PreprocessedElement::LetterGroup {
|
||||||
|
width: self.width_in_sprite as u8,
|
||||||
|
}
|
||||||
|
.encode(),
|
||||||
|
);
|
||||||
self.width_in_sprite = 0;
|
self.width_in_sprite = 0;
|
||||||
}
|
}
|
||||||
if self.width_in_sprite != 0 {
|
if self.width_in_sprite != 0 {
|
||||||
|
@ -105,7 +95,7 @@ impl Preprocessor {
|
||||||
pub(crate) struct Lines<'preprocess> {
|
pub(crate) struct Lines<'preprocess> {
|
||||||
minimum_space_width: i32,
|
minimum_space_width: i32,
|
||||||
layout_width: i32,
|
layout_width: i32,
|
||||||
data: &'preprocess VecDeque<PreprocessedElement>,
|
data: &'preprocess VecDeque<PreprocessedElementEncoded>,
|
||||||
current_start_idx: usize,
|
current_start_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,35 +140,58 @@ impl<'pre> Iterator for Lines<'pre> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut line_idx_length = 0;
|
let mut line_idx_length = 0;
|
||||||
let mut current_line_width = 0;
|
let mut current_line_width_pixels = 0;
|
||||||
let mut additional_space_count = 0;
|
let mut spaces_after_last_word_count = 0usize;
|
||||||
|
let mut start_of_current_word = usize::MAX;
|
||||||
|
let mut length_of_current_word_pixels = 0;
|
||||||
|
let mut length_of_current_word = 0;
|
||||||
let mut number_of_spaces = 0;
|
let mut number_of_spaces = 0;
|
||||||
let mut number_of_words = 0;
|
let mut number_of_words = 0;
|
||||||
let mut number_of_letter_groups = 0;
|
let mut number_of_letter_groups = 0;
|
||||||
|
|
||||||
while let Some(next) = self.data.get(self.current_start_idx + line_idx_length) {
|
while let Some(next) = self.data.get(self.current_start_idx + line_idx_length) {
|
||||||
match next {
|
match next.decode() {
|
||||||
PreprocessedElement::Word(word) => {
|
PreprocessedElement::LetterGroup { width } => {
|
||||||
let additional_space_width =
|
if start_of_current_word == usize::MAX {
|
||||||
additional_space_count as i32 * self.minimum_space_width;
|
start_of_current_word = line_idx_length;
|
||||||
let width = word.pixels();
|
}
|
||||||
if width + current_line_width + additional_space_width > self.layout_width {
|
length_of_current_word_pixels += width as i32;
|
||||||
|
length_of_current_word += 1;
|
||||||
|
if current_line_width_pixels
|
||||||
|
+ length_of_current_word_pixels
|
||||||
|
+ spaces_after_last_word_count as i32 * self.minimum_space_width
|
||||||
|
>= self.layout_width
|
||||||
|
{
|
||||||
|
line_idx_length = start_of_current_word;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
number_of_letter_groups += word.number_of_sprites.get() as usize;
|
|
||||||
number_of_words += 1;
|
|
||||||
current_line_width += width + additional_space_width;
|
|
||||||
number_of_spaces += additional_space_count;
|
|
||||||
}
|
}
|
||||||
PreprocessedElement::WhiteSpace(space) => match space {
|
PreprocessedElement::WhiteSpace(space) => {
|
||||||
|
if start_of_current_word != usize::MAX {
|
||||||
|
// flush word
|
||||||
|
current_line_width_pixels += length_of_current_word_pixels
|
||||||
|
+ spaces_after_last_word_count as i32 * self.minimum_space_width;
|
||||||
|
number_of_spaces += spaces_after_last_word_count;
|
||||||
|
number_of_words += 1;
|
||||||
|
number_of_letter_groups += length_of_current_word;
|
||||||
|
|
||||||
|
// reset parser
|
||||||
|
length_of_current_word_pixels = 0;
|
||||||
|
length_of_current_word = 0;
|
||||||
|
start_of_current_word = usize::MAX;
|
||||||
|
spaces_after_last_word_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
match space {
|
||||||
WhiteSpace::NewLine => {
|
WhiteSpace::NewLine => {
|
||||||
line_idx_length += 1;
|
line_idx_length += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
WhiteSpace::Space => {
|
WhiteSpace::Space => {
|
||||||
additional_space_count += 1;
|
spaces_after_last_word_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
line_idx_length += 1;
|
line_idx_length += 1;
|
||||||
|
@ -187,7 +200,7 @@ impl<'pre> Iterator for Lines<'pre> {
|
||||||
self.current_start_idx += line_idx_length;
|
self.current_start_idx += line_idx_length;
|
||||||
|
|
||||||
Some(Line {
|
Some(Line {
|
||||||
width: current_line_width,
|
width: current_line_width_pixels,
|
||||||
number_of_text_elements: line_idx_length,
|
number_of_text_elements: line_idx_length,
|
||||||
number_of_spaces,
|
number_of_spaces,
|
||||||
number_of_words,
|
number_of_words,
|
||||||
|
@ -226,7 +239,7 @@ impl Preprocessed {
|
||||||
&self,
|
&self,
|
||||||
layout_width: i32,
|
layout_width: i32,
|
||||||
minimum_space_width: i32,
|
minimum_space_width: i32,
|
||||||
) -> impl Iterator<Item = (Line, impl Iterator<Item = PreprocessedElement> + '_)> {
|
) -> impl Iterator<Item = (Line, impl Iterator<Item = PreprocessedElementEncoded> + '_)> {
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
self.lines(layout_width, minimum_space_width).map(move |x| {
|
self.lines(layout_width, minimum_space_width).map(move |x| {
|
||||||
let length = x.number_of_text_elements;
|
let length = x.number_of_text_elements;
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
use crate::display::{
|
use crate::display::{
|
||||||
object::{DynamicSprite, PaletteVram, Size},
|
object::{DynamicSprite, PaletteVram, Size, SpriteVram},
|
||||||
Font,
|
Font,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::LetterGroup;
|
|
||||||
|
|
||||||
struct WorkingLetter {
|
struct WorkingLetter {
|
||||||
dynamic: DynamicSprite,
|
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
|
// where to render the letter from x_min to x_max
|
||||||
x_offset: i32,
|
x_offset: i32,
|
||||||
}
|
}
|
||||||
|
@ -17,13 +13,11 @@ impl WorkingLetter {
|
||||||
fn new(size: Size) -> Self {
|
fn new(size: Size) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dynamic: DynamicSprite::new(size),
|
dynamic: DynamicSprite::new(size),
|
||||||
x_position: 0,
|
|
||||||
x_offset: 0,
|
x_offset: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.x_position = 0;
|
|
||||||
self.x_offset = 0;
|
self.x_offset = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +58,7 @@ impl WordRender {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn finalise_letter(&mut self) -> Option<LetterGroup> {
|
pub(crate) fn finalise_letter(&mut self) -> Option<SpriteVram> {
|
||||||
if self.working.x_offset == 0 {
|
if self.working.x_offset == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -72,19 +66,13 @@ impl WordRender {
|
||||||
let mut new_sprite = DynamicSprite::new(self.config.sprite_size);
|
let mut new_sprite = DynamicSprite::new(self.config.sprite_size);
|
||||||
core::mem::swap(&mut self.working.dynamic, &mut new_sprite);
|
core::mem::swap(&mut self.working.dynamic, &mut new_sprite);
|
||||||
let sprite = new_sprite.to_vram(self.config.palette.clone());
|
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();
|
self.working.reset();
|
||||||
|
|
||||||
Some(group)
|
Some(sprite)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn render_char(&mut self, font: &Font, c: char) -> Option<LetterGroup> {
|
pub(crate) fn render_char(&mut self, font: &Font, c: char) -> Option<SpriteVram> {
|
||||||
let font_letter: &crate::display::FontLetter = font.letter(c);
|
let font_letter: &crate::display::FontLetter = font.letter(c);
|
||||||
|
|
||||||
// uses more than the sprite can hold
|
// uses more than the sprite can hold
|
||||||
|
@ -96,9 +84,7 @@ impl WordRender {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.working.x_offset == 0 {
|
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;
|
self.working.x_offset += font_letter.xmin as i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue