Skip to content

Commit 57bb8e3

Browse files
committed
Move LayoutRun back to buffer module
1 parent c8a5391 commit 57bb8e3

File tree

2 files changed

+164
-163
lines changed

2 files changed

+164
-163
lines changed

src/buffer.rs

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,170 @@ use unicode_segmentation::UnicodeSegmentation;
1111

1212
use crate::{
1313
render_decoration, Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem,
14-
BufferLine, Color, Cursor, Ellipsize, FontSystem, Hinting, LayoutCursor, LayoutLine,
15-
LayoutRunIter, LineEnding, LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap,
14+
BufferLine, Color, Cursor, DecorationSpan, Ellipsize, FontSystem, Hinting, LayoutCursor,
15+
LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping,
16+
Wrap,
1617
};
1718

19+
/// A line of visible text for rendering
20+
#[derive(Debug)]
21+
pub struct LayoutRun<'a> {
22+
/// The index of the original text line
23+
pub line_i: usize,
24+
/// The original text line
25+
pub text: &'a str,
26+
/// True if the original paragraph direction is RTL
27+
pub rtl: bool,
28+
/// The array of layout glyphs to draw
29+
pub glyphs: &'a [LayoutGlyph],
30+
/// Text decoration spans covering ranges of glyphs
31+
pub decorations: &'a [DecorationSpan],
32+
/// Y offset to baseline of line
33+
pub line_y: f32,
34+
/// Y offset to top of line
35+
pub line_top: f32,
36+
/// Y offset to next line
37+
pub line_height: f32,
38+
/// Width of line
39+
pub line_w: f32,
40+
}
41+
42+
impl LayoutRun<'_> {
43+
/// Return the pixel span `Some((x_left, x_width))` of the highlighted area between `cursor_start`
44+
/// and `cursor_end` within this run, or None if the cursor range does not intersect this run.
45+
/// This may return widths of zero if `cursor_start == cursor_end`, if the run is empty, or if the
46+
/// region's left start boundary is the same as the cursor's end boundary or vice versa.
47+
#[allow(clippy::missing_panics_doc)]
48+
pub fn highlight(&self, cursor_start: Cursor, cursor_end: Cursor) -> Option<(f32, f32)> {
49+
let mut x_start = None;
50+
let mut x_end = None;
51+
let rtl_factor = if self.rtl { 1. } else { 0. };
52+
let ltr_factor = 1. - rtl_factor;
53+
for glyph in self.glyphs {
54+
let cursor = self.cursor_from_glyph_left(glyph);
55+
if cursor >= cursor_start && cursor <= cursor_end {
56+
if x_start.is_none() {
57+
x_start = Some(glyph.x + glyph.w.mul_add(rtl_factor, 0.0));
58+
}
59+
x_end = Some(glyph.x + glyph.w.mul_add(rtl_factor, 0.0));
60+
}
61+
let cursor = self.cursor_from_glyph_right(glyph);
62+
if cursor >= cursor_start && cursor <= cursor_end {
63+
if x_start.is_none() {
64+
x_start = Some(glyph.x + glyph.w.mul_add(ltr_factor, 0.0));
65+
}
66+
x_end = Some(glyph.x + glyph.w.mul_add(ltr_factor, 0.0));
67+
}
68+
}
69+
x_start.map(|x_start| {
70+
let x_end = x_end.expect("end of cursor not found");
71+
let (x_start, x_end) = if x_start < x_end {
72+
(x_start, x_end)
73+
} else {
74+
(x_end, x_start)
75+
};
76+
(x_start, x_end - x_start)
77+
})
78+
}
79+
80+
const fn cursor_from_glyph_left(&self, glyph: &LayoutGlyph) -> Cursor {
81+
if self.rtl {
82+
Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
83+
} else {
84+
Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
85+
}
86+
}
87+
88+
const fn cursor_from_glyph_right(&self, glyph: &LayoutGlyph) -> Cursor {
89+
if self.rtl {
90+
Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
91+
} else {
92+
Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
93+
}
94+
}
95+
}
96+
97+
/// An iterator of visible text lines, see [`LayoutRun`]
98+
#[derive(Debug)]
99+
pub struct LayoutRunIter<'b> {
100+
lines: &'b [BufferLine],
101+
height_opt: Option<f32>,
102+
line_height: f32,
103+
scroll: f32,
104+
line_i: usize,
105+
layout_i: usize,
106+
total_height: f32,
107+
line_top: f32,
108+
}
109+
110+
impl<'b> LayoutRunIter<'b> {
111+
pub const fn new(
112+
lines: &'b [BufferLine],
113+
height_opt: Option<f32>,
114+
line_height: f32,
115+
scroll: f32,
116+
start: usize,
117+
) -> Self {
118+
Self {
119+
lines,
120+
height_opt,
121+
line_height,
122+
scroll,
123+
line_i: start,
124+
layout_i: 0,
125+
total_height: 0.0,
126+
line_top: 0.0,
127+
}
128+
}
129+
}
130+
131+
impl<'b> Iterator for LayoutRunIter<'b> {
132+
type Item = LayoutRun<'b>;
133+
134+
fn next(&mut self) -> Option<Self::Item> {
135+
while let Some(line) = self.lines.get(self.line_i) {
136+
let shape = line.shape_opt()?;
137+
let layout = line.layout_opt()?;
138+
while let Some(layout_line) = layout.get(self.layout_i) {
139+
self.layout_i += 1;
140+
141+
let line_height = layout_line.line_height_opt.unwrap_or(self.line_height);
142+
self.total_height += line_height;
143+
144+
let line_top = self.line_top - self.scroll;
145+
let glyph_height = layout_line.max_ascent + layout_line.max_descent;
146+
let centering_offset = (line_height - glyph_height) / 2.0;
147+
let line_y = line_top + centering_offset + layout_line.max_ascent;
148+
if let Some(height) = self.height_opt {
149+
if line_y - layout_line.max_ascent > height {
150+
return None;
151+
}
152+
}
153+
self.line_top += line_height;
154+
if line_y + layout_line.max_descent < 0.0 {
155+
continue;
156+
}
157+
158+
return Some(LayoutRun {
159+
line_i: self.line_i,
160+
text: line.text(),
161+
rtl: shape.rtl,
162+
glyphs: &layout_line.glyphs,
163+
decorations: &layout_line.decorations,
164+
line_y,
165+
line_top,
166+
line_height,
167+
line_w: layout_line.w,
168+
});
169+
}
170+
self.line_i += 1;
171+
self.layout_i = 0;
172+
}
173+
174+
None
175+
}
176+
}
177+
18178
/// Metrics of text
19179
#[derive(Clone, Copy, Debug, Default, PartialEq)]
20180
pub struct Metrics {

src/buffer_line.rs

Lines changed: 2 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use alloc::{string::String, vec::Vec};
55
use core::mem;
66

77
use crate::{
8-
Affinity, Align, Attrs, AttrsList, Cached, Cursor, DecorationSpan, Ellipsize, FontSystem,
9-
Hinting, LayoutGlyph, LayoutLine, LineEnding, ShapeLine, Shaping, Wrap,
8+
Align, Attrs, AttrsList, Cached, Ellipsize, FontSystem, Hinting, LayoutLine, LayoutRunIter,
9+
LineEnding, ShapeLine, Shaping, Wrap,
1010
};
1111

1212
/// A line (or paragraph) of text that is shaped and laid out
@@ -325,162 +325,3 @@ impl BufferLine {
325325
text
326326
}
327327
}
328-
329-
/// A line of visible text for rendering
330-
#[derive(Debug)]
331-
pub struct LayoutRun<'a> {
332-
/// The index of the original text line
333-
pub line_i: usize,
334-
/// The original text line
335-
pub text: &'a str,
336-
/// True if the original paragraph direction is RTL
337-
pub rtl: bool,
338-
/// The array of layout glyphs to draw
339-
pub glyphs: &'a [LayoutGlyph],
340-
/// Text decoration spans covering ranges of glyphs
341-
pub decorations: &'a [DecorationSpan],
342-
/// Y offset to baseline of line
343-
pub line_y: f32,
344-
/// Y offset to top of line
345-
pub line_top: f32,
346-
/// Y offset to next line
347-
pub line_height: f32,
348-
/// Width of line
349-
pub line_w: f32,
350-
}
351-
352-
impl LayoutRun<'_> {
353-
/// Return the pixel span `Some((x_left, x_width))` of the highlighted area between `cursor_start`
354-
/// and `cursor_end` within this run, or None if the cursor range does not intersect this run.
355-
/// This may return widths of zero if `cursor_start == cursor_end`, if the run is empty, or if the
356-
/// region's left start boundary is the same as the cursor's end boundary or vice versa.
357-
#[allow(clippy::missing_panics_doc)]
358-
pub fn highlight(&self, cursor_start: Cursor, cursor_end: Cursor) -> Option<(f32, f32)> {
359-
let mut x_start = None;
360-
let mut x_end = None;
361-
let rtl_factor = if self.rtl { 1. } else { 0. };
362-
let ltr_factor = 1. - rtl_factor;
363-
for glyph in self.glyphs {
364-
let cursor = self.cursor_from_glyph_left(glyph);
365-
if cursor >= cursor_start && cursor <= cursor_end {
366-
if x_start.is_none() {
367-
x_start = Some(glyph.x + glyph.w.mul_add(rtl_factor, 0.0));
368-
}
369-
x_end = Some(glyph.x + glyph.w.mul_add(rtl_factor, 0.0));
370-
}
371-
let cursor = self.cursor_from_glyph_right(glyph);
372-
if cursor >= cursor_start && cursor <= cursor_end {
373-
if x_start.is_none() {
374-
x_start = Some(glyph.x + glyph.w.mul_add(ltr_factor, 0.0));
375-
}
376-
x_end = Some(glyph.x + glyph.w.mul_add(ltr_factor, 0.0));
377-
}
378-
}
379-
x_start.map(|x_start| {
380-
let x_end = x_end.expect("end of cursor not found");
381-
let (x_start, x_end) = if x_start < x_end {
382-
(x_start, x_end)
383-
} else {
384-
(x_end, x_start)
385-
};
386-
(x_start, x_end - x_start)
387-
})
388-
}
389-
390-
pub(crate) const fn cursor_from_glyph_left(&self, glyph: &LayoutGlyph) -> Cursor {
391-
if self.rtl {
392-
Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
393-
} else {
394-
Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
395-
}
396-
}
397-
398-
pub(crate) const fn cursor_from_glyph_right(&self, glyph: &LayoutGlyph) -> Cursor {
399-
if self.rtl {
400-
Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
401-
} else {
402-
Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
403-
}
404-
}
405-
}
406-
407-
/// An iterator of visible text lines, see [`LayoutRun`]
408-
#[derive(Debug)]
409-
pub struct LayoutRunIter<'b> {
410-
lines: &'b [BufferLine],
411-
height_opt: Option<f32>,
412-
line_height: f32,
413-
scroll: f32,
414-
line_i: usize,
415-
layout_i: usize,
416-
total_height: f32,
417-
line_top: f32,
418-
}
419-
420-
impl<'b> LayoutRunIter<'b> {
421-
pub const fn new(
422-
lines: &'b [BufferLine],
423-
height_opt: Option<f32>,
424-
line_height: f32,
425-
scroll: f32,
426-
start: usize,
427-
) -> Self {
428-
Self {
429-
lines,
430-
height_opt,
431-
line_height,
432-
scroll,
433-
line_i: start,
434-
layout_i: 0,
435-
total_height: 0.0,
436-
line_top: 0.0,
437-
}
438-
}
439-
}
440-
441-
impl<'b> Iterator for LayoutRunIter<'b> {
442-
type Item = LayoutRun<'b>;
443-
444-
fn next(&mut self) -> Option<Self::Item> {
445-
while let Some(line) = self.lines.get(self.line_i) {
446-
let shape = line.shape_opt()?;
447-
let layout = line.layout_opt()?;
448-
while let Some(layout_line) = layout.get(self.layout_i) {
449-
self.layout_i += 1;
450-
451-
let line_height = layout_line.line_height_opt.unwrap_or(self.line_height);
452-
self.total_height += line_height;
453-
454-
let line_top = self.line_top - self.scroll;
455-
let glyph_height = layout_line.max_ascent + layout_line.max_descent;
456-
let centering_offset = (line_height - glyph_height) / 2.0;
457-
let line_y = line_top + centering_offset + layout_line.max_ascent;
458-
if let Some(height) = self.height_opt {
459-
if line_y - layout_line.max_ascent > height {
460-
return None;
461-
}
462-
}
463-
self.line_top += line_height;
464-
if line_y + layout_line.max_descent < 0.0 {
465-
continue;
466-
}
467-
468-
return Some(LayoutRun {
469-
line_i: self.line_i,
470-
text: line.text(),
471-
rtl: shape.rtl,
472-
glyphs: &layout_line.glyphs,
473-
decorations: &layout_line.decorations,
474-
line_y,
475-
line_top,
476-
line_height,
477-
line_w: layout_line.w,
478-
});
479-
}
480-
self.line_i += 1;
481-
self.layout_i = 0;
482-
}
483-
484-
None
485-
}
486-
}

0 commit comments

Comments
 (0)