Skip to content

Commit d2965eb

Browse files
authored
Merge pull request #105 from kas-gui/push-zvszylowqqqo
Expose per-line data
2 parents cff3688 + 9056db3 commit d2965eb

File tree

7 files changed

+112
-52
lines changed

7 files changed

+112
-52
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ rust-version = "1.88.0"
1515

1616
[package.metadata.docs.rs]
1717
# To build locally:
18-
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --open
18+
# cargo +nightly doc --all-features --no-deps --open
1919
all-features = true
2020

2121
[features]

src/data.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,6 @@ impl Range {
118118
to_usize(self.end) - to_usize(self.start)
119119
}
120120

121-
/// True if the given value is contained, inclusive of end points
122-
pub fn includes(self, value: usize) -> bool {
123-
to_usize(self.start) <= value && value <= to_usize(self.end)
124-
}
125-
126121
/// Convert to a standard range
127122
pub fn to_std(self) -> std::ops::Range<usize> {
128123
to_usize(self.start)..to_usize(self.end)

src/display/glyph_pos.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ impl TextDisplay {
478478
/// Calls `f(top_left, bottom_right)` for each highlighting rectangle.
479479
pub fn highlight_range(&self, range: std::ops::Range<usize>, f: &mut dyn FnMut(Vec2, Vec2)) {
480480
for line in &self.lines {
481-
let line_range: std::ops::Range<usize> = line.text_range.into();
481+
let line_range = line.text_range();
482482
if line_range.end <= range.start {
483483
continue;
484484
} else if range.end <= line_range.start {

src/display/mod.rs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ mod text_runs;
1717
mod wrap_lines;
1818
pub use glyph_pos::{Effect, EffectFlags, GlyphRun, MarkerPos, MarkerPosIter};
1919
pub(crate) use text_runs::RunSpecial;
20-
use wrap_lines::{Line, RunPart};
20+
pub use wrap_lines::Line;
21+
use wrap_lines::RunPart;
2122

2223
/// Error returned on operations if not ready
2324
///
@@ -77,7 +78,7 @@ pub struct NotReady;
7778
/// direction in bi-directional text).
7879
///
7980
/// Navigating to the start or end of a line can be done with
80-
/// [`TextDisplay::find_line`] and [`TextDisplay::line_range`].
81+
/// [`TextDisplay::find_line`], [`TextDisplay::get_line`] and [`Line::text_range`].
8182
///
8283
/// Navigating forwards or backwards should be done via a library such as
8384
/// [`unicode-segmentation`](https://github.com/unicode-rs/unicode-segmentation)
@@ -151,10 +152,27 @@ impl TextDisplay {
151152
/// Get the number of lines (after wrapping)
152153
///
153154
/// [Requires status][Self#status-of-preparation]: lines have been wrapped.
155+
#[inline]
154156
pub fn num_lines(&self) -> usize {
155157
self.lines.len()
156158
}
157159

160+
/// Get line properties
161+
///
162+
/// [Requires status][Self#status-of-preparation]: lines have been wrapped.
163+
#[inline]
164+
pub fn get_line(&self, index: usize) -> Option<&Line> {
165+
self.lines.get(index)
166+
}
167+
168+
/// Iterate over line properties
169+
///
170+
/// [Requires status][Self#status-of-preparation]: lines have been wrapped.
171+
#[inline]
172+
pub fn lines(&self) -> impl Iterator<Item = &Line> {
173+
self.lines.iter()
174+
}
175+
158176
/// Get the size of the required bounding box
159177
///
160178
/// [Requires status][Self#status-of-preparation]: lines have been wrapped.
@@ -184,25 +202,19 @@ impl TextDisplay {
184202
pub fn find_line(&self, index: usize) -> Option<(usize, std::ops::Range<usize>)> {
185203
let mut first = None;
186204
for (n, line) in self.lines.iter().enumerate() {
187-
if line.text_range.end() == index {
205+
let text_range = line.text_range();
206+
if text_range.end == index {
188207
// When line wrapping, this also matches the start of the next
189208
// line which is the preferred location. At the end of other
190209
// lines it does not match any other location.
191-
first = Some((n, line.text_range.to_std()));
192-
} else if line.text_range.includes(index) {
193-
return Some((n, line.text_range.to_std()));
210+
first = Some((n, text_range));
211+
} else if text_range.contains(&index) {
212+
return Some((n, text_range));
194213
}
195214
}
196215
first
197216
}
198217

199-
/// Get the range of a line, by line number
200-
///
201-
/// [Requires status][Self#status-of-preparation]: lines have been wrapped.
202-
pub fn line_range(&self, line: usize) -> Option<std::ops::Range<usize>> {
203-
self.lines.get(line).map(|line| line.text_range.to_std())
204-
}
205-
206218
/// Get the base directionality of the text
207219
///
208220
/// [Requires status][Self#status-of-preparation]: none.
@@ -281,11 +293,11 @@ impl TextDisplay {
281293
let line = &self.lines[line];
282294
let run_range = line.run_range.to_std();
283295

284-
let mut best = line.text_range.start;
296+
let mut best = line.text_range().start;
285297
let mut best_dist = f32::INFINITY;
286-
let mut try_best = |dist, index| {
298+
let mut try_best = |dist, index: u32| {
287299
if dist < best_dist {
288-
best = index;
300+
best = to_usize(index);
289301
best_dist = dist;
290302
}
291303
};
@@ -319,6 +331,6 @@ impl TextDisplay {
319331
try_best((end_pos - rel_pos).abs(), end_index);
320332
}
321333

322-
Some(to_usize(best))
334+
Some(best)
323335
}
324336
}

src/display/wrap_lines.rs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::fonts::{self, FontLibrary};
1111
use crate::shaper::{GlyphRun, PartMetrics};
1212
use crate::{Align, Range, Vec2};
1313
use smallvec::SmallVec;
14+
use std::num::NonZeroUsize;
1415
use tinyvec::TinyVec;
1516
use unicode_bidi::{LTR_LEVEL, Level};
1617

@@ -22,12 +23,33 @@ pub struct RunPart {
2223
pub offset: Vec2,
2324
}
2425

26+
/// Per-line data (post wrapping)
2527
#[derive(Clone, Debug, Default)]
2628
pub struct Line {
27-
pub text_range: Range, // range in text
28-
pub run_range: Range, // range in wrapped_runs
29-
pub top: f32,
30-
pub bottom: f32,
29+
text_range: Range, // range in text
30+
pub(crate) run_range: Range, // range in wrapped_runs
31+
pub(crate) top: f32,
32+
pub(crate) bottom: f32,
33+
}
34+
35+
impl Line {
36+
/// Get the text range of line contents
37+
#[inline]
38+
pub fn text_range(&self) -> std::ops::Range<usize> {
39+
self.text_range.to_std()
40+
}
41+
42+
/// Get the upper bound of the line
43+
#[inline]
44+
pub fn top(&self) -> f32 {
45+
self.top
46+
}
47+
48+
/// Get the lower bound of the line
49+
#[inline]
50+
pub fn bottom(&self) -> f32 {
51+
self.bottom
52+
}
3153
}
3254

3355
impl TextDisplay {
@@ -70,16 +92,15 @@ impl TextDisplay {
7092

7193
/// Measure required vertical height, wrapping as configured
7294
///
95+
/// Stops after `max_lines`, if provided.
96+
///
7397
/// [Requires status][Self#status-of-preparation]: level runs have been
7498
/// prepared.
75-
///
76-
/// This method performs most required preparation steps of the
77-
/// [`TextDisplay`]. Remaining prepartion should be fast.
78-
pub fn measure_height(&self, wrap_width: f32) -> f32 {
99+
pub fn measure_height(&self, wrap_width: f32, max_lines: Option<NonZeroUsize>) -> f32 {
79100
#[derive(Default)]
80101
struct MeasureAdder {
81102
parts: Vec<usize>, // run index for each part
82-
has_any_lines: bool,
103+
lines: usize,
83104
line_gap: f32,
84105
vcaret: f32,
85106
}
@@ -89,6 +110,10 @@ impl TextDisplay {
89110
self.parts.len()
90111
}
91112

113+
fn num_lines(&self) -> usize {
114+
self.lines
115+
}
116+
92117
fn add_part(
93118
&mut self,
94119
_: &[GlyphRun],
@@ -134,7 +159,7 @@ impl TextDisplay {
134159
line_gap = line_gap.max(scale_font.line_gap());
135160
}
136161

137-
if self.has_any_lines {
162+
if self.lines > 0 {
138163
self.vcaret += line_gap.max(self.line_gap);
139164
}
140165
self.vcaret += ascent - descent;
@@ -143,13 +168,14 @@ impl TextDisplay {
143168
// Vertically align lines to the nearest pixel (improves rendering):
144169
self.vcaret = self.vcaret.round();
145170

146-
self.has_any_lines = true;
171+
self.lines += 1;
147172
self.parts.clear();
148173
}
149174
}
150175

151176
let mut adder = MeasureAdder::default();
152-
self.wrap_lines(&mut adder, wrap_width);
177+
let max_lines = max_lines.map(|n| n.get()).unwrap_or(0);
178+
self.wrap_lines(&mut adder, wrap_width, max_lines);
153179
adder.vcaret
154180
}
155181

@@ -188,7 +214,7 @@ impl TextDisplay {
188214
debug_assert!(width_bound.is_finite());
189215
let mut adder = LineAdder::new(width_bound, h_align);
190216

191-
self.wrap_lines(&mut adder, wrap_width);
217+
self.wrap_lines(&mut adder, wrap_width, 0);
192218

193219
self.wrapped_runs = adder.wrapped_runs;
194220
self.lines = adder.lines;
@@ -201,7 +227,12 @@ impl TextDisplay {
201227
adder.vcaret
202228
}
203229

204-
fn wrap_lines(&self, accumulator: &mut impl PartAccumulator, wrap_width: f32) {
230+
fn wrap_lines(
231+
&self,
232+
accumulator: &mut impl PartAccumulator,
233+
wrap_width: f32,
234+
max_lines: usize,
235+
) {
205236
let fonts = fonts::library();
206237

207238
// Tuples: (index, part_index, num_parts)
@@ -244,6 +275,10 @@ impl TextDisplay {
244275
// Add up to last valid break point then wrap and reset
245276
accumulator.add_line(fonts, &self.runs, end.2, true);
246277

278+
if accumulator.num_lines() == max_lines {
279+
return;
280+
}
281+
247282
end.2 = 0;
248283
start = end;
249284
caret = 0.0;
@@ -276,6 +311,10 @@ impl TextDisplay {
276311
debug_assert_eq!(num_parts, end.2);
277312

278313
accumulator.add_line(fonts, &self.runs, num_parts, false);
314+
315+
if accumulator.num_lines() == max_lines {
316+
return;
317+
}
279318
}
280319

281320
start = (run_index, 0, 0);
@@ -332,6 +371,7 @@ impl TextDisplay {
332371

333372
trait PartAccumulator {
334373
fn num_parts(&self) -> usize;
374+
fn num_lines(&self) -> usize;
335375

336376
fn add_part(
337377
&mut self,
@@ -386,6 +426,10 @@ impl PartAccumulator for LineAdder {
386426
self.parts.len()
387427
}
388428

429+
fn num_lines(&self) -> usize {
430+
self.lines.len()
431+
}
432+
389433
fn add_part(
390434
&mut self,
391435
runs: &[GlyphRun],

src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@
1313
//!
1414
//! [`format`]: mod@format
1515
16-
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
17-
1816
mod env;
1917
pub use env::*;
2018

2119
mod conv;
2220
pub use conv::DPU;
2321

2422
mod data;
25-
pub use data::{Range, Vec2};
23+
use data::Range;
24+
pub use data::Vec2;
2625

2726
mod display;
2827
pub use display::*;

src/text.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
use crate::display::{Effect, MarkerPosIter, NotReady, TextDisplay};
99
use crate::fonts::{FontSelector, NoFontMatch};
1010
use crate::format::FormattableText;
11-
use crate::{Align, Direction, GlyphRun, Status, Vec2};
11+
use crate::{Align, Direction, GlyphRun, Line, Status, Vec2};
12+
use std::num::NonZeroUsize;
1213

1314
/// Text type-setting object (high-level API)
1415
///
@@ -420,14 +421,16 @@ impl<T: FormattableText + ?Sized> Text<T> {
420421
}
421422

422423
/// Measure required vertical height, wrapping as configured
423-
pub fn measure_height(&mut self) -> Result<f32, NoFontMatch> {
424+
///
425+
/// Stops after `max_lines`, if provided.
426+
pub fn measure_height(&mut self, max_lines: Option<NonZeroUsize>) -> Result<f32, NoFontMatch> {
424427
if self.status >= Status::Wrapped {
425428
let (tl, br) = self.display.bounding_box();
426429
return Ok(br.1 - tl.1);
427430
}
428431

429432
self.prepare_runs()?;
430-
Ok(self.display.measure_height(self.wrap_width))
433+
Ok(self.display.measure_height(self.wrap_width, max_lines))
431434
}
432435

433436
/// Prepare text for display, as necessary
@@ -471,6 +474,7 @@ impl<T: FormattableText + ?Sized> Text<T> {
471474
pub fn bounding_box(&self) -> Result<(Vec2, Vec2), NotReady> {
472475
Ok(self.wrapped_display()?.bounding_box())
473476
}
477+
474478
/// Get the number of lines (after wrapping)
475479
///
476480
/// See [`TextDisplay::num_lines`].
@@ -479,6 +483,20 @@ impl<T: FormattableText + ?Sized> Text<T> {
479483
Ok(self.wrapped_display()?.num_lines())
480484
}
481485

486+
/// Get line properties
487+
#[inline]
488+
pub fn get_line(&self, index: usize) -> Result<Option<&Line>, NotReady> {
489+
Ok(self.wrapped_display()?.get_line(index))
490+
}
491+
492+
/// Iterate over line properties
493+
///
494+
/// [Requires status][Self#status-of-preparation]: lines have been wrapped.
495+
#[inline]
496+
pub fn lines(&self) -> Result<impl Iterator<Item = &Line>, NotReady> {
497+
Ok(self.wrapped_display()?.lines())
498+
}
499+
482500
/// Find the line containing text `index`
483501
///
484502
/// See [`TextDisplay::find_line`].
@@ -490,14 +508,6 @@ impl<T: FormattableText + ?Sized> Text<T> {
490508
Ok(self.wrapped_display()?.find_line(index))
491509
}
492510

493-
/// Get the range of a line, by line number
494-
///
495-
/// See [`TextDisplay::line_range`].
496-
#[inline]
497-
pub fn line_range(&self, line: usize) -> Result<Option<std::ops::Range<usize>>, NotReady> {
498-
Ok(self.wrapped_display()?.line_range(line))
499-
}
500-
501511
/// Get the directionality of the current line
502512
///
503513
/// See [`TextDisplay::line_is_rtl`].

0 commit comments

Comments
 (0)