1use crate::{
2 App, Bounds, InlineLayout, LineLayout, PaintFragment, PaintQuad, Pixels, Point, Result,
3 ShapedTextLayout, SharedString, TextAlign, TextSystem, VisualLine, Window, point, size,
4};
5use derive_more::{Deref, DerefMut};
6use std::sync::{
7 Arc,
8 atomic::{AtomicUsize, Ordering},
9};
10
11static GLYPH_PAINT_ERRORS: AtomicUsize = AtomicUsize::new(0);
12const MAX_LOGGED_GLYPH_PAINT_ERRORS: usize = 20;
13
14#[derive(Clone, Debug, Deref, DerefMut)]
40pub struct ShapedLine {
41 #[deref]
42 #[deref_mut]
43 pub(crate) layout: Arc<LineLayout>,
44 pub text: SharedString,
46}
47
48impl ShapedLine {
49 #[allow(clippy::len_without_is_empty)]
51 pub fn len(&self) -> usize {
52 self.layout.len
53 }
54
55 pub fn width(&self) -> Pixels {
60 self.layout.width
61 }
62
63 pub fn paint(
78 &self,
79 origin: Point<Pixels>,
80 line_height: Pixels,
81 align: TextAlign,
82 align_width: Option<Pixels>,
83 window: &mut Window,
84 cx: &mut App,
85 ) -> Result<()> {
86 paint_visual_text(
87 origin,
88 &self.layout,
89 line_height,
90 align,
91 align_width,
92 &self.layout.visual_lines,
93 TextPaintPass::Foreground,
94 window,
95 cx,
96 )
97 }
98
99 pub fn paint_background(
104 &self,
105 origin: Point<Pixels>,
106 line_height: Pixels,
107 align: TextAlign,
108 align_width: Option<Pixels>,
109 window: &mut Window,
110 cx: &mut App,
111 ) -> Result<()> {
112 paint_visual_text(
113 origin,
114 &self.layout,
115 line_height,
116 align,
117 align_width,
118 &self.layout.visual_lines,
119 TextPaintPass::Background,
120 window,
121 cx,
122 )
123 }
124}
125
126#[derive(Debug, Deref, DerefMut)]
157pub struct ShapedText {
158 #[deref]
159 #[deref_mut]
160 pub(crate) layout: Arc<ShapedTextLayout>,
161 pub text: SharedString,
163}
164
165impl ShapedText {
166 #[allow(clippy::len_without_is_empty)]
168 pub fn len(&self) -> usize {
169 self.layout.len()
170 }
171
172 pub fn paint(
184 &self,
185 origin: Point<Pixels>,
186 line_height: Pixels,
187 align: TextAlign,
188 bounds: Option<Bounds<Pixels>>,
189 window: &mut Window,
190 cx: &mut App,
191 ) -> Result<()> {
192 let align_width = match bounds {
193 Some(bounds) => Some(bounds.size.width),
194 None => self.layout.wrap_width,
195 };
196
197 paint_visual_text(
198 origin,
199 &self.layout.layout,
200 line_height,
201 align,
202 align_width,
203 &self.layout.visual_lines,
204 TextPaintPass::Foreground,
205 window,
206 cx,
207 )
208 }
209
210 pub fn paint_background(
213 &self,
214 origin: Point<Pixels>,
215 line_height: Pixels,
216 align: TextAlign,
217 bounds: Option<Bounds<Pixels>>,
218 window: &mut Window,
219 cx: &mut App,
220 ) -> Result<()> {
221 let align_width = match bounds {
222 Some(bounds) => Some(bounds.size.width),
223 None => self.layout.wrap_width,
224 };
225
226 paint_visual_text(
227 origin,
228 &self.layout.layout,
229 line_height,
230 align,
231 align_width,
232 &self.layout.visual_lines,
233 TextPaintPass::Background,
234 window,
235 cx,
236 )
237 }
238}
239
240impl InlineLayout {
241 pub fn paint_background(
246 &self,
247 origin: Point<Pixels>,
248 window: &mut Window,
249 cx: &mut App,
250 ) -> Result<()> {
251 paint_inline_layout(self, origin, TextPaintPass::Background, window, cx)
252 }
253
254 pub fn paint(&self, origin: Point<Pixels>, window: &mut Window, cx: &mut App) -> Result<()> {
259 paint_inline_layout(self, origin, TextPaintPass::Foreground, window, cx)
260 }
261}
262
263#[derive(Clone, Copy, PartialEq)]
264enum TextPaintPass {
265 Background,
266 Foreground,
267}
268
269struct FragmentPaintContext<'a> {
270 line_origin: Point<Pixels>,
271 line_height: Pixels,
272 baseline_y: Pixels,
273 layout: &'a LineLayout,
274 pass: TextPaintPass,
275 text_system: &'a TextSystem,
276}
277
278fn paint_inline_layout(
279 inline: &InlineLayout,
280 origin: Point<Pixels>,
281 pass: TextPaintPass,
282 window: &mut Window,
283 cx: &mut App,
284) -> Result<()> {
285 if inline.lines.is_empty() {
286 return Ok(());
287 }
288
289 let text_system = cx.text_system().clone();
290 let placement = place_inline_layout(origin, inline.alignment_offset, window);
291 window.paint_layer(Bounds::new(placement.origin, inline.size), |window| {
292 for (line, visual_line) in inline.lines.iter().zip(&inline.layout.visual_lines) {
293 let line_origin = origin + line.origin + placement.delta;
294 paint_visual_line(
295 &inline.layout,
296 visual_line,
297 line_origin,
298 line.size.height,
299 line_origin.y + line.baseline,
300 pass,
301 &text_system,
302 window,
303 );
304 }
305
306 Ok(())
307 })
308}
309
310#[derive(Clone, Copy, Debug, PartialEq)]
311pub(crate) struct InlineLayoutPlacement {
312 pub(crate) origin: Point<Pixels>,
313 pub(crate) delta: Point<Pixels>,
314}
315
316pub(crate) fn place_inline_layout(
317 content_origin: Point<Pixels>,
318 alignment_offset: Pixels,
319 window: &Window,
320) -> InlineLayoutPlacement {
321 let anchor = content_origin + point(alignment_offset, Pixels::ZERO);
322 let placed_anchor = window.pixel_snap_point(anchor);
323 let delta = placed_anchor - anchor;
324
325 InlineLayoutPlacement {
326 origin: content_origin + delta,
327 delta,
328 }
329}
330
331fn paint_visual_line(
332 layout: &LineLayout,
333 visual_line: &VisualLine,
334 line_origin: Point<Pixels>,
335 line_height: Pixels,
336 baseline_y: Pixels,
337 pass: TextPaintPass,
338 text_system: &TextSystem,
339 window: &mut Window,
340) {
341 let context = FragmentPaintContext {
342 line_origin,
343 line_height,
344 baseline_y,
345 layout,
346 pass,
347 text_system,
348 };
349 for fragment in &layout.paint_fragments[visual_line.paint_fragment_range.clone()] {
350 paint_text_fragment(fragment, &context, window);
351 }
352}
353
354fn paint_text_fragment(
355 fragment: &PaintFragment,
356 context: &FragmentPaintContext<'_>,
357 window: &mut Window,
358) {
359 paint_fragment_decorations_at(
360 fragment,
361 context.line_origin,
362 context.line_height,
363 context.baseline_y,
364 context.layout,
365 window,
366 context.pass == TextPaintPass::Foreground,
367 );
368 if context.pass == TextPaintPass::Background {
369 return;
370 }
371
372 let max_glyph_size = context
373 .text_system
374 .bounding_box(fragment.font_id, fragment.font_size)
375 .size;
376 for glyph in &*fragment.glyphs {
377 let cull_origin = point(
378 context.line_origin.x + glyph.position.x,
379 context.line_origin.y,
380 );
381 if !Bounds::new(cull_origin, max_glyph_size).intersects(&window.content_mask().bounds) {
382 continue;
383 }
384
385 let glyph_origin = point(
386 context.line_origin.x + glyph.position.x,
387 context.baseline_y + glyph.position.y,
388 );
389 let result = if glyph.is_emoji {
390 window.paint_emoji_with_color(
391 glyph_origin,
392 fragment.font_id,
393 glyph.id,
394 fragment.font_size,
395 fragment.style.color,
396 )
397 } else {
398 window.paint_glyph(
399 glyph_origin,
400 fragment.font_id,
401 glyph.id,
402 fragment.font_size,
403 fragment.style.color,
404 )
405 };
406
407 if let Err(error) = result {
408 let error_idx = GLYPH_PAINT_ERRORS.fetch_add(1, Ordering::Relaxed);
409
410 if error_idx < MAX_LOGGED_GLYPH_PAINT_ERRORS {
411 log::error!(
412 "failed to paint glyph {:?} from font {:?}: {error:#}",
413 glyph.id,
414 fragment.font_id
415 );
416 } else if error_idx == MAX_LOGGED_GLYPH_PAINT_ERRORS {
417 log::error!("suppressing further glyph paint errors");
418 }
419 }
420 }
421}
422
423fn paint_visual_text(
424 origin: Point<Pixels>,
425 layout: &LineLayout,
426 line_height: Pixels,
427 align: TextAlign,
428 align_width: Option<Pixels>,
429 visual_lines: &[VisualLine],
430 pass: TextPaintPass,
431 window: &mut Window,
432 cx: &mut App,
433) -> Result<()> {
434 if visual_lines.is_empty() {
435 return Ok(());
436 }
437
438 let line_bounds = visual_text_bounds(origin, line_height, align, align_width, visual_lines);
439 window.paint_layer(line_bounds, |window| {
440 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
441 let text_system = cx.text_system().clone();
442
443 for (line_index, line) in visual_lines.iter().enumerate() {
444 let line_origin = point(
445 visual_line_origin_x(origin.x, align, align_width, line),
446 origin.y + line_index as f32 * line_height,
447 );
448 paint_visual_line(
449 layout,
450 line,
451 line_origin,
452 line_height,
453 line_origin.y + padding_top + layout.ascent,
454 pass,
455 &text_system,
456 window,
457 );
458 }
459
460 Ok(())
461 })
462}
463
464fn visual_text_bounds(
465 origin: Point<Pixels>,
466 line_height: Pixels,
467 align: TextAlign,
468 align_width: Option<Pixels>,
469 visual_lines: &[VisualLine],
470) -> Bounds<Pixels> {
471 let mut left = Pixels::MAX;
472 let mut right = Pixels::MIN;
473
474 for line in visual_lines {
475 let line_left = visual_line_origin_x(origin.x, align, align_width, line);
476 left = left.min(line_left);
477 right = right.max(line_left + line.advance_width);
478 }
479 Bounds::new(
480 point(left, origin.y),
481 size(right - left, line_height * visual_lines.len() as f32),
482 )
483}
484
485fn visual_line_origin_x(
486 origin_x: Pixels,
487 align: TextAlign,
488 align_width: Option<Pixels>,
489 line: &VisualLine,
490) -> Pixels {
491 line.offset
492 + aligned_visual_origin_x(
493 origin_x,
494 align_width.unwrap_or(line.advance_width),
495 line.advance_width,
496 align,
497 line.direction,
498 )
499}
500
501fn paint_fragment_decorations_at(
502 fragment: &PaintFragment,
503 line_origin: Point<Pixels>,
504 line_height: Pixels,
505 baseline_y: Pixels,
506 layout: &LineLayout,
507 window: &mut Window,
508 foreground: bool,
509) {
510 let range = line_origin.x + fragment.x_range.start..line_origin.x + fragment.x_range.end;
511 if foreground {
512 if let Some(mut underline) = fragment.style.underline {
513 underline.color = Some(underline.color.unwrap_or(fragment.style.color));
514 window.paint_underline(
515 point(
516 range.start,
517 baseline_y + fragment.underline_offset.unwrap_or(layout.descent * 0.618),
518 ),
519 range.end - range.start,
520 &underline,
521 );
522 }
523 if let Some(mut strike) = fragment.style.strikethrough {
524 strike.color = Some(strike.color.unwrap_or(fragment.style.color));
525 let strike_y = baseline_y
526 + fragment
527 .strikethrough_offset
528 .unwrap_or(-layout.ascent * 0.5);
529 window.paint_strikethrough(
530 point(range.start, strike_y),
531 range.end - range.start,
532 &strike,
533 );
534 }
535 } else if let Some(color) = fragment.style.background_color {
536 window.paint_quad(
537 PaintQuad::new(Bounds::new(
538 point(range.start, line_origin.y),
539 size(range.end - range.start, line_height),
540 ))
541 .background(color),
542 );
543 }
544}
545
546fn aligned_visual_origin_x(
547 origin_x: Pixels,
548 align_width: Pixels,
549 line_width: Pixels,
550 align: TextAlign,
551 direction: crate::ResolvedDirection,
552) -> Pixels {
553 match align {
554 TextAlign::Start if direction.is_rtl() => origin_x + align_width - line_width,
555 TextAlign::Start | TextAlign::Left => origin_x,
556 TextAlign::Center => (origin_x * 2.0 + align_width - line_width) / 2.0,
557 TextAlign::End if direction.is_rtl() => origin_x,
558 TextAlign::End | TextAlign::Right => origin_x + align_width - line_width,
559 }
560}