1use 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#[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 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn buffer(&self) -> &cosmic_text::Buffer {
36 &self.internal().buffer
37 }
38
39 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 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", ¶graph.font)
390 .field("shaping", ¶graph.shaping)
391 .field("horizontal_alignment", ¶graph.align_x)
392 .field("vertical_alignment", ¶graph.align_y)
393 .field("bounds", ¶graph.bounds)
394 .field("min_bounds", ¶graph.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#[derive(Debug, Clone)]
432pub struct Weak {
433 raw: sync::Weak<Internal>,
434 pub min_bounds: Size,
436 pub align_x: Alignment,
438 pub align_y: alignment::Vertical,
440}
441
442impl Weak {
443 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}