diff --git a/CHANGELOG.md b/CHANGELOG.md index ed8e8d5a..47662270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +0.6.3 (???) +================== + +## Changed: + + - [#161] Internal improvements + +[#161]: https://github.com/embedded-graphics/embedded-text/pull/161 + 0.6.2 (2023-09-04) ================== diff --git a/Cargo.toml b/Cargo.toml index 8c7bbcb8..d1ec9959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,3 +49,4 @@ object-chain = "0.1" [dev-dependencies] embedded-graphics-simulator = "0.5.0" sdl2 = "0.35.2" +rayon-core = "=1.11" diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 622242c6..c5f525e8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -114,6 +114,25 @@ where pub fn as_str(&self) -> &str { self.inner.as_str() } + + fn consume_string(&mut self, string: &'a str, c: char) -> &'a str { + // pointer arithmetic to get the offset of `c` relative to `string` + let offset = { + let ptr_start = string.as_ptr() as usize; + let ptr_cur = self.inner.as_str().as_ptr() as usize; + ptr_cur - ptr_start - c.len_utf8() + }; + + debug_assert!(string.is_char_boundary(offset)); + + unsafe { + // SAFETY: we only work with character boundaries and + // offset is <= length + self.inner = string.get_unchecked(offset..).chars(); + + string.get_unchecked(0..offset) + } + } } impl<'a, C> Iterator for Parser<'a, C> @@ -131,23 +150,8 @@ where // find the longest consecutive slice of text for a Word token for c in &mut self.inner { if !is_word_char(c) { - // pointer arithmetic to get the offset of `c` relative to `string` - let offset = { - let ptr_start = string.as_ptr() as usize; - let ptr_cur = self.inner.as_str().as_ptr() as usize; - ptr_cur - ptr_start - c.len_utf8() - }; - debug_assert!(string.is_char_boundary(offset)); - let (word, remainder) = unsafe { - // SAFETY: we only work with character boundaries and - // offset is <= length - ( - string.get_unchecked(0..offset), - string.get_unchecked(offset..).chars(), - ) - }; - self.inner = remainder; - return Some(Token::Word(word)); + let consumed = self.consume_string(string, c); + return Some(Token::Word(consumed)); } } @@ -182,24 +186,8 @@ where len += 1; } } else { - // pointer arithmetic to get the offset of `c` relative to `string` - let offset = { - let ptr_start = string.as_ptr() as usize; - let ptr_cur = self.inner.as_str().as_ptr() as usize; - ptr_cur - ptr_start - c.len_utf8() - }; - debug_assert!(string.is_char_boundary(offset)); - // consume the whitespaces - let (sequence, remainder) = unsafe { - // SAFETY: we only work with character boundaries and - // offset is <= length - ( - string.get_unchecked(0..offset), - string.get_unchecked(offset..).chars(), - ) - }; - self.inner = remainder; - return Some(Token::Whitespace(len, sequence)); + let consumed = self.consume_string(string, c); + return Some(Token::Whitespace(len, consumed)); } } diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 3ee6dec7..ac80684f 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -7,6 +7,7 @@ use core::{ cell::UnsafeCell, hash::{Hash, Hasher}, marker::PhantomData, + ptr::addr_of, }; use embedded_graphics::{ draw_target::DrawTarget, @@ -74,7 +75,7 @@ where #[derive(Clone, Debug)] pub(crate) struct PluginInner<'a, M, C> { - pub(crate) plugin: M, + plugin: M, state: ProcessingState, peeked_token: Option>, } @@ -87,7 +88,11 @@ pub(crate) struct PluginWrapper<'a, M, C> { impl<'a, M: Clone, C: Clone> Clone for PluginWrapper<'a, M, C> { fn clone(&self) -> Self { Self { - inner: UnsafeCell::new(self.inner(|this| this.clone())), + inner: UnsafeCell::new(self.with(|this| PluginInner { + plugin: this.plugin.clone(), + state: this.state.clone(), + peeked_token: unsafe { addr_of!(this.peeked_token).read() }, + })), } } } @@ -97,7 +102,7 @@ where C: PixelColor, { fn hash(&self, state: &mut H) { - self.inner(|this| this.state.hash(state)) + self.with(|this| this.state.hash(state)) } } @@ -116,10 +121,19 @@ impl<'a, M, C> PluginWrapper<'a, M, C> { self.inner.into_inner().plugin } - fn inner(&self, cb: impl FnOnce(&mut PluginInner<'a, M, C>) -> R) -> R { + fn with(&self, cb: impl FnOnce(&PluginInner<'a, M, C>) -> R) -> R { let inner = unsafe { // SAFETY: This is safe because we aren't exposing the reference. - core::ptr::NonNull::new_unchecked(self.inner.get()).as_mut() + self.inner.get().as_ref().unwrap_unchecked() + }; + + cb(inner) + } + + fn with_mut(&self, cb: impl FnOnce(&mut PluginInner<'a, M, C>) -> R) -> R { + let inner = unsafe { + // SAFETY: This is safe because we aren't exposing the reference. + self.inner.get().as_mut().unwrap_unchecked() }; cb(inner) @@ -132,23 +146,23 @@ where M: private::Plugin<'a, C>, { pub fn new_line(&self) { - self.inner(|this| this.plugin.new_line()); + self.with_mut(|this| this.plugin.new_line()); } pub fn set_state(&self, state: ProcessingState) { - self.inner(|this| this.state = state); + self.with_mut(|this| this.state = state); } #[inline] pub fn render_token(&self, token: Token<'a, C>) -> Option> { - self.inner(|this| match this.state { + self.with_mut(|this| match this.state { ProcessingState::Measure => Some(token), ProcessingState::Render => this.plugin.render_token(token), }) } pub fn peek_token(&self, source: &mut Parser<'a, C>) -> Option> { - self.inner(|this| { + self.with_mut(|this| { if this.peeked_token.is_none() { this.peeked_token = this.plugin.next_token(|| source.next()); } @@ -158,11 +172,11 @@ where } pub fn consume_peeked_token(&self) { - self.inner(|this| this.peeked_token = None); + self.with_mut(|this| this.peeked_token = None); } pub fn consume_partial(&self, len: usize) { - self.inner(|this| { + self.with_mut(|this| { // Only string-like tokens can be partially consumed. debug_assert!(matches!( this.peeked_token, @@ -196,7 +210,7 @@ where cursor: &mut Cursor, props: TextBoxProperties<'_, S>, ) { - self.inner(|this| { + self.with_mut(|this| { this.peeked_token = None; this.plugin.on_start_render(cursor, &props); @@ -204,7 +218,7 @@ where } pub fn on_rendering_finished(&self) { - self.inner(|this| this.plugin.on_rendering_finished()); + self.with_mut(|this| this.plugin.on_rendering_finished()); } pub fn post_render( @@ -218,7 +232,7 @@ where T: TextRenderer, D: DrawTarget, { - self.inner(|this| { + self.with_mut(|this| { this.plugin .post_render(draw_target, character_style, text, bounds) }) diff --git a/src/rendering/line.rs b/src/rendering/line.rs index 382802f2..01a15735 100644 --- a/src/rendering/line.rs +++ b/src/rendering/line.rs @@ -90,6 +90,26 @@ where plugin: &'b PluginWrapper<'a, M, F::Color>, } +impl<'a, 'b, F, D, M> RenderElementHandler<'a, 'b, F, D, M> +where + F: CharacterStyle + TextRenderer, + ::Color: From, + D: DrawTarget::Color>, + M: Plugin<'a, ::Color>, +{ + fn post_print(&mut self, width: u32, st: &str) -> Result<(), D::Error> { + let bounds = Rectangle::new( + self.pos, + Size::new(width, self.style.line_height().saturating_as()), + ); + + self.pos += Point::new(width.saturating_as(), 0); + + self.plugin + .post_render(self.display, self.style, Some(st), bounds) + } +} + impl<'a, 'c, F, D, M> ElementHandler for RenderElementHandler<'a, 'c, F, D, M> where F: CharacterStyle + TextRenderer, @@ -105,39 +125,22 @@ where } fn whitespace(&mut self, st: &str, _space_count: u32, width: u32) -> Result<(), Self::Error> { - let top_left = self.pos; if width > 0 { - self.pos = self - .style + self.style .draw_whitespace(width, self.pos, Baseline::Top, self.display)?; } - let size = Size::new(width, self.style.line_height().saturating_as()); - let bounds = Rectangle::new(top_left, size); - - self.plugin - .post_render(self.display, self.style, Some(st), bounds)?; - - Ok(()) + self.post_print(width, st) } fn printed_characters(&mut self, st: &str, width: Option) -> Result<(), Self::Error> { - let top_left = self.pos; let render_width = self .style .draw_string(st, self.pos, Baseline::Top, self.display)?; - let width = width.unwrap_or((render_width - top_left).x as u32); + let width = width.unwrap_or((render_width - self.pos).x as u32); - self.pos += Point::new(width.saturating_as(), 0); - - let size = Size::new(width, self.style.line_height().saturating_as()); - let bounds = Rectangle::new(top_left, size); - - self.plugin - .post_render(self.display, self.style, Some(st), bounds)?; - - Ok(()) + self.post_print(width, st) } fn move_cursor(&mut self, by: i32) -> Result<(), Self::Error> { diff --git a/src/rendering/line_iter.rs b/src/rendering/line_iter.rs index eeecb8e1..c2de8850 100644 --- a/src/rendering/line_iter.rs +++ b/src/rendering/line_iter.rs @@ -10,7 +10,7 @@ use crate::{ rendering::{cursor::LineCursor, space_config::SpaceConfig}, style::TextBoxStyle, }; -use az::{SaturatingAs, SaturatingCast}; +use az::SaturatingAs; use embedded_graphics::{pixelcolor::Rgb888, prelude::PixelColor}; /// Parser to break down a line into primitive elements used by measurement and rendering. @@ -156,11 +156,10 @@ where (w, "") } - fn next_word_fits(&self, space_width: i32, handler: &E) -> bool { + fn next_word_fits(&self, handler: &E) -> bool { let mut cursor = self.cursor.clone(); - let mut spaces = self.spaces; - let mut exit = false; + let mut spaces = self.spaces; // This looks extremely inefficient. let lookahead = self.plugin.clone(); @@ -169,9 +168,7 @@ where // We don't want to count the current token. lookahead.consume_peeked_token(); - if cursor.move_cursor(space_width).is_err() { - return false; - } + let mut exit = false; while !exit { let width = match lookahead.peek_token(&mut lookahead_parser) { Some(Token::Word(w)) | Some(Token::Break(w, _)) => { @@ -219,17 +216,13 @@ where handler.whitespace(string, space_count, 0)?; return Ok(false); } - let signed_width = space_width.saturating_as(); - let draw_whitespace = (self.empty && self.render_leading_spaces()) - || self.render_trailing_spaces() - || self.next_word_fits(signed_width, handler); - match self.move_cursor(signed_width) { + match self.move_cursor(space_width.saturating_as()) { Ok(moved) => { handler.whitespace( string, space_count, - moved.saturating_as::() * draw_whitespace as u32, + moved.saturating_as::() * self.should_draw_whitespace(handler) as u32, )?; } @@ -273,16 +266,12 @@ where return Ok(()); } - let draw_whitespace = (self.empty && self.render_leading_spaces()) - || self.render_trailing_spaces() - || self.next_word_fits(space_width.saturating_as(), handler); - - match self.move_cursor(space_width.saturating_cast()) { - Ok(moved) if draw_whitespace => handler.whitespace("\t", 0, moved.saturating_as())?, - - Ok(moved) | Err(moved) => { - handler.move_cursor(moved.saturating_as())?; + match self.move_cursor(space_width.saturating_as()) { + Ok(moved) if self.should_draw_whitespace(handler) => { + handler.whitespace("\t", 0, moved.saturating_as())? } + + Ok(moved) | Err(moved) => handler.move_cursor(moved)?, } Ok(()) } @@ -459,6 +448,12 @@ where Ok(()) } + + fn should_draw_whitespace(&self, handler: &E) -> bool { + (self.empty && self.render_leading_spaces()) + || self.render_trailing_spaces() + || self.next_word_fits(handler) + } } #[cfg(test)]