mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
garbage renderer
This commit is contained in:
parent
93024f6bab
commit
ec3003c81d
|
@ -4,7 +4,7 @@
|
|||
use agb::{
|
||||
display::{
|
||||
object::{
|
||||
font::{BufferedRender, LayoutCache, TextAlignment},
|
||||
font::{ObjectTextRender, TextAlignment},
|
||||
PaletteVram, Size,
|
||||
},
|
||||
palette16::Palette16,
|
||||
|
@ -35,7 +35,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
let palette = Palette16::new(palette);
|
||||
let palette = PaletteVram::new(&palette).unwrap();
|
||||
|
||||
let mut wr = BufferedRender::new(&FONT, Size::S16x8, palette);
|
||||
let mut wr = ObjectTextRender::new(&FONT, Size::S16x8, palette);
|
||||
let _ = writeln!(
|
||||
wr,
|
||||
"{}",
|
||||
|
@ -52,55 +52,30 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
timer.set_enabled(true);
|
||||
timer.set_divider(agb::timer::Divider::Divider256);
|
||||
|
||||
let mut num_letters = 0;
|
||||
|
||||
let mut alignment = TextAlignment::Left;
|
||||
|
||||
let mut cache = LayoutCache::new();
|
||||
wr.set_alignment(TextAlignment::Left);
|
||||
wr.set_size((WIDTH / 3, 20).into());
|
||||
wr.set_paragraph_spacing(2);
|
||||
wr.layout();
|
||||
|
||||
loop {
|
||||
vblank.wait_for_vblank();
|
||||
input.update();
|
||||
let oam = &mut unmanaged.iter();
|
||||
cache.commit(oam);
|
||||
wr.commit(oam, (WIDTH / 3, 0).into());
|
||||
|
||||
let start = timer.value();
|
||||
wr.process();
|
||||
cache.update(
|
||||
&mut wr,
|
||||
Rect::new((WIDTH / 3, 0).into(), (WIDTH / 3, 100).into()),
|
||||
alignment,
|
||||
2,
|
||||
num_letters,
|
||||
);
|
||||
let line_done = !wr.next_letter_group();
|
||||
if line_done && input.is_just_pressed(Button::A) {
|
||||
wr.pop_line();
|
||||
}
|
||||
wr.layout();
|
||||
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;
|
||||
}
|
||||
|
||||
num_letters += 1;
|
||||
|
||||
if input.is_just_pressed(Button::A) {
|
||||
break;
|
||||
}
|
||||
agb::println!(
|
||||
"Took {} cycles, line done {}",
|
||||
256 * (end.wrapping_sub(start) as u32),
|
||||
line_done
|
||||
);
|
||||
}
|
||||
let start = timer.value();
|
||||
drop(wr);
|
||||
let oam = unmanaged.iter();
|
||||
drop(oam);
|
||||
let end = timer.value();
|
||||
agb::println!(
|
||||
"Drop took {} cycles",
|
||||
256 * (end.wrapping_sub(start) as u32)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use core::fmt::Write;
|
|||
use agb_fixnum::{Rect, Vector2D};
|
||||
use alloc::{collections::VecDeque, vec::Vec};
|
||||
|
||||
use crate::display::Font;
|
||||
use crate::display::{object::font::preprocess::Word, Font};
|
||||
|
||||
use self::{
|
||||
preprocess::{Line, Preprocessed, PreprocessedElement},
|
||||
|
@ -15,7 +15,7 @@ use super::{OamIterator, ObjectUnmanaged, PaletteVram, Size, SpriteVram};
|
|||
mod preprocess;
|
||||
mod renderer;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
pub(crate) enum WhiteSpace {
|
||||
NewLine,
|
||||
|
@ -50,23 +50,14 @@ pub struct BufferedRender<'font> {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
struct Letters {
|
||||
letters: Vec<LetterGroup>,
|
||||
letters: VecDeque<LetterGroup>,
|
||||
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)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum TextAlignment {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
|
@ -103,7 +94,7 @@ impl TextAlignment {
|
|||
|
||||
impl<'font> BufferedRender<'font> {
|
||||
#[must_use]
|
||||
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
||||
fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
||||
let config = Configuration::new(sprite_size, palette);
|
||||
BufferedRender {
|
||||
char_render: WordRender::new(config),
|
||||
|
@ -115,19 +106,25 @@ impl<'font> BufferedRender<'font> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_private_use(c: char) -> bool {
|
||||
('\u{E000}'..'\u{F8FF}').contains(&c)
|
||||
}
|
||||
|
||||
impl BufferedRender<'_> {
|
||||
fn input_character(&mut self, character: char) {
|
||||
self.preprocessor
|
||||
.add_character(self.font, character, self.char_render.sprite_width());
|
||||
if !is_private_use(character) {
|
||||
self.preprocessor
|
||||
.add_character(self.font, character, self.char_render.sprite_width());
|
||||
}
|
||||
self.buffered_chars.push_back(character);
|
||||
}
|
||||
|
||||
pub fn process(&mut self) {
|
||||
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.letters.push_back(group);
|
||||
self.letters.number_of_groups += 1;
|
||||
}
|
||||
|
||||
|
@ -135,7 +132,7 @@ impl BufferedRender<'_> {
|
|||
}
|
||||
letter => {
|
||||
if let Some(group) = self.char_render.render_char(self.font, letter) {
|
||||
self.letters.letters.push(group);
|
||||
self.letters.letters.push_back(group);
|
||||
self.letters.number_of_groups += 1;
|
||||
}
|
||||
}
|
||||
|
@ -143,32 +140,173 @@ impl BufferedRender<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct LayoutCache {
|
||||
objects: Vec<ObjectUnmanaged>,
|
||||
state: LayoutCacheState,
|
||||
pub struct ObjectTextRender<'font> {
|
||||
buffer: BufferedRender<'font>,
|
||||
layout: LayoutCache,
|
||||
settings: LayoutSettings,
|
||||
}
|
||||
|
||||
impl<'font> ObjectTextRender<'font> {
|
||||
#[must_use]
|
||||
pub fn new(font: &'font Font, sprite_size: Size, palette: PaletteVram) -> Self {
|
||||
Self {
|
||||
buffer: BufferedRender::new(font, sprite_size, palette),
|
||||
layout: LayoutCache::new(),
|
||||
settings: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for ObjectTextRender<'_> {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
for c in s.chars() {
|
||||
self.buffer.input_character(c);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn commit(&mut self, oam: &mut OamIterator, position: Vector2D<i32>) {
|
||||
self.layout.commit(oam, position);
|
||||
}
|
||||
/// 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 {
|
||||
object: ObjectUnmanaged,
|
||||
offset: Vector2D<i16>,
|
||||
}
|
||||
|
||||
struct LayoutCache {
|
||||
objects: VecDeque<LayoutObject>,
|
||||
state: LayoutCacheState,
|
||||
settings: LayoutSettings,
|
||||
desired_number_of_groups: usize,
|
||||
position: Vector2D<i32>,
|
||||
}
|
||||
|
||||
impl LayoutCache {
|
||||
fn update_cache(&mut self, number_of_groups: usize, render: &BufferedRender) {
|
||||
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.size.x, minimum_space_width);
|
||||
.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.size,
|
||||
);
|
||||
let settings =
|
||||
self.settings
|
||||
.alignment
|
||||
.settings(&line, minimum_space_width, self.settings.area);
|
||||
|
||||
if self.state.line_element_depth == 0 {
|
||||
self.state.head_position.x += settings.start_x;
|
||||
self.state.head_offset.x += settings.start_x;
|
||||
}
|
||||
|
||||
for element in line_elements.iter().skip(self.state.line_element_depth) {
|
||||
for element in line_elements.skip(self.state.line_element_depth) {
|
||||
match element {
|
||||
PreprocessedElement::Word(word) => {
|
||||
for letter in (word.sprite_index()
|
||||
|
@ -177,14 +315,24 @@ impl LayoutCache {
|
|||
.map(|x| &render.letters.letters[x])
|
||||
{
|
||||
let mut object = ObjectUnmanaged::new(letter.sprite.clone());
|
||||
self.state.head_position.x += letter.left as i32;
|
||||
object.set_position(self.state.head_position);
|
||||
self.state.head_position.x += letter.width as i32;
|
||||
object.show();
|
||||
self.objects.push(object);
|
||||
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 >= number_of_groups {
|
||||
if self.state.rendered_groups >= self.desired_number_of_groups {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
|
@ -193,36 +341,39 @@ impl LayoutCache {
|
|||
self.state.line_element_depth += 1;
|
||||
}
|
||||
PreprocessedElement::WhiteSpace(space_type) => {
|
||||
if *space_type == WhiteSpace::NewLine {
|
||||
self.state.head_position.y += self.settings.paragraph_spacing;
|
||||
if space_type == WhiteSpace::NewLine {
|
||||
self.state.head_offset.y += self.settings.paragraph_spacing;
|
||||
}
|
||||
self.state.head_position.x += settings.space_width;
|
||||
self.state.head_offset.x += settings.space_width;
|
||||
self.state.rendered_groups += 1;
|
||||
self.state.line_element_depth += 1;
|
||||
if self.state.rendered_groups >= number_of_groups {
|
||||
if self.state.rendered_groups >= self.desired_number_of_groups {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.state.head_position.y += render.font.line_height();
|
||||
self.state.head_position.x = self.settings.area.position.x;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
fn update(
|
||||
&mut self,
|
||||
r: &mut BufferedRender<'_>,
|
||||
area: Rect<i32>,
|
||||
area: Vector2D<i32>,
|
||||
alignment: TextAlignment,
|
||||
paragraph_spacing: i32,
|
||||
number_of_groups: usize,
|
||||
) {
|
||||
while !r.buffered_chars.is_empty() && r.letters.number_of_groups <= number_of_groups {
|
||||
r.process();
|
||||
|
||||
while !r.buffered_chars.is_empty()
|
||||
&& r.letters.number_of_groups <= self.desired_number_of_groups
|
||||
{
|
||||
r.process();
|
||||
}
|
||||
|
||||
|
@ -235,32 +386,43 @@ impl LayoutCache {
|
|||
self.reset(settings);
|
||||
}
|
||||
|
||||
self.update_cache(number_of_groups, r);
|
||||
self.update_cache(r);
|
||||
}
|
||||
|
||||
pub fn commit(&self, oam: &mut OamIterator) {
|
||||
for (object, slot) in self.objects.iter().zip(oam) {
|
||||
slot.set(object);
|
||||
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]
|
||||
pub fn new() -> Self {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
objects: Vec::new(),
|
||||
objects: VecDeque::new(),
|
||||
state: Default::default(),
|
||||
settings: LayoutSettings {
|
||||
area: Rect::new((0, 0).into(), (0, 0).into()),
|
||||
area: (0, 0).into(),
|
||||
alignment: TextAlignment::Right,
|
||||
paragraph_spacing: -100,
|
||||
},
|
||||
desired_number_of_groups: 0,
|
||||
position: (0, 0).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self, settings: LayoutSettings) {
|
||||
self.objects.clear();
|
||||
self.state = LayoutCacheState {
|
||||
head_position: settings.area.position,
|
||||
head_offset: (0, 0).into(),
|
||||
word_depth: 0,
|
||||
rendered_groups: 0,
|
||||
line_depth: 0,
|
||||
|
@ -270,16 +432,16 @@ impl LayoutCache {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Default)]
|
||||
struct LayoutSettings {
|
||||
area: Rect<i32>,
|
||||
area: Vector2D<i32>,
|
||||
alignment: TextAlignment,
|
||||
paragraph_spacing: i32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct LayoutCacheState {
|
||||
head_position: Vector2D<i32>,
|
||||
head_offset: Vector2D<i32>,
|
||||
word_depth: usize,
|
||||
rendered_groups: usize,
|
||||
line_depth: usize,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use core::num::NonZeroU8;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{collections::VecDeque, vec::Vec};
|
||||
|
||||
use crate::display::Font;
|
||||
|
||||
use super::WhiteSpace;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub(crate) enum PreprocessedElement {
|
||||
Word(Word),
|
||||
WhiteSpace(WhiteSpace),
|
||||
|
@ -40,12 +40,9 @@ impl Word {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct PreprocessedElementStored(u8);
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Preprocessed {
|
||||
widths: Vec<PreprocessedElement>,
|
||||
widths: VecDeque<PreprocessedElement>,
|
||||
preprocessor: Preprocessor,
|
||||
}
|
||||
|
||||
|
@ -63,14 +60,14 @@ impl Preprocessor {
|
|||
font: &Font,
|
||||
character: char,
|
||||
sprite_width: i32,
|
||||
widths: &mut Vec<PreprocessedElement>,
|
||||
widths: &mut VecDeque<PreprocessedElement>,
|
||||
) {
|
||||
match character {
|
||||
space @ (' ' | '\n') => {
|
||||
if self.current_word_width != 0 {
|
||||
self.number_of_sprites += 1;
|
||||
self.total_number_of_sprites += 1;
|
||||
widths.push(PreprocessedElement::Word(Word {
|
||||
widths.push_back(PreprocessedElement::Word(Word {
|
||||
pixels: self.current_word_width.try_into().expect("word too wide"),
|
||||
number_of_sprites: NonZeroU8::new(
|
||||
self.number_of_sprites.try_into().expect("word too wide"),
|
||||
|
@ -84,7 +81,7 @@ impl Preprocessor {
|
|||
self.number_of_sprites = 0;
|
||||
self.width_in_sprite = 0;
|
||||
}
|
||||
widths.push(PreprocessedElement::WhiteSpace(WhiteSpace::from_char(
|
||||
widths.push_back(PreprocessedElement::WhiteSpace(WhiteSpace::from_char(
|
||||
space,
|
||||
)));
|
||||
}
|
||||
|
@ -108,7 +105,7 @@ impl Preprocessor {
|
|||
pub(crate) struct Lines<'preprocess> {
|
||||
minimum_space_width: i32,
|
||||
layout_width: i32,
|
||||
data: &'preprocess [PreprocessedElement],
|
||||
data: &'preprocess VecDeque<PreprocessedElement>,
|
||||
current_start_idx: usize,
|
||||
}
|
||||
|
||||
|
@ -117,6 +114,7 @@ pub(crate) struct Line {
|
|||
number_of_text_elements: usize,
|
||||
number_of_spaces: usize,
|
||||
number_of_words: usize,
|
||||
number_of_letter_groups: usize,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
|
@ -136,6 +134,11 @@ impl Line {
|
|||
pub(crate) fn number_of_words(&self) -> usize {
|
||||
self.number_of_words
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn number_of_letter_groups(&self) -> usize {
|
||||
self.number_of_letter_groups
|
||||
}
|
||||
}
|
||||
|
||||
impl<'pre> Iterator for Lines<'pre> {
|
||||
|
@ -151,6 +154,7 @@ impl<'pre> Iterator for Lines<'pre> {
|
|||
let mut additional_space_count = 0;
|
||||
let mut number_of_spaces = 0;
|
||||
let mut number_of_words = 0;
|
||||
let mut number_of_letter_groups = 0;
|
||||
|
||||
while let Some(next) = self.data.get(self.current_start_idx + line_idx_length) {
|
||||
match next {
|
||||
|
@ -161,6 +165,7 @@ impl<'pre> Iterator for Lines<'pre> {
|
|||
if width + current_line_width + additional_space_width > self.layout_width {
|
||||
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;
|
||||
|
@ -186,6 +191,7 @@ impl<'pre> Iterator for Lines<'pre> {
|
|||
number_of_text_elements: line_idx_length,
|
||||
number_of_spaces,
|
||||
number_of_words,
|
||||
number_of_letter_groups,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -200,6 +206,13 @@ impl Preprocessed {
|
|||
.add_character(font, c, sprite_width, &mut self.widths);
|
||||
}
|
||||
|
||||
pub(crate) fn pop(&mut self, line: &Line) {
|
||||
let elements = line.number_of_text_elements();
|
||||
for _ in 0..elements {
|
||||
self.widths.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lines(&self, layout_width: i32, minimum_space_width: i32) -> Lines<'_> {
|
||||
Lines {
|
||||
minimum_space_width,
|
||||
|
@ -213,11 +226,12 @@ impl Preprocessed {
|
|||
&self,
|
||||
layout_width: i32,
|
||||
minimum_space_width: i32,
|
||||
) -> impl Iterator<Item = (Line, &[PreprocessedElement])> {
|
||||
) -> impl Iterator<Item = (Line, impl Iterator<Item = PreprocessedElement> + '_)> {
|
||||
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)];
|
||||
|
||||
let d = self.widths.range(idx..(idx + length)).copied();
|
||||
idx += length;
|
||||
(x, d)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue