1use crate::{
2 App, Bounds, DevicePixels, Half, LineLayout, Pixels, Point, RenderGlyphParams, Result,
3 ShapedGlyph, ShapedRun, SharedString, StrikethroughStyle, TextAlign, UnderlineStyle, Window,
4 WrapBoundary, WrappedLineLayout, black, fill, point, px, size,
5};
6use derive_more::{Deref, DerefMut};
7use palette::Hsla;
8use smallvec::SmallVec;
9use std::sync::Arc;
10
11#[derive(Clone, Debug)]
16pub struct GlyphRasterData {
17 pub bounds: Vec<Bounds<DevicePixels>>,
19 pub params: Vec<RenderGlyphParams>,
21}
22
23#[derive(Debug, Clone)]
25pub struct DecorationRun {
26 pub len: u32,
28
29 pub color: Hsla,
31
32 pub background_color: Option<Hsla>,
34
35 pub underline: Option<UnderlineStyle>,
37
38 pub strikethrough: Option<StrikethroughStyle>,
40}
41
42#[derive(Clone, Default, Debug, Deref, DerefMut)]
44pub struct ShapedLine {
45 #[deref]
46 #[deref_mut]
47 pub(crate) layout: Arc<LineLayout>,
48 pub text: SharedString,
50 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
51}
52
53impl ShapedLine {
54 #[allow(clippy::len_without_is_empty)]
56 pub fn len(&self) -> usize {
57 self.layout.len
58 }
59
60 pub fn width(&self) -> Pixels {
65 self.layout.width
66 }
67
68 pub fn with_len(mut self, len: usize) -> Self {
71 let layout = self.layout.as_ref();
72 self.layout = Arc::new(LineLayout {
73 font_size: layout.font_size,
74 width: layout.width,
75 ascent: layout.ascent,
76 descent: layout.descent,
77 runs: layout.runs.clone(),
78 len,
79 });
80 self
81 }
82
83 pub fn paint(
85 &self,
86 origin: Point<Pixels>,
87 line_height: Pixels,
88 align: TextAlign,
89 align_width: Option<Pixels>,
90 window: &mut Window,
91 cx: &mut App,
92 ) -> Result<()> {
93 paint_line(
94 origin,
95 &self.layout,
96 line_height,
97 align,
98 align_width,
99 &self.decoration_runs,
100 &[],
101 window,
102 cx,
103 )?;
104
105 Ok(())
106 }
107
108 pub fn paint_background(
110 &self,
111 origin: Point<Pixels>,
112 line_height: Pixels,
113 align: TextAlign,
114 align_width: Option<Pixels>,
115 window: &mut Window,
116 cx: &mut App,
117 ) -> Result<()> {
118 paint_line_background(
119 origin,
120 &self.layout,
121 line_height,
122 align,
123 align_width,
124 &self.decoration_runs,
125 &[],
126 window,
127 cx,
128 )?;
129
130 Ok(())
131 }
132
133 pub fn split_at(&self, byte_index: usize) -> (ShapedLine, ShapedLine) {
143 let x_offset = self.layout.x_for_index(byte_index);
144
145 let mut left_runs = Vec::new();
147 let mut right_runs = Vec::new();
148
149 for run in &self.layout.runs {
150 let split_pos = run.glyphs.partition_point(|g| g.index < byte_index);
151
152 if split_pos > 0 {
153 left_runs.push(ShapedRun {
154 font_id: run.font_id,
155 glyphs: run.glyphs[..split_pos].to_vec(),
156 });
157 }
158
159 if split_pos < run.glyphs.len() {
160 let right_glyphs = run.glyphs[split_pos..]
161 .iter()
162 .map(|g| ShapedGlyph {
163 id: g.id,
164 position: point(g.position.x - x_offset, g.position.y),
165 index: g.index - byte_index,
166 is_emoji: g.is_emoji,
167 })
168 .collect();
169 right_runs.push(ShapedRun {
170 font_id: run.font_id,
171 glyphs: right_glyphs,
172 });
173 }
174 }
175
176 let mut left_decorations = SmallVec::new();
178 let mut right_decorations = SmallVec::new();
179 let mut decoration_offset = 0u32;
180 let split_point = byte_index as u32;
181
182 for decoration in &self.decoration_runs {
183 let run_end = decoration_offset + decoration.len;
184
185 if run_end <= split_point {
186 left_decorations.push(decoration.clone());
187 } else if decoration_offset >= split_point {
188 right_decorations.push(decoration.clone());
189 } else {
190 let left_len = split_point - decoration_offset;
191 let right_len = run_end - split_point;
192 left_decorations.push(DecorationRun {
193 len: left_len,
194 color: decoration.color,
195 background_color: decoration.background_color,
196 underline: decoration.underline,
197 strikethrough: decoration.strikethrough,
198 });
199 right_decorations.push(DecorationRun {
200 len: right_len,
201 color: decoration.color,
202 background_color: decoration.background_color,
203 underline: decoration.underline,
204 strikethrough: decoration.strikethrough,
205 });
206 }
207
208 decoration_offset = run_end;
209 }
210
211 let left_text = if byte_index == self.text.len() {
213 self.text.clone()
214 } else {
215 SharedString::new(&self.text[..byte_index])
216 };
217 let right_text = if byte_index == 0 {
218 self.text.clone()
219 } else {
220 SharedString::new(&self.text[byte_index..])
221 };
222
223 let left_width = x_offset;
224 let right_width = self.layout.width - left_width;
225
226 let left = ShapedLine {
227 layout: Arc::new(LineLayout {
228 font_size: self.layout.font_size,
229 width: left_width,
230 ascent: self.layout.ascent,
231 descent: self.layout.descent,
232 runs: left_runs,
233 len: byte_index,
234 }),
235 text: left_text,
236 decoration_runs: left_decorations,
237 };
238
239 let right = ShapedLine {
240 layout: Arc::new(LineLayout {
241 font_size: self.layout.font_size,
242 width: right_width,
243 ascent: self.layout.ascent,
244 descent: self.layout.descent,
245 runs: right_runs,
246 len: self.layout.len - byte_index,
247 }),
248 text: right_text,
249 decoration_runs: right_decorations,
250 };
251
252 (left, right)
253 }
254}
255
256#[derive(Default, Debug, Deref, DerefMut)]
258pub struct WrappedLine {
259 #[deref]
260 #[deref_mut]
261 pub(crate) layout: Arc<WrappedLineLayout>,
262 pub text: SharedString,
264 pub(crate) decoration_runs: Vec<DecorationRun>,
265}
266
267impl WrappedLine {
268 #[allow(clippy::len_without_is_empty)]
270 pub fn len(&self) -> usize {
271 self.layout.len()
272 }
273
274 pub fn paint(
276 &self,
277 origin: Point<Pixels>,
278 line_height: Pixels,
279 align: TextAlign,
280 bounds: Option<Bounds<Pixels>>,
281 window: &mut Window,
282 cx: &mut App,
283 ) -> Result<()> {
284 let align_width = match bounds {
285 Some(bounds) => Some(bounds.size.width),
286 None => self.layout.wrap_width,
287 };
288
289 paint_line(
290 origin,
291 &self.layout.unwrapped_layout,
292 line_height,
293 align,
294 align_width,
295 &self.decoration_runs,
296 &self.wrap_boundaries,
297 window,
298 cx,
299 )?;
300
301 Ok(())
302 }
303
304 pub fn paint_background(
306 &self,
307 origin: Point<Pixels>,
308 line_height: Pixels,
309 align: TextAlign,
310 bounds: Option<Bounds<Pixels>>,
311 window: &mut Window,
312 cx: &mut App,
313 ) -> Result<()> {
314 let align_width = match bounds {
315 Some(bounds) => Some(bounds.size.width),
316 None => self.layout.wrap_width,
317 };
318
319 paint_line_background(
320 origin,
321 &self.layout.unwrapped_layout,
322 line_height,
323 align,
324 align_width,
325 &self.decoration_runs,
326 &self.wrap_boundaries,
327 window,
328 cx,
329 )?;
330
331 Ok(())
332 }
333}
334
335fn paint_line(
336 origin: Point<Pixels>,
337 layout: &LineLayout,
338 line_height: Pixels,
339 align: TextAlign,
340 align_width: Option<Pixels>,
341 decoration_runs: &[DecorationRun],
342 wrap_boundaries: &[WrapBoundary],
343 window: &mut Window,
344 cx: &mut App,
345) -> Result<()> {
346 let line_bounds = Bounds::new(
347 origin,
348 size(
349 layout.width,
350 line_height * (wrap_boundaries.len() as f32 + 1.),
351 ),
352 );
353 window.paint_layer(line_bounds, |window| {
354 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
355 let baseline_offset = point(px(0.), padding_top + layout.ascent);
356 let mut decoration_runs = decoration_runs.iter();
357 let mut wraps = wrap_boundaries.iter().peekable();
358 let mut run_end = 0;
359 let mut color = black();
360 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
361 let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
362 let text_system = cx.text_system().clone();
363 let mut glyph_origin = point(
364 aligned_origin_x(
365 origin,
366 align_width.unwrap_or(layout.width),
367 px(0.0),
368 &align,
369 layout,
370 wraps.peek(),
371 ),
372 origin.y,
373 );
374 let mut prev_glyph_position = Point::default();
375 let mut max_glyph_size = size(px(0.), px(0.));
376 let mut first_glyph_x = origin.x;
377 for (run_ix, run) in layout.runs.iter().enumerate() {
378 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
379
380 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
381 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
382 if glyph_ix == 0 && run_ix == 0 {
383 first_glyph_x = glyph_origin.x;
384 }
385
386 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
387 wraps.next();
388 if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
389 if glyph_origin.x == underline_origin.x {
390 underline_origin.x -= max_glyph_size.width.half();
391 };
392 window.paint_underline(
393 *underline_origin,
394 glyph_origin.x - underline_origin.x,
395 underline_style,
396 );
397 if glyph.index < run_end {
398 underline_origin.x = origin.x;
399 underline_origin.y += line_height;
400 } else {
401 current_underline = None;
402 }
403 }
404 if let Some((strikethrough_origin, strikethrough_style)) =
405 current_strikethrough.as_mut()
406 {
407 if glyph_origin.x == strikethrough_origin.x {
408 strikethrough_origin.x -= max_glyph_size.width.half();
409 };
410 window.paint_strikethrough(
411 *strikethrough_origin,
412 glyph_origin.x - strikethrough_origin.x,
413 strikethrough_style,
414 );
415 if glyph.index < run_end {
416 strikethrough_origin.x = origin.x;
417 strikethrough_origin.y += line_height;
418 } else {
419 current_strikethrough = None;
420 }
421 }
422
423 glyph_origin.x = aligned_origin_x(
424 origin,
425 align_width.unwrap_or(layout.width),
426 glyph.position.x,
427 &align,
428 layout,
429 wraps.peek(),
430 );
431 glyph_origin.y += line_height;
432 }
433 prev_glyph_position = glyph.position;
434
435 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
436 let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
437 if glyph.index >= run_end {
438 let mut style_run = decoration_runs.next();
439
440 while let Some(run) = style_run {
442 if glyph.index < run_end + (run.len as usize) {
443 break;
444 }
445 run_end += run.len as usize;
446 style_run = decoration_runs.next();
447 }
448
449 if let Some(style_run) = style_run {
450 if let Some((_, underline_style)) = &mut current_underline
451 && style_run.underline.as_ref() != Some(underline_style)
452 {
453 finished_underline = current_underline.take();
454 }
455 if let Some(run_underline) = style_run.underline.as_ref() {
456 current_underline.get_or_insert((
457 point(
458 glyph_origin.x,
459 glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
460 ),
461 UnderlineStyle {
462 color: Some(run_underline.color.unwrap_or(style_run.color)),
463 thickness: run_underline.thickness,
464 wavy: run_underline.wavy,
465 },
466 ));
467 }
468 if let Some((_, strikethrough_style)) = &mut current_strikethrough
469 && style_run.strikethrough.as_ref() != Some(strikethrough_style)
470 {
471 finished_strikethrough = current_strikethrough.take();
472 }
473 if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
474 current_strikethrough.get_or_insert((
475 point(
476 glyph_origin.x,
477 glyph_origin.y
478 + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
479 ),
480 StrikethroughStyle {
481 color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
482 thickness: run_strikethrough.thickness,
483 },
484 ));
485 }
486
487 run_end += style_run.len as usize;
488 color = style_run.color;
489 } else {
490 run_end = layout.len;
491 finished_underline = current_underline.take();
492 finished_strikethrough = current_strikethrough.take();
493 }
494 }
495
496 if let Some((mut underline_origin, underline_style)) = finished_underline {
497 if underline_origin.x == glyph_origin.x {
498 underline_origin.x -= max_glyph_size.width.half();
499 };
500 window.paint_underline(
501 underline_origin,
502 glyph_origin.x - underline_origin.x,
503 &underline_style,
504 );
505 }
506
507 if let Some((mut strikethrough_origin, strikethrough_style)) =
508 finished_strikethrough
509 {
510 if strikethrough_origin.x == glyph_origin.x {
511 strikethrough_origin.x -= max_glyph_size.width.half();
512 };
513 window.paint_strikethrough(
514 strikethrough_origin,
515 glyph_origin.x - strikethrough_origin.x,
516 &strikethrough_style,
517 );
518 }
519
520 let max_glyph_bounds = Bounds {
521 origin: glyph_origin,
522 size: max_glyph_size,
523 };
524
525 let content_mask = window.content_mask();
526 if max_glyph_bounds.intersects(&content_mask.bounds) {
527 let vertical_offset = point(px(0.0), glyph.position.y);
528 if glyph.is_emoji {
529 window.paint_emoji(
530 glyph_origin + baseline_offset + vertical_offset,
531 run.font_id,
532 glyph.id,
533 layout.font_size,
534 )?;
535 } else {
536 window.paint_glyph(
537 glyph_origin + baseline_offset + vertical_offset,
538 run.font_id,
539 glyph.id,
540 layout.font_size,
541 color,
542 )?;
543 }
544 }
545 }
546 }
547
548 let mut last_line_end_x = first_glyph_x + layout.width;
549 if let Some(boundary) = wrap_boundaries.last() {
550 let run = &layout.runs[boundary.run_ix];
551 let glyph = &run.glyphs[boundary.glyph_ix];
552 last_line_end_x -= glyph.position.x;
553 }
554
555 if let Some((mut underline_start, underline_style)) = current_underline.take() {
556 if last_line_end_x == underline_start.x {
557 underline_start.x -= max_glyph_size.width.half()
558 };
559 window.paint_underline(
560 underline_start,
561 last_line_end_x - underline_start.x,
562 &underline_style,
563 );
564 }
565
566 if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
567 if last_line_end_x == strikethrough_start.x {
568 strikethrough_start.x -= max_glyph_size.width.half()
569 };
570 window.paint_strikethrough(
571 strikethrough_start,
572 last_line_end_x - strikethrough_start.x,
573 &strikethrough_style,
574 );
575 }
576
577 Ok(())
578 })
579}
580
581fn paint_line_background(
582 origin: Point<Pixels>,
583 layout: &LineLayout,
584 line_height: Pixels,
585 align: TextAlign,
586 align_width: Option<Pixels>,
587 decoration_runs: &[DecorationRun],
588 wrap_boundaries: &[WrapBoundary],
589 window: &mut Window,
590 cx: &mut App,
591) -> Result<()> {
592 let line_bounds = Bounds::new(
593 origin,
594 size(
595 layout.width,
596 line_height * (wrap_boundaries.len() as f32 + 1.),
597 ),
598 );
599 window.paint_layer(line_bounds, |window| {
600 let mut decoration_runs = decoration_runs.iter();
601 let mut wraps = wrap_boundaries.iter().peekable();
602 let mut run_end = 0;
603 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
604 let text_system = cx.text_system().clone();
605 let mut glyph_origin = point(
606 aligned_origin_x(
607 origin,
608 align_width.unwrap_or(layout.width),
609 px(0.0),
610 &align,
611 layout,
612 wraps.peek(),
613 ),
614 origin.y,
615 );
616 let mut prev_glyph_position = Point::default();
617 let mut max_glyph_size = size(px(0.), px(0.));
618 for (run_ix, run) in layout.runs.iter().enumerate() {
619 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
620
621 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
622 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
623
624 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
625 wraps.next();
626 if let Some((background_origin, background_color)) = current_background.as_mut()
627 {
628 if glyph_origin.x == background_origin.x {
629 background_origin.x -= max_glyph_size.width.half()
630 }
631 window.paint_quad(fill(
632 Bounds {
633 origin: *background_origin,
634 size: size(glyph_origin.x - background_origin.x, line_height),
635 },
636 *background_color,
637 ));
638 if glyph.index < run_end {
639 background_origin.x = origin.x;
640 background_origin.y += line_height;
641 } else {
642 current_background = None;
643 }
644 }
645
646 glyph_origin.x = aligned_origin_x(
647 origin,
648 align_width.unwrap_or(layout.width),
649 glyph.position.x,
650 &align,
651 layout,
652 wraps.peek(),
653 );
654 glyph_origin.y += line_height;
655 }
656 prev_glyph_position = glyph.position;
657
658 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
659 if glyph.index >= run_end {
660 let mut style_run = decoration_runs.next();
661
662 while let Some(run) = style_run {
664 if glyph.index < run_end + (run.len as usize) {
665 break;
666 }
667 run_end += run.len as usize;
668 style_run = decoration_runs.next();
669 }
670
671 if let Some(style_run) = style_run {
672 if let Some((_, background_color)) = &mut current_background
673 && style_run.background_color.as_ref() != Some(background_color)
674 {
675 finished_background = current_background.take();
676 }
677 if let Some(run_background) = style_run.background_color {
678 current_background.get_or_insert((
679 point(glyph_origin.x, glyph_origin.y),
680 run_background,
681 ));
682 }
683 run_end += style_run.len as usize;
684 } else {
685 run_end = layout.len;
686 finished_background = current_background.take();
687 }
688 }
689
690 if let Some((mut background_origin, background_color)) = finished_background {
691 let mut width = glyph_origin.x - background_origin.x;
692 if background_origin.x == glyph_origin.x {
693 background_origin.x -= max_glyph_size.width.half();
694 };
695 window.paint_quad(fill(
696 Bounds {
697 origin: background_origin,
698 size: size(width, line_height),
699 },
700 background_color,
701 ));
702 }
703 }
704 }
705
706 let mut last_line_end_x = origin.x + layout.width;
707 if let Some(boundary) = wrap_boundaries.last() {
708 let run = &layout.runs[boundary.run_ix];
709 let glyph = &run.glyphs[boundary.glyph_ix];
710 last_line_end_x -= glyph.position.x;
711 }
712
713 if let Some((mut background_origin, background_color)) = current_background.take() {
714 if last_line_end_x == background_origin.x {
715 background_origin.x -= max_glyph_size.width.half()
716 };
717 window.paint_quad(fill(
718 Bounds {
719 origin: background_origin,
720 size: size(last_line_end_x - background_origin.x, line_height),
721 },
722 background_color,
723 ));
724 }
725
726 Ok(())
727 })
728}
729
730fn aligned_origin_x(
731 origin: Point<Pixels>,
732 align_width: Pixels,
733 last_glyph_x: Pixels,
734 align: &TextAlign,
735 layout: &LineLayout,
736 wrap_boundary: Option<&&WrapBoundary>,
737) -> Pixels {
738 let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
739 layout.runs[*run_ix].glyphs[*glyph_ix].position.x
740 } else {
741 layout.width
742 };
743
744 let line_width = end_of_line - last_glyph_x;
745
746 match align {
747 TextAlign::Left => origin.x,
748 TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
749 TextAlign::Right => origin.x + align_width - line_width,
750 }
751}
752
753#[cfg(test)]
754mod tests {
755 use super::*;
756 use crate::{FontId, GlyphId};
757
758 fn make_shaped_line(
761 text: &str,
762 glyphs: &[(usize, f32)],
763 width: f32,
764 decorations: &[DecorationRun],
765 ) -> ShapedLine {
766 let shaped_glyphs: Vec<ShapedGlyph> = glyphs
767 .iter()
768 .map(|&(index, x)| ShapedGlyph {
769 id: GlyphId(0),
770 position: point(px(x), px(0.0)),
771 index,
772 is_emoji: false,
773 })
774 .collect();
775
776 ShapedLine {
777 layout: Arc::new(LineLayout {
778 font_size: px(16.0),
779 width: px(width),
780 ascent: px(12.0),
781 descent: px(4.0),
782 runs: vec![ShapedRun {
783 font_id: FontId(0),
784 glyphs: shaped_glyphs,
785 }],
786 len: text.len(),
787 }),
788 text: SharedString::new(text),
789 decoration_runs: SmallVec::from(decorations.to_vec()),
790 }
791 }
792
793 #[test]
794 fn test_split_at_invariants() {
795 let line = make_shaped_line(
797 "abcdef",
798 &[
799 (0, 0.0),
800 (1, 10.0),
801 (2, 20.0),
802 (3, 30.0),
803 (4, 40.0),
804 (5, 50.0),
805 ],
806 60.0,
807 &[],
808 );
809
810 for i in 0..=6 {
811 let (left, right) = line.split_at(i);
812
813 assert_eq!(
814 left.width() + right.width(),
815 line.width(),
816 "widths must sum at split={i}"
817 );
818 assert_eq!(
819 left.len() + right.len(),
820 line.len(),
821 "lengths must sum at split={i}"
822 );
823 assert_eq!(
824 format!("{}{}", left.text.as_ref(), right.text.as_ref()),
825 "abcdef",
826 "text must concatenate at split={i}"
827 );
828 assert_eq!(left.font_size, line.font_size, "font_size at split={i}");
829 assert_eq!(right.ascent, line.ascent, "ascent at split={i}");
830 assert_eq!(right.descent, line.descent, "descent at split={i}");
831 }
832
833 let (left, right) = line.split_at(0);
835 assert_eq!(left.runs.len(), 0);
836 assert_eq!(right.runs[0].glyphs.len(), 6);
837
838 let (left, right) = line.split_at(6);
840 assert_eq!(left.runs[0].glyphs.len(), 6);
841 assert_eq!(right.runs.len(), 0);
842 }
843
844 #[test]
845 fn test_split_at_glyph_rebasing() {
846 let line = ShapedLine {
851 layout: Arc::new(LineLayout {
852 font_size: px(16.0),
853 width: px(60.0),
854 ascent: px(12.0),
855 descent: px(4.0),
856 runs: vec![
857 ShapedRun {
858 font_id: FontId(0),
859 glyphs: vec![
860 ShapedGlyph {
861 id: GlyphId(0),
862 position: point(px(0.0), px(0.0)),
863 index: 0,
864 is_emoji: false,
865 },
866 ShapedGlyph {
867 id: GlyphId(0),
868 position: point(px(10.0), px(0.0)),
869 index: 1,
870 is_emoji: false,
871 },
872 ShapedGlyph {
873 id: GlyphId(0),
874 position: point(px(20.0), px(0.0)),
875 index: 2,
876 is_emoji: false,
877 },
878 ],
879 },
880 ShapedRun {
881 font_id: FontId(1),
882 glyphs: vec![
883 ShapedGlyph {
884 id: GlyphId(0),
885 position: point(px(30.0), px(0.0)),
886 index: 3,
887 is_emoji: false,
888 },
889 ShapedGlyph {
890 id: GlyphId(0),
891 position: point(px(40.0), px(0.0)),
892 index: 4,
893 is_emoji: false,
894 },
895 ShapedGlyph {
896 id: GlyphId(0),
897 position: point(px(50.0), px(0.0)),
898 index: 5,
899 is_emoji: false,
900 },
901 ],
902 },
903 ],
904 len: 6,
905 }),
906 text: "abcdef".into(),
907 decoration_runs: SmallVec::new(),
908 };
909
910 let (first, remainder) = line.split_at(2);
912 assert_eq!(first.text.as_ref(), "ab");
913 assert_eq!(first.runs.len(), 1);
914 assert_eq!(first.runs[0].font_id, FontId(0));
915
916 assert_eq!(remainder.text.as_ref(), "cdef");
918 assert_eq!(remainder.runs.len(), 2);
919 assert_eq!(remainder.runs[0].font_id, FontId(0));
920 assert_eq!(remainder.runs[0].glyphs.len(), 1);
921 assert_eq!(remainder.runs[0].glyphs[0].index, 0);
922 assert_eq!(remainder.runs[0].glyphs[0].position.x, px(0.0));
923 assert_eq!(remainder.runs[1].font_id, FontId(1));
924 assert_eq!(remainder.runs[1].glyphs[0].index, 1);
925 assert_eq!(remainder.runs[1].glyphs[0].position.x, px(10.0));
926
927 let (second, final_part) = remainder.split_at(2);
929 assert_eq!(second.text.as_ref(), "cd");
930 assert_eq!(final_part.text.as_ref(), "ef");
931 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
932 assert_eq!(final_part.runs[0].glyphs[0].position.x, px(0.0));
933
934 assert_eq!(
936 first.width() + second.width() + final_part.width(),
937 line.width()
938 );
939 }
940
941 #[test]
942 fn test_split_at_decorations() {
943 let red = Hsla::new(0., 1., 0.5, 1.);
946 let green = Hsla::new(108., 1., 0.5, 1.);
947 let blue = Hsla::new(216., 1., 0.5, 1.);
948
949 let line = make_shaped_line(
950 "abcdef",
951 &[
952 (0, 0.0),
953 (1, 10.0),
954 (2, 20.0),
955 (3, 30.0),
956 (4, 40.0),
957 (5, 50.0),
958 ],
959 60.0,
960 &[
961 DecorationRun {
962 len: 2,
963 color: red,
964 background_color: None,
965 underline: None,
966 strikethrough: None,
967 },
968 DecorationRun {
969 len: 3,
970 color: green,
971 background_color: None,
972 underline: None,
973 strikethrough: None,
974 },
975 DecorationRun {
976 len: 1,
977 color: blue,
978 background_color: None,
979 underline: None,
980 strikethrough: None,
981 },
982 ],
983 );
984
985 let (left, right) = line.split_at(3);
986
987 assert_eq!(left.decoration_runs.len(), 2);
989 assert_eq!(left.decoration_runs[0].len, 2);
990 assert_eq!(left.decoration_runs[0].color, red);
991 assert_eq!(left.decoration_runs[1].len, 1);
992 assert_eq!(left.decoration_runs[1].color, green);
993
994 assert_eq!(right.decoration_runs.len(), 2);
996 assert_eq!(right.decoration_runs[0].len, 2);
997 assert_eq!(right.decoration_runs[0].color, green);
998 assert_eq!(right.decoration_runs[1].len, 1);
999 assert_eq!(right.decoration_runs[1].color, blue);
1000 }
1001}