diff --git a/src/buffer.rs b/src/buffer.rs index 43e4858..9680bb5 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -7,7 +7,7 @@ use std::{ }; use unicode_segmentation::UnicodeSegmentation; -use crate::{FontLayoutLine, FontMatches, FontShapeLine}; +use crate::{LayoutGlyph, LayoutLine, FontMatches, FontShapeLine}; /// An action to perform on a [TextBuffer] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -76,11 +76,73 @@ impl TextLayoutCursor { } pub struct TextLayoutRun<'a> { - line_i: TextLineIndex, - text: &'a str, - shape: &'a FontShapeLine, - layout_line: &'a FontLayoutLine, + /// The index of the original text line + pub line_i: TextLineIndex, + /// The original text line + pub text: &'a str, + /// True if the original shaped line was RTL + pub rtl: bool, + /// The array of layout glyphs to draw + pub glyphs: &'a [LayoutGlyph], + /// Y offset of line + pub line_y: i32, +} + +pub struct TextLayoutRunIter<'a> { + buffer: &'a TextBuffer<'a>, + line_i: usize, + layout_i: usize, line_y: i32, + total_layout: i32, +} + +impl<'a> TextLayoutRunIter<'a> { + pub fn new(buffer: &'a TextBuffer<'a>) -> Self { + Self { + buffer, + line_i: 0, + layout_i: 0, + line_y: buffer.metrics.font_size - buffer.metrics.line_height, + total_layout: 0, + } + } +} + +impl<'a> Iterator for TextLayoutRunIter<'a> { + type Item = TextLayoutRun<'a>; + + fn next(&mut self) -> Option { + while let Some(line) = self.buffer.lines.get(self.line_i) { + let shape = line.shape_opt.as_ref()?; + let layout = line.layout_opt.as_ref()?; + while let Some(layout_line) = layout.get(self.layout_i) { + self.layout_i += 1; + + let scrolled = self.total_layout < self.buffer.scroll; + self.total_layout += 1; + if scrolled { + continue; + } + + self.line_y += self.buffer.metrics.line_height; + if self.line_y > self.buffer.height { + return None; + } + + return Some(TextLayoutRun { + line_i: TextLineIndex::new(self.line_i), + text: line.text.as_str(), + rtl: shape.rtl, + glyphs: &layout_line.glyphs, + line_y: self.line_y, + }); + } + self.line_i += 1; + self.layout_i = 0; + } + + None + } } /// Index of a text line @@ -128,7 +190,7 @@ impl fmt::Display for TextMetrics { pub struct TextBufferLine { text: String, shape_opt: Option, - layout_opt: Option>, + layout_opt: Option>, } impl TextBufferLine { @@ -157,7 +219,7 @@ impl TextBufferLine { self.shape_opt.as_ref().unwrap() } - pub fn layout(&mut self, font_matches: &FontMatches<'_>, font_size: i32, width: i32) -> &[FontLayoutLine] { + pub fn layout(&mut self, font_matches: &FontMatches<'_>, font_size: i32, width: i32) -> &[LayoutLine] { if self.layout_opt.is_none() { let mut layout = Vec::new(); let shape = self.shape(font_matches); @@ -802,43 +864,8 @@ impl<'a> TextBuffer<'a> { } /// Get the visible layout runs for rendering and other tasks - pub fn with_layout_runs(&self, mut f: F) { - let mut line_y = self.metrics.font_size; - let mut total_layout = 0; - for (line_i, line) in self.lines.iter().enumerate() { - let shape = match line.shape_opt.as_ref() { - Some(some) => some, - None => break, - }; - - let layout = match line.layout_opt.as_ref() { - Some(some) => some, - None => break, - }; - - for layout_line in layout { - let scrolled = total_layout < self.scroll; - total_layout += 1; - - if scrolled { - continue; - } - - if line_y > self.height { - return; - } - - f(TextLayoutRun { - line_i: TextLineIndex::new(line_i), - text: line.text.as_str(), - shape, - layout_line, - line_y, - }); - - line_y += self.metrics.line_height; - } - } + pub fn layout_runs(&self) -> TextLayoutRunIter { + TextLayoutRunIter::new(self) } /// Draw the buffer @@ -849,16 +876,13 @@ impl<'a> TextBuffer<'a> { let font_size = self.metrics.font_size; let line_height = self.metrics.line_height; - self.with_layout_runs(|run| { + for run in self.layout_runs() { let line_i = run.line_i; - let text = run.text; - let shape = run.shape; - let layout_line = run.layout_line; let line_y = run.line_y; let cursor_glyph_opt = |cursor: &TextCursor| -> Option<(usize, f32)> { if cursor.line == line_i { - for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() { + for (glyph_i, glyph) in run.glyphs.iter().enumerate() { if cursor.index == glyph.start { return Some((glyph_i, 0.0)); } else if cursor.index > glyph.start && cursor.index < glyph.end { @@ -866,7 +890,7 @@ impl<'a> TextBuffer<'a> { let mut before = 0; let mut total = 0; - let cluster = &text[glyph.start..glyph.end]; + let cluster = &run.text[glyph.start..glyph.end]; for (i, _) in cluster.grapheme_indices(true) { if glyph.start + i < cursor.index { before += 1; @@ -878,10 +902,10 @@ impl<'a> TextBuffer<'a> { return Some((glyph_i, offset)); } } - match layout_line.glyphs.last() { + match run.glyphs.last() { Some(glyph) => { if cursor.index == glyph.end { - return Some((layout_line.glyphs.len(), 0.0)); + return Some((run.glyphs.len(), 0.0)); } }, None => { @@ -910,9 +934,9 @@ impl<'a> TextBuffer<'a> { if line_i >= start.line && line_i <= end.line { let mut range_opt = None; - for glyph in layout_line.glyphs.iter() { + for glyph in run.glyphs.iter() { // Guess x offset based on characters - let cluster = &text[glyph.start..glyph.end]; + let cluster = &run.text[glyph.start..glyph.end]; let total = cluster.grapheme_indices(true).count(); let mut c_x = glyph.x; let c_w = glyph.w / total as f32; @@ -947,7 +971,7 @@ impl<'a> TextBuffer<'a> { if let Some((mut min, mut max)) = range_opt.take() { if end.line > line_i { // Draw to end of line - if shape.rtl { + if run.rtl { min = 0; } else { max = self.width; @@ -967,7 +991,7 @@ impl<'a> TextBuffer<'a> { // Draw cursor //TODO: draw at end of line but not start of next line if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) { - let x = match layout_line.glyphs.get(cursor_glyph) { + let x = match run.glyphs.get(cursor_glyph) { Some(glyph) => { // Start of detected glyph if glyph.rtl { @@ -976,7 +1000,7 @@ impl<'a> TextBuffer<'a> { (glyph.x + cursor_glyph_offset) as i32 } }, - None => match layout_line.glyphs.last() { + None => match run.glyphs.last() { Some(glyph) => { // End of last glyph if glyph.rtl { @@ -1001,12 +1025,12 @@ impl<'a> TextBuffer<'a> { ); } - for glyph in layout_line.glyphs.iter() { + for glyph in run.glyphs.iter() { let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); cache.with_pixels(&self.font_matches, cache_key, color, |x, y, color| { f(x_int + x, line_y + y_int + y, 1, 1, color) }); } - }); + } } } diff --git a/src/font/cache.rs b/src/cache.rs similarity index 94% rename from src/font/cache.rs rename to src/cache.rs index bd4cd34..9c77626 100644 --- a/src/font/cache.rs +++ b/src/cache.rs @@ -1,11 +1,17 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 +/// Key for building a glyph cache #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct CacheKey { + /// Font ID pub font_id: fontdb::ID, + /// Glyph ID pub glyph_id: u16, + /// Font size in pixels pub font_size: i32, + /// Binning of fractional X offset pub x_bin: SubpixelBin, + /// Binning of fractional Y offset pub y_bin: SubpixelBin, } @@ -32,6 +38,7 @@ impl CacheKey { } } +/// Binning of subpixel position for cache optimization #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SubpixelBin { Zero, diff --git a/src/font/layout.rs b/src/font/layout.rs deleted file mode 100644 index 09725ba..0000000 --- a/src/font/layout.rs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use super::CacheKey; - -pub struct FontLayoutGlyph { - pub start: usize, - pub end: usize, - pub x: f32, - pub w: f32, - pub rtl: bool, - pub cache_key: CacheKey, - pub x_int: i32, - pub y_int: i32, -} - -pub struct FontLayoutLine { - pub rtl: bool, - pub glyphs: Vec, -} diff --git a/src/font/mod.rs b/src/font/mod.rs index 2ace434..1304a3b 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -2,15 +2,9 @@ pub(crate) mod fallback; -pub(crate) use self::cache::*; -mod cache; - pub(crate) use self::font::*; mod font; -pub(crate) use self::layout::*; -mod layout; - pub use self::matches::*; mod matches; diff --git a/src/font/shape.rs b/src/font/shape.rs index 80d068c..7bede6e 100644 --- a/src/font/shape.rs +++ b/src/font/shape.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 -use super::{CacheKey, FontLayoutGlyph, FontLayoutLine}; +use crate::{CacheKey, LayoutGlyph, LayoutLine}; pub struct FontShapeGlyph { pub start: usize, @@ -14,7 +14,7 @@ pub struct FontShapeGlyph { } impl FontShapeGlyph { - fn layout(&self, font_size: i32, x: f32, y: f32, rtl: bool) -> FontLayoutGlyph { + fn layout(&self, font_size: i32, x: f32, y: f32, rtl: bool) -> LayoutGlyph { let x_offset = font_size as f32 * self.x_offset; let y_offset = font_size as f32 * self.y_offset; let x_advance = font_size as f32 * self.x_advance; @@ -25,7 +25,7 @@ impl FontShapeGlyph { font_size, (x + x_offset, y - y_offset) ); - FontLayoutGlyph { + LayoutGlyph { start: self.start, end: self.end, x, @@ -58,7 +58,7 @@ impl FontShapeLine { &self, font_size: i32, line_width: i32, - layout_lines: &mut Vec, + layout_lines: &mut Vec, mut layout_i: usize, ) { let mut push_line = true; @@ -171,8 +171,7 @@ impl FontShapeLine { std::mem::swap(&mut glyphs, &mut glyphs_swap); layout_lines.insert( layout_i, - FontLayoutLine { - rtl: self.rtl, + LayoutLine { glyphs: glyphs_swap, }, ); @@ -205,8 +204,7 @@ impl FontShapeLine { std::mem::swap(&mut glyphs, &mut glyphs_swap); layout_lines.insert( layout_i, - FontLayoutLine { - rtl: self.rtl, + LayoutLine { glyphs: glyphs_swap, }, ); @@ -221,8 +219,7 @@ impl FontShapeLine { if push_line { layout_lines.insert( layout_i, - FontLayoutLine { - rtl: self.rtl, + LayoutLine { glyphs, }, ); diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 0000000..e7ddd4b --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use super::CacheKey; + +/// A laid out glyph +pub struct LayoutGlyph { + /// Start index of cluster in original line + pub start: usize, + /// End index of cluster in original line + pub end: usize, + /// X offset of hitbox + pub x: f32, + /// width of hitbox + pub w: f32, + /// True if the character is from an RTL script + pub rtl: bool, + /// Cache key, see [CacheKey] + pub cache_key: CacheKey, + /// Integer component of X offset in line + pub x_int: i32, + /// Integer component of Y offset in line + pub y_int: i32, +} + +/// A line of laid out glyphs +pub struct LayoutLine { + /// Glyphs in line + pub glyphs: Vec, +} diff --git a/src/lib.rs b/src/lib.rs index 9ab2f13..b952ded 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,15 @@ pub use self::buffer::*; mod buffer; +pub use self::cache::*; +mod cache; + pub use self::font::*; mod font; +pub use self::layout::*; +mod layout; + #[cfg(feature = "swash")] pub use self::swash::*; #[cfg(feature = "swash")] diff --git a/src/swash.rs b/src/swash.rs index 7d4a288..41b07e0 100644 --- a/src/swash.rs +++ b/src/swash.rs @@ -9,7 +9,7 @@ pub use swash::scale::image::Image as SwashImage; pub struct SwashCache { context: ScaleContext, - cache: HashMap>, + pub image_cache: HashMap>, } impl SwashCache { @@ -17,13 +17,13 @@ impl SwashCache { pub fn new() -> Self { Self { context: ScaleContext::new(), - cache: HashMap::new() + image_cache: HashMap::new() } } /// Create a swash Image from a cache key, caching results pub fn get_image(&mut self, matches: &FontMatches<'_>, cache_key: CacheKey) -> &Option { - self.cache.entry(cache_key).or_insert_with(|| { + self.image_cache.entry(cache_key).or_insert_with(|| { let font = match matches.get_font(&cache_key.font_id) { Some(some) => some, None => {