Add layout run iterator
This commit is contained in:
parent
5d7dd59078
commit
a9b7b4e914
8 changed files with 135 additions and 97 deletions
142
src/buffer.rs
142
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<Self::Item> {
|
||||
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<FontShapeLine>,
|
||||
layout_opt: Option<Vec<FontLayoutLine>>,
|
||||
layout_opt: Option<Vec<LayoutLine>>,
|
||||
}
|
||||
|
||||
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<F: FnMut(TextLayoutRun)>(&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)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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<FontLayoutGlyph>,
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<FontLayoutLine>,
|
||||
layout_lines: &mut Vec<LayoutLine>,
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
29
src/layout.rs
Normal file
29
src/layout.rs
Normal file
|
|
@ -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<LayoutGlyph>,
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub use swash::scale::image::Image as SwashImage;
|
|||
|
||||
pub struct SwashCache {
|
||||
context: ScaleContext,
|
||||
cache: HashMap<CacheKey, Option<SwashImage>>,
|
||||
pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
|
||||
}
|
||||
|
||||
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<SwashImage> {
|
||||
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 => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue