1use std::collections::HashMap;
2use std::ops::Range;
3
4use unicode_bidi::BidiInfo;
5use valo_geometry::{Color, Rect};
6
7use crate::font::{FaceSet, FontCollection, FontDemand, FontId};
8use crate::shape::{shape_runs, ShapedRun};
9use crate::style::{ParagraphStyle, TextDirection, TextStyle};
10use crate::wrap::{place_lines, wrap_lines, Wrapped};
11
12#[derive(Clone, Copy, Debug)]
14pub struct PlacedGlyph {
15 pub id: u32,
16 pub x: f32,
17 pub y: f32,
18 pub cluster: usize,
20 pub advance: f32,
22}
23
24#[derive(Clone, Debug)]
27pub struct PlacedRun {
28 pub font: FontId,
29 pub size: f32,
30 pub color: Color,
31 pub decoration: Option<crate::style::Decoration>,
32 pub shadows: Vec<crate::style::Shadow>,
33 pub glyphs: Vec<PlacedGlyph>,
34 pub rtl: bool,
38 pub bounds: Rect,
41 pub ink: Rect,
46}
47
48#[derive(Clone, Debug)]
49pub struct Line {
50 pub runs: Vec<PlacedRun>,
51 pub baseline: f32,
53 pub ascent: f32,
55 pub descent: f32,
56 pub left: f32,
58 pub width: f32,
60 pub range: Range<usize>,
62}
63
64#[derive(Clone, Debug)]
67pub(crate) struct Layout {
68 pub max_width: f32,
69 pub lines: Vec<Line>,
70 pub width: f32,
71 pub height: f32,
72 pub truncated: bool,
74 pub wrapped: Wrapped,
76}
77
78#[derive(Clone)]
90pub struct Paragraph {
91 faces: FaceSet,
92 text: String,
93 style: ParagraphStyle,
94 spans: Vec<(Range<usize>, TextStyle)>,
95 shaped: Vec<ShapedRun>,
96 layout: Option<Layout>,
97 empty_metrics: Option<(f32, f32, f32)>,
100 demand: FontDemand,
103}
104
105impl Paragraph {
106 pub fn layout(&mut self, max_width: f32) {
109 if self
110 .layout
111 .as_ref()
112 .is_some_and(|l| l.max_width == max_width)
113 {
114 return;
115 }
116 let bidi = BidiInfo::new(&self.text, base_level(&self.style));
117 let wrapped = wrap_lines(
118 &self.text,
119 &self.shaped,
120 max_width,
121 self.style.max_lines,
122 self.style.preserve_trailing_whitespace,
123 );
124 self.layout = Some(self.place(&bidi, wrapped, max_width));
125 }
126
127 pub fn update_color(&mut self, span: usize, color: Color) {
130 let Some((range, style)) = self.spans.get_mut(span) else {
131 return;
132 };
133 style.color = color;
134 let range = range.clone();
135 for run in &mut self.shaped {
136 if run.range.start >= range.start && run.range.end <= range.end {
137 run.color = color;
138 }
139 }
140 if let Some(prior) = self.layout.take() {
141 let bidi = BidiInfo::new(&self.text, base_level(&self.style));
142 self.layout = Some(self.place(&bidi, prior.wrapped, prior.max_width));
143 }
144 }
145
146 fn place(&self, bidi: &BidiInfo, wrapped: Wrapped, max_width: f32) -> Layout {
147 place_lines(
148 &self.faces,
149 &self.text,
150 &self.shaped,
151 bidi,
152 wrapped,
153 max_width,
154 &self.style,
155 self.empty_line_metrics(),
156 )
157 }
158
159 fn empty_line_metrics(&self) -> Option<(f32, f32, f32)> {
162 self.empty_metrics
163 }
164
165 pub fn lines(&self) -> &[Line] {
167 self.layout.as_ref().map_or(&[], |l| &l.lines)
168 }
169
170 pub fn width(&self) -> f32 {
172 self.layout.as_ref().map_or(0.0, |l| l.width)
173 }
174
175 pub fn advance(&self) -> f32 {
184 self.lines()
185 .iter()
186 .map(|line| line.width)
187 .reduce(f32::max)
188 .unwrap_or(0.0)
189 }
190
191 pub fn last_glyph_origin(&self) -> Option<f32> {
195 self.lines()
196 .iter()
197 .flat_map(|line| &line.runs)
198 .flat_map(|run| &run.glyphs)
199 .next_back()
200 .map(|glyph| glyph.x)
201 }
202
203 pub fn height(&self) -> f32 {
204 self.layout.as_ref().map_or(0.0, |l| l.height)
205 }
206
207 pub fn ink_bounds(&self) -> Option<Rect> {
212 let mut result: Option<Rect> = None;
213 let mut rasterizer = crate::raster::Rasterizer::new();
214 let mut color_bounds = HashMap::<(FontId, u32, u32), Option<Rect>>::new();
215 for run in self.lines().iter().flat_map(|line| &line.runs) {
216 let font = self.faces.get(run.font);
217 for glyph in &run.glyphs {
218 let key = (run.font, glyph.id, run.size.to_bits());
219 let color = *color_bounds
220 .entry(key)
221 .or_insert_with(|| rasterizer.color_bounds(font, glyph.id, run.size));
222 let bounds = if let Some(bounds) = color {
223 bounds
224 } else if let Some(path) = crate::raster::glyph_path(font, glyph.id, run.size) {
225 path.tight_bounds()
226 } else {
227 continue;
228 };
229 let placed = Rect::new(
230 bounds.x + glyph.x,
231 bounds.y + glyph.y,
232 bounds.width,
233 bounds.height,
234 );
235 result = Some(result.map_or(placed, |current| current.union(&placed)));
236 }
237 }
238 result
239 }
240
241 pub fn primary_font(&self) -> Option<(&crate::font::Font, f32)> {
243 if let Some(run) = self.lines().first().and_then(|line| line.runs.first()) {
244 return Some((self.faces.get(run.font), run.size));
245 }
246 let (_, style) = self.spans.first()?;
247 if self.faces.is_empty() {
248 return None;
249 }
250 let attributes = style.font_attrs();
251 let identifier = self.faces.resolve(&style.families, attributes, ' ');
252 Some((self.faces.get(identifier), style.size))
253 }
254
255 pub fn bounds(&self) -> Rect {
256 Rect::new(0.0, 0.0, self.width(), self.height())
257 }
258
259 pub fn truncated(&self) -> bool {
261 self.layout.as_ref().is_some_and(|l| l.truncated)
262 }
263
264 pub fn min_intrinsic_width(&self) -> f32 {
266 self.layout
267 .as_ref()
268 .map_or(0.0, |l| l.wrapped.min_intrinsic)
269 }
270
271 pub fn max_intrinsic_width(&self) -> f32 {
273 self.layout
274 .as_ref()
275 .map_or(0.0, |l| l.wrapped.max_intrinsic)
276 }
277
278 pub fn longest_line(&self) -> f32 {
280 self.width()
281 }
282}
283
284pub struct ParagraphBuilder<'a> {
287 fonts: &'a mut FontCollection,
288 style: ParagraphStyle,
289 text: String,
290 spans: Vec<(Range<usize>, TextStyle)>,
291}
292
293impl<'a> ParagraphBuilder<'a> {
294 pub fn new(fonts: &'a mut FontCollection) -> Self {
297 Self {
298 fonts,
299 style: ParagraphStyle::default(),
300 text: String::new(),
301 spans: Vec::new(),
302 }
303 }
304
305 pub fn style(&mut self, style: ParagraphStyle) -> &mut Self {
306 self.style = style;
307 self
308 }
309
310 pub fn add_text(&mut self, text: &str, style: &TextStyle) -> &mut Self {
311 let start = self.text.len();
312 self.text.push_str(text);
313 self.spans.push((start..self.text.len(), style.clone()));
314 self
315 }
316
317 pub fn build(&mut self) -> Paragraph {
322 let bidi = BidiInfo::new(&self.text, base_level(&self.style));
323 let mut demand = FontDemand::default();
324 let shaped = shape_runs(self.fonts, &self.text, &self.spans, &bidi, &mut demand);
325 let faces = self.fonts.faces().clone();
326 let empty_metrics = empty_line_metrics(&faces, self.spans.first());
327 Paragraph {
328 faces,
329 text: std::mem::take(&mut self.text),
330 style: std::mem::take(&mut self.style),
331 spans: std::mem::take(&mut self.spans),
332 shaped,
333 layout: None,
334 empty_metrics,
335 demand,
336 }
337 }
338}
339
340#[derive(Clone, Copy, Debug, PartialEq, Eq)]
346pub struct PositionWithAffinity {
347 pub offset: usize,
348 pub downstream: bool,
349}
350
351#[derive(Clone, Debug)]
353pub struct LineMetrics {
354 pub range: Range<usize>,
355 pub baseline: f32,
356 pub ascent: f32,
357 pub descent: f32,
358 pub left: f32,
359 pub width: f32,
360}
361
362impl Paragraph {
363 pub fn text(&self) -> &str {
364 &self.text
365 }
366
367 pub fn demand(&self) -> &FontDemand {
370 &self.demand
371 }
372
373 pub fn faces(&self) -> &FaceSet {
376 &self.faces
377 }
378
379 pub fn line_metrics(&self) -> Vec<LineMetrics> {
380 self.lines()
381 .iter()
382 .map(|line| LineMetrics {
383 range: line.range.clone(),
384 baseline: line.baseline,
385 ascent: line.ascent,
386 descent: line.descent,
387 left: line.left,
388 width: line.width,
389 })
390 .collect()
391 }
392
393 pub fn caret_for_offset(&self, offset: usize) -> Rect {
397 let Some(line) = self.line_for_offset(offset) else {
398 return Rect::default();
399 };
400 let x = caret_x(line, offset);
401 Rect::new(
402 x,
403 line.baseline - line.ascent,
404 0.0,
405 line.ascent + line.descent,
406 )
407 }
408
409 pub fn glyph_position_at(&self, p: valo_geometry::Point) -> PositionWithAffinity {
413 let Some(line) = self.line_at_y(p.y) else {
414 return PositionWithAffinity {
415 offset: 0,
416 downstream: true,
417 };
418 };
419 let mut best = PositionWithAffinity {
420 offset: line.range.start,
421 downstream: true,
422 };
423 let mut best_dx = f32::MAX;
424 for run in &line.runs {
425 for g in &run.glyphs {
426 let (lead_x, trail_x) = if run.rtl {
428 (g.x + g.advance, g.x)
429 } else {
430 (g.x, g.x + g.advance)
431 };
432 let leading = (p.x - lead_x).abs();
433 if leading < best_dx {
434 best_dx = leading;
435 best = PositionWithAffinity {
436 offset: g.cluster,
437 downstream: true,
438 };
439 }
440 let trailing = (p.x - trail_x).abs();
441 if trailing < best_dx {
442 best_dx = trailing;
443 best = PositionWithAffinity {
444 offset: self.cluster_end(g.cluster),
445 downstream: false,
446 };
447 }
448 }
449 }
450 best
451 }
452
453 pub fn rects_for_range(&self, range: Range<usize>) -> Vec<Rect> {
457 let mut out = Vec::new();
458 for line in self.lines() {
459 if range.end <= line.range.start || range.start >= line.range.end {
460 continue;
461 }
462 for run in &line.runs {
463 let cells: Vec<&PlacedGlyph> = run
464 .glyphs
465 .iter()
466 .filter(|g| g.cluster >= range.start && g.cluster < range.end)
467 .collect();
468 let Some(first) = cells.first() else {
469 continue;
470 };
471 let x0 = cells.iter().map(|g| g.x).fold(first.x, f32::min);
472 let x1 = cells
473 .iter()
474 .map(|g| g.x + g.advance)
475 .fold(first.x + first.advance, f32::max);
476 out.push(Rect::from_ltrb(
477 x0,
478 line.baseline - line.ascent,
479 x1,
480 line.baseline + line.descent,
481 ));
482 }
483 }
484 out
485 }
486
487 pub fn word_boundary(&self, offset: usize) -> Range<usize> {
489 use unicode_segmentation::UnicodeSegmentation;
490 for (start, word) in self.text.split_word_bound_indices() {
491 if offset < start + word.len() {
492 return start..start + word.len();
493 }
494 }
495 self.text.len()..self.text.len()
496 }
497
498 fn line_for_offset(&self, offset: usize) -> Option<&Line> {
499 let lines = self.lines();
500 lines
501 .iter()
502 .find(|l| l.range.contains(&offset))
503 .or(lines.last())
504 }
505
506 fn line_at_y(&self, y: f32) -> Option<&Line> {
507 let lines = self.lines();
508 lines
509 .iter()
510 .find(|l| y <= l.baseline + l.descent)
511 .or(lines.last())
512 }
513
514 fn cluster_end(&self, cluster: usize) -> usize {
518 let next_on_line = self
519 .line_for_offset(cluster)
520 .into_iter()
521 .flat_map(|l| l.runs.iter())
522 .flat_map(|r| r.glyphs.iter())
523 .map(|g| g.cluster)
524 .filter(|&c| c > cluster)
525 .min();
526 next_on_line.unwrap_or_else(|| self.next_grapheme(cluster))
527 }
528
529 fn next_grapheme(&self, offset: usize) -> usize {
530 use unicode_segmentation::UnicodeSegmentation;
531 self.text[offset..]
532 .graphemes(true)
533 .next()
534 .map_or(self.text.len(), |g| offset + g.len())
535 }
536}
537
538fn caret_x(line: &Line, offset: usize) -> f32 {
541 let mut before: Option<(usize, f32)> = None;
542 for run in &line.runs {
543 for g in &run.glyphs {
544 let (lead_x, trail_x) = if run.rtl {
545 (g.x + g.advance, g.x)
546 } else {
547 (g.x, g.x + g.advance)
548 };
549 if g.cluster == offset {
550 return lead_x;
551 }
552 if g.cluster < offset && before.is_none_or(|(c, _)| g.cluster > c) {
553 before = Some((g.cluster, trail_x));
554 }
555 }
556 }
557 before.map_or(line.left, |(_, x)| x)
558}
559
560fn base_level(style: &ParagraphStyle) -> Option<unicode_bidi::Level> {
562 style.direction.map(|direction| match direction {
563 TextDirection::Ltr => unicode_bidi::Level::ltr(),
564 TextDirection::Rtl => unicode_bidi::Level::rtl(),
565 })
566}
567
568fn empty_line_metrics(
572 faces: &FaceSet,
573 first_span: Option<&(std::ops::Range<usize>, TextStyle)>,
574) -> Option<(f32, f32, f32)> {
575 let (_, style) = first_span?;
576 if faces.is_empty() {
577 return None;
578 }
579 let attrs = style.font_attrs();
580 let id = faces.resolve(&style.families, attrs, ' ');
581 Some(crate::wrap::style_heights(
582 faces.get(id),
583 style.size,
584 style.height,
585 ))
586}