iced_graphics/text/
paragraph.rs

1//! Draw paragraphs.
2use crate::core;
3use crate::core::alignment;
4use crate::core::text::{Alignment, Hit, LineHeight, Shaping, Span, Text, Wrapping};
5use crate::core::{Font, Pixels, Point, Rectangle, Size};
6use crate::text;
7
8use std::fmt;
9use std::sync::{self, Arc};
10
11/// A bunch of text.
12#[derive(Clone, PartialEq)]
13pub struct Paragraph(Arc<Internal>);
14
15#[derive(Clone)]
16struct Internal {
17    buffer: cosmic_text::Buffer,
18    font: Font,
19    shaping: Shaping,
20    wrapping: Wrapping,
21    align_x: Alignment,
22    align_y: alignment::Vertical,
23    bounds: Size,
24    min_bounds: Size,
25    version: text::Version,
26}
27
28impl Paragraph {
29    /// Creates a new empty [`Paragraph`].
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Returns the buffer of the [`Paragraph`].
35    pub fn buffer(&self) -> &cosmic_text::Buffer {
36        &self.internal().buffer
37    }
38
39    /// Creates a [`Weak`] reference to the [`Paragraph`].
40    ///
41    /// This is useful to avoid cloning the [`Paragraph`] when
42    /// referential guarantees are unnecessary. For instance,
43    /// when creating a rendering tree.
44    pub fn downgrade(&self) -> Weak {
45        let paragraph = self.internal();
46
47        Weak {
48            raw: Arc::downgrade(paragraph),
49            min_bounds: paragraph.min_bounds,
50            align_x: paragraph.align_x,
51            align_y: paragraph.align_y,
52        }
53    }
54
55    fn internal(&self) -> &Arc<Internal> {
56        &self.0
57    }
58}
59
60impl core::text::Paragraph for Paragraph {
61    type Font = Font;
62
63    fn with_text(text: Text<&str>) -> Self {
64        log::trace!("Allocating plain paragraph: {}", text.content);
65
66        let mut font_system = text::font_system().write().expect("Write font system");
67
68        let mut buffer = cosmic_text::Buffer::new(
69            font_system.raw(),
70            cosmic_text::Metrics::new(
71                text.size.into(),
72                text.line_height.to_absolute(text.size).into(),
73            ),
74        );
75
76        buffer.set_size(
77            font_system.raw(),
78            Some(text.bounds.width),
79            Some(text.bounds.height),
80        );
81
82        buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
83
84        buffer.set_text(
85            font_system.raw(),
86            text.content,
87            &text::to_attributes(text.font),
88            text::to_shaping(text.shaping, text.content),
89            None,
90        );
91
92        let min_bounds = text::align(&mut buffer, font_system.raw(), text.align_x);
93
94        Self(Arc::new(Internal {
95            buffer,
96            font: text.font,
97            align_x: text.align_x,
98            align_y: text.align_y,
99            shaping: text.shaping,
100            wrapping: text.wrapping,
101            bounds: text.bounds,
102            min_bounds,
103            version: font_system.version(),
104        }))
105    }
106
107    fn with_spans<Link>(text: Text<&[Span<'_, Link>]>) -> Self {
108        log::trace!("Allocating rich paragraph: {} spans", text.content.len());
109
110        let mut font_system = text::font_system().write().expect("Write font system");
111
112        let mut buffer = cosmic_text::Buffer::new(
113            font_system.raw(),
114            cosmic_text::Metrics::new(
115                text.size.into(),
116                text.line_height.to_absolute(text.size).into(),
117            ),
118        );
119
120        buffer.set_size(
121            font_system.raw(),
122            Some(text.bounds.width),
123            Some(text.bounds.height),
124        );
125
126        buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
127
128        buffer.set_rich_text(
129            font_system.raw(),
130            text.content.iter().enumerate().map(|(i, span)| {
131                let attrs = text::to_attributes(span.font.unwrap_or(text.font));
132
133                let attrs = match (span.size, span.line_height) {
134                    (None, None) => attrs,
135                    _ => {
136                        let size = span.size.unwrap_or(text.size);
137
138                        attrs.metrics(cosmic_text::Metrics::new(
139                            size.into(),
140                            span.line_height
141                                .unwrap_or(text.line_height)
142                                .to_absolute(size)
143                                .into(),
144                        ))
145                    }
146                };
147
148                let attrs = if let Some(color) = span.color {
149                    attrs.color(text::to_color(color))
150                } else {
151                    attrs
152                };
153
154                (span.text.as_ref(), attrs.metadata(i))
155            }),
156            &text::to_attributes(text.font),
157            cosmic_text::Shaping::Advanced,
158            None,
159        );
160
161        let min_bounds = text::align(&mut buffer, font_system.raw(), text.align_x);
162
163        Self(Arc::new(Internal {
164            buffer,
165            font: text.font,
166            align_x: text.align_x,
167            align_y: text.align_y,
168            shaping: text.shaping,
169            wrapping: text.wrapping,
170            bounds: text.bounds,
171            min_bounds,
172            version: font_system.version(),
173        }))
174    }
175
176    fn resize(&mut self, new_bounds: Size) {
177        let paragraph = Arc::make_mut(&mut self.0);
178
179        let mut font_system = text::font_system().write().expect("Write font system");
180
181        paragraph.buffer.set_size(
182            font_system.raw(),
183            Some(new_bounds.width),
184            Some(new_bounds.height),
185        );
186
187        let min_bounds = text::align(&mut paragraph.buffer, font_system.raw(), paragraph.align_x);
188
189        paragraph.bounds = new_bounds;
190        paragraph.min_bounds = min_bounds;
191    }
192
193    fn compare(&self, text: Text<()>) -> core::text::Difference {
194        let font_system = text::font_system().read().expect("Read font system");
195        let paragraph = self.internal();
196        let metrics = paragraph.buffer.metrics();
197
198        if paragraph.version != font_system.version
199            || metrics.font_size != text.size.0
200            || metrics.line_height != text.line_height.to_absolute(text.size).0
201            || paragraph.font != text.font
202            || paragraph.shaping != text.shaping
203            || paragraph.wrapping != text.wrapping
204            || paragraph.align_x != text.align_x
205            || paragraph.align_y != text.align_y
206        {
207            core::text::Difference::Shape
208        } else if paragraph.bounds != text.bounds {
209            core::text::Difference::Bounds
210        } else {
211            core::text::Difference::None
212        }
213    }
214
215    fn size(&self) -> Pixels {
216        Pixels(self.0.buffer.metrics().font_size)
217    }
218
219    fn font(&self) -> Font {
220        self.0.font
221    }
222
223    fn line_height(&self) -> LineHeight {
224        LineHeight::Absolute(Pixels(self.0.buffer.metrics().line_height))
225    }
226
227    fn align_x(&self) -> Alignment {
228        self.internal().align_x
229    }
230
231    fn align_y(&self) -> alignment::Vertical {
232        self.internal().align_y
233    }
234
235    fn wrapping(&self) -> Wrapping {
236        self.0.wrapping
237    }
238
239    fn shaping(&self) -> Shaping {
240        self.0.shaping
241    }
242
243    fn bounds(&self) -> Size {
244        self.0.bounds
245    }
246
247    fn min_bounds(&self) -> Size {
248        self.internal().min_bounds
249    }
250
251    fn hit_test(&self, point: Point) -> Option<Hit> {
252        let cursor = self.internal().buffer.hit(point.x, point.y)?;
253
254        Some(Hit::CharOffset(cursor.index))
255    }
256
257    fn hit_span(&self, point: Point) -> Option<usize> {
258        let internal = self.internal();
259
260        let cursor = internal.buffer.hit(point.x, point.y)?;
261        let line = internal.buffer.lines.get(cursor.line)?;
262
263        if cursor.index >= line.text().len() {
264            return None;
265        }
266
267        let index = match cursor.affinity {
268            cosmic_text::Affinity::Before => cursor.index.saturating_sub(1),
269            cosmic_text::Affinity::After => cursor.index,
270        };
271
272        let mut hit = None;
273        let glyphs = line
274            .layout_opt()
275            .as_ref()?
276            .iter()
277            .flat_map(|line| line.glyphs.iter());
278
279        for glyph in glyphs {
280            if glyph.start <= index && index < glyph.end {
281                hit = Some(glyph);
282                break;
283            }
284        }
285
286        Some(hit?.metadata)
287    }
288
289    fn span_bounds(&self, index: usize) -> Vec<Rectangle> {
290        let internal = self.internal();
291
292        let mut bounds = Vec::new();
293        let mut current_bounds = None;
294
295        let glyphs = internal
296            .buffer
297            .layout_runs()
298            .flat_map(|run| {
299                let line_top = run.line_top;
300                let line_height = run.line_height;
301
302                run.glyphs
303                    .iter()
304                    .map(move |glyph| (line_top, line_height, glyph))
305            })
306            .skip_while(|(_, _, glyph)| glyph.metadata != index)
307            .take_while(|(_, _, glyph)| glyph.metadata == index);
308
309        for (line_top, line_height, glyph) in glyphs {
310            let y = line_top + glyph.y;
311
312            let new_bounds = || {
313                Rectangle::new(
314                    Point::new(glyph.x, y),
315                    Size::new(glyph.w, glyph.line_height_opt.unwrap_or(line_height)),
316                )
317            };
318
319            match current_bounds.as_mut() {
320                None => {
321                    current_bounds = Some(new_bounds());
322                }
323                Some(current_bounds) if y != current_bounds.y => {
324                    bounds.push(*current_bounds);
325                    *current_bounds = new_bounds();
326                }
327                Some(current_bounds) => {
328                    current_bounds.width += glyph.w;
329                }
330            }
331        }
332
333        bounds.extend(current_bounds);
334        bounds
335    }
336
337    fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> {
338        use unicode_segmentation::UnicodeSegmentation;
339
340        let run = self.internal().buffer.layout_runs().nth(line)?;
341
342        // index represents a grapheme, not a glyph
343        // Let's find the first glyph for the given grapheme cluster
344        let mut last_start = None;
345        let mut last_grapheme_count = 0;
346        let mut graphemes_seen = 0;
347
348        let glyph = run
349            .glyphs
350            .iter()
351            .find(|glyph| {
352                if Some(glyph.start) != last_start {
353                    last_grapheme_count = run.text[glyph.start..glyph.end].graphemes(false).count();
354                    last_start = Some(glyph.start);
355                    graphemes_seen += last_grapheme_count;
356                }
357
358                graphemes_seen >= index
359            })
360            .or_else(|| run.glyphs.last())?;
361
362        let advance = if index == 0 {
363            0.0
364        } else {
365            glyph.w
366                * (1.0
367                    - graphemes_seen.saturating_sub(index) as f32
368                        / last_grapheme_count.max(1) as f32)
369        };
370
371        Some(Point::new(
372            glyph.x + glyph.x_offset * glyph.font_size + advance,
373            glyph.y - glyph.y_offset * glyph.font_size,
374        ))
375    }
376}
377
378impl Default for Paragraph {
379    fn default() -> Self {
380        Self(Arc::new(Internal::default()))
381    }
382}
383
384impl fmt::Debug for Paragraph {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        let paragraph = self.internal();
387
388        f.debug_struct("Paragraph")
389            .field("font", &paragraph.font)
390            .field("shaping", &paragraph.shaping)
391            .field("horizontal_alignment", &paragraph.align_x)
392            .field("vertical_alignment", &paragraph.align_y)
393            .field("bounds", &paragraph.bounds)
394            .field("min_bounds", &paragraph.min_bounds)
395            .finish()
396    }
397}
398
399impl PartialEq for Internal {
400    fn eq(&self, other: &Self) -> bool {
401        self.font == other.font
402            && self.shaping == other.shaping
403            && self.align_x == other.align_x
404            && self.align_y == other.align_y
405            && self.bounds == other.bounds
406            && self.min_bounds == other.min_bounds
407            && self.buffer.metrics() == other.buffer.metrics()
408    }
409}
410
411impl Default for Internal {
412    fn default() -> Self {
413        Self {
414            buffer: cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
415                font_size: 1.0,
416                line_height: 1.0,
417            }),
418            font: Font::default(),
419            shaping: Shaping::default(),
420            wrapping: Wrapping::default(),
421            align_x: Alignment::Default,
422            align_y: alignment::Vertical::Top,
423            bounds: Size::ZERO,
424            min_bounds: Size::ZERO,
425            version: text::Version::default(),
426        }
427    }
428}
429
430/// A weak reference to a [`Paragraph`].
431#[derive(Debug, Clone)]
432pub struct Weak {
433    raw: sync::Weak<Internal>,
434    /// The minimum bounds of the [`Paragraph`].
435    pub min_bounds: Size,
436    /// The horizontal alignment of the [`Paragraph`].
437    pub align_x: Alignment,
438    /// The vertical alignment of the [`Paragraph`].
439    pub align_y: alignment::Vertical,
440}
441
442impl Weak {
443    /// Tries to update the reference into a [`Paragraph`].
444    pub fn upgrade(&self) -> Option<Paragraph> {
445        self.raw.upgrade().map(Paragraph)
446    }
447}
448
449impl PartialEq for Weak {
450    fn eq(&self, other: &Self) -> bool {
451        match (self.raw.upgrade(), other.raw.upgrade()) {
452            (Some(p1), Some(p2)) => p1 == p2,
453            _ => false,
454        }
455    }
456}