1use std::any::Any;
2use std::cell::{Cell, Ref, RefCell};
3use std::rc::Rc;
4
5use crate::media::text_formatting::bidi_reorderer::BidiReorderer;
6use crate::media::text_formatting::indexed_text_run::IndexedTextRun;
7use crate::media::text_formatting::unicode::Codepoint;
8use crate::media::text_formatting::{
9 DrawableTextRun, ITextDrawingSink, JustificationProperties, LogicalDirection, ShapedTextRun, TextBounds,
10 TextCollapsingProperties, TextLine, TextLineBreak, TextLineMetrics, TextParagraphProperties, TextRun,
11 TextRunBounds,
12};
13use crate::media::{BaselineAlignment, CharacterHit, FlowDirection, TextAlignment};
14use crate::utilities::MathUtilities;
15use crate::{Point, Rect, Vector};
16
17pub struct TextLineImpl {
23 indexed_text_runs: RefCell<Option<Rc<[IndexedTextRun]>>>,
24 text_runs: RefCell<Vec<Rc<dyn TextRun>>>,
25 paragraph_width: f64,
26 paragraph_properties: Rc<dyn TextParagraphProperties>,
27 text_line_metrics: Cell<TextLineMetrics>,
28 text_line_break: RefCell<Option<Rc<TextLineBreak>>>,
29 resolved_flow_direction: FlowDirection,
30 first_text_source_index: i32,
31 length: i32,
32 has_collapsed: bool,
33
34 ink_bounds: Cell<Rect>,
35 bounds: Cell<Rect>,
36}
37
38impl TextLineImpl {
39 #[allow(clippy::too_many_arguments)]
40 pub fn new(
41 text_runs: Vec<Rc<dyn TextRun>>,
42 first_text_source_index: i32,
43 length: i32,
44 paragraph_width: f64,
45 paragraph_properties: Rc<dyn TextParagraphProperties>,
46 resolved_flow_direction: FlowDirection,
47 line_break: Option<Rc<TextLineBreak>>,
48 has_collapsed: bool,
49 ) -> Rc<Self> {
50 Rc::new(Self {
51 indexed_text_runs: RefCell::new(None),
52 text_runs: RefCell::new(text_runs),
53 paragraph_width,
54 paragraph_properties,
55 text_line_metrics: Cell::new(TextLineMetrics::default()),
56 text_line_break: RefCell::new(line_break),
57 resolved_flow_direction,
58 first_text_source_index,
59 length,
60 has_collapsed,
61 ink_bounds: Cell::new(Rect::default()),
62 bounds: Cell::new(Rect::default()),
63 })
64 }
65
66 pub(crate) fn text_bounds_comparer(x: &TextBounds, y: &TextBounds) -> std::cmp::Ordering {
68 x.rectangle().left().total_cmp(&y.rectangle().left())
69 }
70
71 pub(crate) fn indexed_text_runs(&self) -> Option<Rc<[IndexedTextRun]>> {
73 self.indexed_text_runs.borrow().clone()
74 }
75
76 #[allow(dead_code)] pub(crate) fn bounds(&self) -> Rect {
79 self.bounds.get()
80 }
81
82 #[allow(dead_code)] pub(crate) fn ink_bounds(&self) -> Rect {
85 self.ink_bounds.get()
86 }
87
88 pub fn get_baseline_offset(text_line: &dyn TextLine, text_run: &dyn DrawableTextRun) -> f64 {
93 let baseline = text_run.baseline();
94 let baseline_alignment = text_run.properties().map(|properties| properties.baseline_alignment());
95
96 let mut baseline_offset = -baseline;
97
98 match baseline_alignment {
99 Some(BaselineAlignment::Baseline) => {
100 baseline_offset += text_line.baseline();
101 }
102 Some(BaselineAlignment::Top | BaselineAlignment::TextTop) => {
103 baseline_offset += text_line.height() - text_line.extent() + text_run.size().height / 2.0;
104 }
105 Some(BaselineAlignment::Center) => {
106 baseline_offset += text_line.height() / 2.0 + baseline - text_run.size().height / 2.0;
107 }
108 Some(BaselineAlignment::Subscript | BaselineAlignment::Bottom | BaselineAlignment::TextBottom) => {
109 baseline_offset += text_line.height() - text_run.size().height + baseline;
110 }
111 Some(BaselineAlignment::Superscript) => {
112 baseline_offset += baseline;
113 }
114 None => panic!("Specified argument was out of the range of valid values. (Parameter 'baselineAlignment')"),
115 }
116
117 baseline_offset
118 }
119
120 pub(crate) fn replace_text_run(&self, index: usize, text_run: Rc<dyn TextRun>) {
121 let old_run = self.text_runs.borrow()[index].clone();
122
123 if same_run(&old_run, &text_run) {
124 return;
125 }
126
127 self.text_runs.borrow_mut()[index] = text_run.clone();
128
129 let Some(indexed_text_runs) = self.indexed_text_runs() else {
130 return;
131 };
132
133 for indexed_text_run in indexed_text_runs.iter() {
134 let is_old_run =
135 indexed_text_run.text_run.borrow().as_ref().is_some_and(|current| same_run(current, &old_run));
136
137 if is_old_run {
138 *indexed_text_run.text_run.borrow_mut() = Some(text_run);
139
140 break;
141 }
142 }
143 }
144
145 fn get_run_character_hit(run: &dyn TextRun, current_position: i32, distance: f64) -> CharacterHit {
146 if let Some(shaped_run) = run.downcast_ref::<ShapedTextRun>() {
147 let glyph_run = shaped_run.glyph_run();
148
149 let (character_hit, _) = glyph_run.get_character_hit_from_distance(distance);
150
151 let offset = 0.max(current_position - glyph_run.metrics().first_cluster);
152
153 CharacterHit::with_trailing_length(
154 offset + character_hit.first_character_index(),
155 character_hit.trailing_length(),
156 )
157 } else if let Some(drawable_text_run) = run.as_drawable() {
158 if distance < drawable_text_run.size().width / 2.0 {
159 CharacterHit::new(current_position)
160 } else {
161 CharacterHit::with_trailing_length(current_position, run.length())
162 }
163 } else {
164 CharacterHit::with_trailing_length(current_position, run.length())
165 }
166 }
167
168 fn find_indexed_run(indexed_text_runs: &[IndexedTextRun], current_position: i32) -> usize {
169 let mut index = 0;
170
171 while indexed_text_runs[index].text_source_character_index != current_position {
172 if index + 1 == indexed_text_runs.len() {
173 break;
174 }
175
176 index += 1;
177 }
178
179 index
180 }
181
182 fn get_preceding_distance(&self, first_index: i32) -> f64 {
183 let text_runs = self.text_runs.borrow();
184
185 let mut distance = 0.0;
186
187 for current_run in text_runs.iter().take(first_index.max(0) as usize) {
188 if let Some(drawable_text_run) = current_run.as_drawable() {
189 distance += drawable_text_run.size().width;
190 }
191 }
192
193 distance
194 }
195
196 fn get_run_direction(text_run: Option<&dyn TextRun>, current_direction: FlowDirection) -> FlowDirection {
197 if let Some(shaped_text_run) = text_run.and_then(|text_run| text_run.downcast_ref::<ShapedTextRun>()) {
198 return if shaped_text_run.shaped_buffer().is_left_to_right() {
199 FlowDirection::LeftToRight
200 } else {
201 FlowDirection::RightToLeft
202 };
203 }
204
205 current_direction
206 }
207
208 fn get_run_text_source_position(&self, visual_run_index: usize) -> i32 {
209 if let Some(indexed_text_runs) = self.indexed_text_runs() {
210 for indexed_text_run in indexed_text_runs.iter() {
211 if indexed_text_run.run_index.get() == visual_run_index as i32 {
212 return indexed_text_run.text_source_character_index;
213 }
214 }
215 }
216
217 let text_runs = self.text_runs.borrow();
218
219 let mut position = self.first_text_source_index;
220
221 for text_run in text_runs.iter().take(visual_run_index) {
222 position += text_run.length();
223 }
224
225 position
226 }
227
228 fn get_last_directional_run_index(
229 indexed_text_runs: &[IndexedTextRun],
230 mut indexed_run_index: usize,
231 flow_direction: FlowDirection,
232 directional_width: &mut f64,
233 ) -> i32 {
234 let mut last_run_index = indexed_text_runs[indexed_run_index].run_index.get();
235
236 while indexed_run_index + 1 < indexed_text_runs.len() {
238 indexed_run_index += 1;
239
240 let next_indexed_run = &indexed_text_runs[indexed_run_index];
241
242 if next_indexed_run.run_index.get() != last_run_index + 1 {
243 break;
244 }
245
246 let next_run = next_indexed_run.text_run.borrow();
247
248 let Some(next_run) = next_run.as_ref() else {
249 break;
250 };
251
252 let next_direction = Self::get_run_direction(Some(&**next_run), flow_direction);
253
254 if next_direction != flow_direction {
255 break;
256 }
257
258 if let Some(next_drawable) = next_run.as_drawable() {
259 *directional_width += next_drawable.size().width;
260 }
261
262 last_run_index = next_indexed_run.run_index.get();
263 }
264
265 last_run_index
266 }
267
268 fn try_merge_with_last_bounds(current_bounds: TextBounds, last_bounds: &mut TextBounds) -> Option<TextBounds> {
271 if current_bounds.flow_direction() != last_bounds.flow_direction() {
272 return Some(current_bounds);
273 }
274
275 if MathUtilities::are_close(current_bounds.rectangle().left(), last_bounds.rectangle().right()) {
279 last_bounds.text_run_bounds_mut().extend(current_bounds.text_run_bounds().iter().cloned());
280
281 last_bounds.set_rectangle(last_bounds.rectangle().union(current_bounds.rectangle()));
282
283 return None;
284 }
285
286 if MathUtilities::are_close(current_bounds.rectangle().right(), last_bounds.rectangle().left()) {
287 for (i, run_bounds) in current_bounds.text_run_bounds().iter().enumerate() {
288 last_bounds.text_run_bounds_mut().insert(i, run_bounds.clone());
289 }
290
291 last_bounds.set_rectangle(last_bounds.rectangle().union(current_bounds.rectangle()));
292
293 return None;
294 }
295
296 Some(current_bounds)
297 }
298
299 fn get_previous_character_hit(&self, character_hit: CharacterHit, is_backspace_delete: bool) -> CharacterHit {
300 if self.text_runs.borrow().is_empty() || self.indexed_text_runs.borrow().is_none() {
301 return CharacterHit::default();
302 }
303
304 if character_hit.trailing_length() > 0 && character_hit.first_character_index() <= self.first_text_source_index {
305 return CharacterHit::new(self.first_text_source_index);
306 }
307
308 let character_index = character_hit.first_character_index() + character_hit.trailing_length();
309
310 if character_index <= self.first_text_source_index {
311 return CharacterHit::new(self.first_text_source_index);
312 }
313
314 let (current_run, current_position) =
315 self.get_run_at_character_index(character_index, LogicalDirection::Backward);
316
317 let mut previous_character_hit = character_hit;
318
319 if let Some(current_run) = ¤t_run {
320 if let Some(shaped_run) = current_run.downcast_ref::<ShapedTextRun>() {
321 let glyph_run = shaped_run.glyph_run();
322
323 let run_offset = 0.max(character_index - current_position);
325
326 let first_cluster = glyph_run.metrics().first_cluster;
327
328 let text_source_offset = current_position - first_cluster;
330
331 if is_backspace_delete {
332 let characters = glyph_run.characters();
333 let characters = characters.span();
334
335 let mut length = 0i32;
336
337 loop {
338 let (codepoint, count) = Codepoint::read_at(characters, length as usize);
339
340 if codepoint == Codepoint::REPLACEMENT_CODEPOINT {
341 break;
342 }
343
344 let mut count = count as i32;
345
346 if codepoint.value() == 0x0D {
347 let (next_codepoint, lf_count) = Codepoint::read_at(characters, (length + count) as usize);
348
349 if next_codepoint.value() == 0x0A {
350 count += lf_count as i32;
351 }
352 }
353
354 if length + count >= run_offset {
355 break;
356 }
357
358 length += count;
359 }
360
361 previous_character_hit = CharacterHit::new(character_index - run_offset + length);
362 } else {
363 previous_character_hit =
364 glyph_run.get_previous_caret_character_hit(CharacterHit::new(first_cluster + run_offset));
365
366 if text_source_offset > 0 {
367 previous_character_hit = CharacterHit::with_trailing_length(
368 text_source_offset + previous_character_hit.first_character_index(),
369 previous_character_hit.trailing_length(),
370 );
371 }
372 }
373 } else {
374 previous_character_hit = CharacterHit::new(current_position);
375 }
376 }
377
378 if character_index == previous_character_hit.first_character_index() + previous_character_hit.trailing_length() {
379 return character_hit;
380 }
381
382 previous_character_hit
383 }
384
385 #[allow(clippy::too_many_arguments)]
387 fn get_text_run_bounds_right_to_left(
388 &self,
389 first_run_index: i32,
390 last_run_index: i32,
391 mut end_x: f64,
392 first_text_source_index: i32,
393 mut current_position: i32,
394 mut remaining_length: i32,
395 ) -> (TextBounds, i32, i32) {
396 let text_runs = self.text_runs.borrow();
397 let height = self.height();
398 let line_end = self.first_text_source_index + self.length;
399
400 let mut covered_length = 0;
401 let mut text_run_bounds: Vec<TextRunBounds> = Vec::new();
402 let mut start_x = end_x;
403
404 let mut i = last_run_index;
405
406 while i >= first_run_index {
407 let current_run = &text_runs[i as usize];
408
409 if let Some(shaped_text_run) = current_run.downcast_ref::<ShapedTextRun>() {
410 let run_bounds = self.get_run_bounds(
411 current_run,
412 shaped_text_run,
413 start_x,
414 first_text_source_index,
415 remaining_length,
416 current_position,
417 );
418
419 let run_bounds_rectangle = run_bounds.rectangle();
420 let run_bounds_index = run_bounds.text_source_character_index();
421 let run_bounds_length = run_bounds.length();
422
423 if run_bounds_index < line_end {
424 text_run_bounds.insert(0, run_bounds);
425 }
426
427 if i == last_run_index {
428 end_x = run_bounds_rectangle.right();
429
430 start_x = end_x;
431 }
432
433 start_x -= run_bounds_rectangle.width;
434
435 current_position = run_bounds_index + run_bounds_length;
436
437 covered_length += run_bounds_length;
438
439 remaining_length -= run_bounds_length;
440 } else {
441 if current_position < line_end {
442 if let Some(drawable_text_run) = current_run.as_drawable() {
443 start_x -= drawable_text_run.size().width;
444
445 let run_bounds = TextRunBounds::new(
446 Rect::new(start_x, 0.0, drawable_text_run.size().width, height),
447 current_position,
448 current_run.length(),
449 current_run.clone(),
450 );
451
452 text_run_bounds.insert(0, run_bounds);
453 } else {
454 let run_bounds = TextRunBounds::new(
456 Rect::new(end_x, 0.0, 0.0, height),
457 current_position,
458 current_run.length(),
459 current_run.clone(),
460 );
461
462 text_run_bounds.push(run_bounds);
463 }
464 }
465
466 current_position += current_run.length();
467
468 covered_length += current_run.length();
469
470 remaining_length -= current_run.length();
471 }
472
473 if remaining_length <= 0 {
474 break;
475 }
476
477 i -= 1;
478 }
479
480 let run_width = end_x - start_x;
481
482 let bounds = Rect::new(start_x, 0.0, run_width, height);
483
484 (TextBounds::new(bounds, FlowDirection::RightToLeft, text_run_bounds), covered_length, current_position)
485 }
486
487 #[allow(clippy::too_many_arguments)]
489 fn get_text_bounds_left_to_right(
490 &self,
491 first_run_index: i32,
492 last_run_index: i32,
493 mut start_x: f64,
494 first_text_source_index: i32,
495 mut current_position: i32,
496 mut remaining_length: i32,
497 ) -> (TextBounds, i32, i32) {
498 let text_runs = self.text_runs.borrow();
499 let height = self.height();
500 let line_end = self.first_text_source_index + self.length;
501
502 let mut covered_length = 0;
503 let mut text_run_bounds: Vec<TextRunBounds> = Vec::with_capacity(1);
504 let mut end_x = start_x;
505
506 let mut i = first_run_index;
507
508 while i <= last_run_index {
509 let current_run = &text_runs[i as usize];
510
511 if let Some(shaped_text_run) = current_run.downcast_ref::<ShapedTextRun>() {
512 let run_bounds = self.get_run_bounds(
513 current_run,
514 shaped_text_run,
515 end_x,
516 first_text_source_index,
517 remaining_length,
518 current_position,
519 );
520
521 let run_bounds_rectangle = run_bounds.rectangle();
522 let run_bounds_index = run_bounds.text_source_character_index();
523 let run_bounds_length = run_bounds.length();
524
525 if run_bounds_index < line_end {
526 text_run_bounds.push(run_bounds);
527 }
528
529 current_position = run_bounds_index + run_bounds_length;
530
531 if i == first_run_index {
532 start_x = run_bounds_rectangle.left();
533 }
534
535 end_x = run_bounds_rectangle.right();
536
537 covered_length += run_bounds_length;
538
539 remaining_length -= run_bounds_length;
540 } else {
541 if current_position < line_end {
542 if let Some(drawable_text_run) = current_run.as_drawable() {
543 let run_bounds = TextRunBounds::new(
544 Rect::new(end_x, 0.0, drawable_text_run.size().width, height),
545 current_position,
546 current_run.length(),
547 current_run.clone(),
548 );
549
550 text_run_bounds.push(run_bounds);
551
552 end_x += drawable_text_run.size().width;
553 } else {
554 let run_bounds = TextRunBounds::new(
556 Rect::new(end_x, 0.0, 0.0, height),
557 current_position,
558 current_run.length(),
559 current_run.clone(),
560 );
561
562 text_run_bounds.push(run_bounds);
563 }
564 }
565
566 current_position += current_run.length();
567
568 covered_length += current_run.length();
569
570 remaining_length -= current_run.length();
571 }
572
573 if remaining_length <= 0 {
574 break;
575 }
576
577 i += 1;
578 }
579
580 let run_width = end_x - start_x;
581
582 let bounds = Rect::new(start_x, 0.0, run_width, height);
583
584 (TextBounds::new(bounds, FlowDirection::LeftToRight, text_run_bounds), covered_length, current_position)
585 }
586
587 fn get_run_bounds(
588 &self,
589 text_run: &Rc<dyn TextRun>,
590 current_run: &ShapedTextRun,
591 current_x: f64,
592 first_text_source_index: i32,
593 remaining_length: i32,
594 current_position: i32,
595 ) -> TextRunBounds {
596 let is_left_to_right = current_run.bidi_level() % 2 == 0;
597 let glyph_run = current_run.glyph_run();
598
599 let mut start_x = current_x;
600 let mut end_x = current_x;
601
602 let run_offset = 0.max(first_text_source_index - current_position);
604 let first_cluster = glyph_run.metrics().first_cluster;
605
606 let mut start_index = first_cluster + run_offset;
608 let end_index = start_index + remaining_length;
609
610 let text_source_offset = current_position - first_cluster;
612
613 debug_assert!(text_source_offset >= 0);
614
615 let mut cluster_offset = 0;
616
617 if run_offset > 0 {
619 let (character_hit, _) = glyph_run.find_nearest_character_hit(start_index);
620 let cluster_start = character_hit.first_character_index();
621 let cluster_end = cluster_start + character_hit.trailing_length();
622
623 if cluster_start < start_index && cluster_end > start_index {
624 cluster_offset = start_index - cluster_start;
626
627 start_index -= cluster_offset;
629 }
630 }
631
632 let start_offset = glyph_run.get_distance_from_character_hit(CharacterHit::new(start_index));
634 let end_offset = glyph_run.get_distance_from_character_hit(CharacterHit::new(end_index));
635
636 if is_left_to_right {
637 end_x = start_x + end_offset;
638 start_x += start_offset;
639 } else {
640 let run_width = current_run.size().width;
643
644 start_x -= run_width - start_offset;
645 end_x -= run_width - end_offset;
646 }
647
648 let (start_hit, _) = glyph_run.find_nearest_character_hit(start_index);
650 let mut start_hit_index = start_hit.first_character_index();
651
652 if start_hit_index < start_index {
654 start_hit_index += start_hit.trailing_length();
655 }
656
657 let (nearest_end_hit, _) = glyph_run.find_nearest_character_hit(end_index);
659
660 let end_hit_index = if nearest_end_hit.first_character_index() < end_index {
661 nearest_end_hit.first_character_index() + nearest_end_hit.trailing_length()
663 } else {
664 nearest_end_hit.first_character_index()
666 };
667
668 let covered_length = 0.max((start_hit_index - end_hit_index).abs() - cluster_offset);
669
670 if end_x < start_x {
672 std::mem::swap(&mut end_x, &mut start_x);
673 }
674
675 let run_width = end_x - start_x;
676
677 let text_source_index = text_source_offset + start_hit_index + cluster_offset;
679
680 TextRunBounds::new(
681 Rect::new(start_x, 0.0, run_width, self.height()),
682 text_source_index,
683 covered_length,
684 text_run.clone(),
685 )
686 }
687
688 pub fn finalize_line(&self) {
689 let indexed_text_runs = {
690 let mut text_runs = self.text_runs.borrow_mut();
691
692 BidiReorderer::bidi_reorder(
693 &mut text_runs,
694 self.paragraph_properties.flow_direction(),
695 self.first_text_source_index,
696 )
697 };
698
699 *self.indexed_text_runs.borrow_mut() = Some(indexed_text_runs);
700
701 self.text_line_metrics.set(self.create_line_metrics());
702
703 if self.text_line_break.borrow().is_none() {
704 let text_runs = self.text_runs.borrow();
705
706 if text_runs.len() > 1 {
707 let last_run = &text_runs[text_runs.len() - 1];
708
709 if last_run.as_text_end_of_line().is_some() {
710 *self.text_line_break.borrow_mut() =
711 Some(Rc::new(TextLineBreak::new(Some(last_run.clone()), FlowDirection::LeftToRight, false)));
712 }
713 }
714 }
715 }
716
717 fn get_run_at_character_index(
719 &self,
720 codepoint_index: i32,
721 direction: LogicalDirection,
722 ) -> (Option<Rc<dyn TextRun>>, i32) {
723 let mut run_index = 0usize;
724 let mut text_position = self.first_text_source_index;
725
726 let Some(indexed_text_runs) = self.indexed_text_runs() else {
727 return (None, text_position);
728 };
729
730 let text_runs_length = self.text_runs.borrow().len();
731
732 let mut current_run: Option<Rc<dyn TextRun>> = None;
733
734 while run_index < indexed_text_runs.len() {
735 let indexed_run = &indexed_text_runs[run_index];
736 current_run = indexed_run.text_run.borrow().clone();
737
738 if let Some(run) = ¤t_run {
739 if let Some(shaped_run) = run.downcast_ref::<ShapedTextRun>() {
740 let mut first_cluster = shaped_run.glyph_run().metrics().first_cluster;
741
742 first_cluster += 0.max(indexed_run.text_source_character_index - first_cluster);
743
744 if direction == LogicalDirection::Forward {
745 if codepoint_index >= first_cluster && codepoint_index < first_cluster + run.length() {
746 return (current_run, text_position);
747 }
748 } else if codepoint_index > first_cluster && codepoint_index <= first_cluster + run.length() {
749 return (current_run, text_position);
750 }
751 } else if direction == LogicalDirection::Forward {
752 if text_position == codepoint_index {
753 return (current_run, text_position);
754 }
755 } else if text_position + run.length() == codepoint_index {
756 return (current_run, text_position);
757 }
758
759 if run_index + 1 >= text_runs_length {
760 return (current_run, text_position);
761 }
762
763 text_position += run.length();
764 }
765
766 run_index += 1;
767 }
768
769 (current_run, text_position)
770 }
771
772 fn create_line_metrics(&self) -> TextLineMetrics {
773 let text_runs = self.text_runs.borrow();
774 let default_text_run_properties = self.paragraph_properties.default_text_run_properties();
775
776 let font_metrics = default_text_run_properties.cached_glyph_typeface().metrics();
777 let font_rendering_em_size = default_text_run_properties.font_rendering_em_size();
778 let scale = font_rendering_em_size / font_metrics.design_em_height as f64;
779 let mut width_including_whitespace = 0f64;
780 let mut trailing_whitespace_length = 0;
781 let mut new_line_length = 0;
782 let mut ascent = font_metrics.ascent as f64 * scale;
783 let mut descent = font_metrics.descent as f64 * scale;
784 let mut line_gap = font_metrics.line_gap as f64 * scale;
785
786 let line_height = self.paragraph_properties.line_height();
787 let line_spacing = self.paragraph_properties.line_spacing();
788
789 for run in text_runs.iter() {
790 if let Some(text_run) = run.downcast_ref::<ShapedTextRun>() {
791 let text_metrics = text_run.text_metrics();
792
793 if ascent > text_metrics.ascent {
794 ascent = text_metrics.ascent;
795 }
796
797 if descent < text_metrics.descent {
798 descent = text_metrics.descent;
799 }
800
801 if line_gap < text_metrics.line_gap {
802 line_gap = text_metrics.line_gap;
803 }
804 } else if let Some(drawable_text_run) = run.as_drawable() {
805 if drawable_text_run.baseline() > -ascent {
806 ascent = -drawable_text_run.baseline();
807 }
808
809 let bottom = drawable_text_run.size().height - drawable_text_run.baseline();
810
811 if bottom > descent {
812 descent = bottom;
813 }
814 }
815 }
816
817 let mut ink_bounds = Rect::default();
818
819 for run in text_runs.iter() {
820 if let Some(text_run) = run.downcast_ref::<ShapedTextRun>() {
821 let glyph_run = text_run.glyph_run();
822 let offset_y = -ascent - text_run.baseline();
824
825 let run_bounds = glyph_run.ink_bounds().translate(Vector::new(width_including_whitespace, offset_y));
826
827 ink_bounds = ink_bounds.union(run_bounds);
828
829 width_including_whitespace += text_run.size().width;
830 } else if let Some(drawable_text_run) = run.as_drawable() {
831 let offset_y = -ascent - drawable_text_run.baseline();
833
834 ink_bounds = ink_bounds.union(Rect::from_position_size(
835 Point::new(width_including_whitespace, offset_y),
836 drawable_text_run.size(),
837 ));
838
839 width_including_whitespace += drawable_text_run.size().width;
840 }
841 }
842
843 let half_line_gap = line_gap * 0.5;
844 let natural_height = descent - ascent + line_gap;
845 let mut baseline = -ascent + half_line_gap;
846 let mut height = natural_height;
847
848 if !line_height.is_nan() && !MathUtilities::is_zero(line_height) {
849 if line_height <= natural_height {
850 height = line_height;
852 baseline = -ascent;
853 } else {
854 height = line_height;
856 let extra = line_height - (descent - ascent);
857 baseline = -ascent + extra / 2.0;
858 }
859 }
860
861 height += line_spacing;
862
863 let mut width = width_including_whitespace;
864
865 let is_rtl = self.paragraph_properties.flow_direction() == FlowDirection::RightToLeft;
866
867 for i in 0..text_runs.len() {
868 let index = if is_rtl { i } else { text_runs.len() - 1 - i };
869 let current_run = &text_runs[index];
870
871 if let Some(shaped_text) = current_run.downcast_ref::<ShapedTextRun>() {
872 let glyph_run = shaped_text.glyph_run();
873 let glyph_run_metrics = glyph_run.metrics();
874
875 new_line_length += glyph_run_metrics.new_line_length;
876
877 if glyph_run_metrics.trailing_whitespace_length == 0 {
878 break;
879 }
880
881 trailing_whitespace_length += glyph_run_metrics.trailing_whitespace_length;
882
883 let whitespace_width = glyph_run.bounds().width - glyph_run_metrics.width;
884
885 width -= whitespace_width;
886
887 if glyph_run_metrics.trailing_whitespace_length != current_run.length() {
888 break;
893 }
894 }
895 }
896
897 let extent = ink_bounds.height;
898 let overhang_after = ink_bounds.bottom() - height + half_line_gap;
900 let overhang_leading = ink_bounds.left();
902 let overhang_trailing = width_including_whitespace - ink_bounds.right();
904 let has_overflowed = MathUtilities::greater_than(width, self.paragraph_width);
905
906 let start = self.get_paragraph_offset_x(width, width_including_whitespace);
907
908 self.ink_bounds.set(ink_bounds.translate(Vector::new(start, 0.0)));
909
910 self.bounds.set(Rect::new(start, 0.0, width_including_whitespace, height));
911
912 TextLineMetrics {
913 has_overflowed,
914 height,
915 extent,
916 newline_length: new_line_length,
917 start,
918 text_baseline: baseline,
919 trailing_whitespace_length,
920 width,
921 width_including_trailing_whitespace: width_including_whitespace,
922 overhang_leading,
923 overhang_trailing,
924 overhang_after,
925 }
926 }
927
928 fn get_paragraph_offset_x(&self, width: f64, width_including_trailing_whitespace: f64) -> f64 {
933 if self.paragraph_width == f64::INFINITY {
934 return 0.0;
935 }
936
937 let mut text_alignment = self.paragraph_properties.text_alignment();
938 let paragraph_flow_direction = self.paragraph_properties.flow_direction();
939
940 if text_alignment == TextAlignment::Justify {
941 text_alignment = TextAlignment::Start;
942 }
943
944 match text_alignment {
945 TextAlignment::Start => {
946 text_alignment = if paragraph_flow_direction == FlowDirection::LeftToRight {
947 TextAlignment::Left
948 } else {
949 TextAlignment::Right
950 };
951 }
952 TextAlignment::End => {
953 text_alignment = if paragraph_flow_direction == FlowDirection::RightToLeft {
954 TextAlignment::Left
955 } else {
956 TextAlignment::Right
957 };
958 }
959 TextAlignment::DetectFromContent => {
960 text_alignment = if self.resolved_flow_direction == FlowDirection::LeftToRight {
961 TextAlignment::Left
962 } else {
963 TextAlignment::Right
964 };
965 }
966 _ => {}
967 }
968
969 match text_alignment {
970 TextAlignment::Center => {
971 let mut start = (self.paragraph_width - width) / 2.0;
972
973 if paragraph_flow_direction == FlowDirection::RightToLeft {
974 start -= width_including_trailing_whitespace - width;
975 }
976
977 start.max(0.0)
978 }
979 TextAlignment::Right => (self.paragraph_width - width_including_trailing_whitespace).max(0.0),
980 _ => 0.0,
981 }
982 }
983}
984
985fn same_run(x: &Rc<dyn TextRun>, y: &Rc<dyn TextRun>) -> bool {
987 std::ptr::addr_eq(Rc::as_ptr(x), Rc::as_ptr(y))
988}
989
990impl TextLine for TextLineImpl {
991 fn text_runs(&self) -> Ref<'_, [Rc<dyn TextRun>]> {
992 Ref::map(self.text_runs.borrow(), |text_runs| text_runs.as_slice())
993 }
994
995 fn first_text_source_index(&self) -> i32 {
996 self.first_text_source_index
997 }
998
999 fn length(&self) -> i32 {
1000 self.length
1001 }
1002
1003 fn text_line_break(&self) -> Option<Rc<TextLineBreak>> {
1004 self.text_line_break.borrow().clone()
1005 }
1006
1007 fn has_collapsed(&self) -> bool {
1008 self.has_collapsed
1009 }
1010
1011 fn has_overflowed(&self) -> bool {
1012 self.text_line_metrics.get().has_overflowed
1013 }
1014
1015 fn baseline(&self) -> f64 {
1016 self.text_line_metrics.get().text_baseline
1017 }
1018
1019 fn extent(&self) -> f64 {
1020 self.text_line_metrics.get().extent
1021 }
1022
1023 fn height(&self) -> f64 {
1024 self.text_line_metrics.get().height
1025 }
1026
1027 fn new_line_length(&self) -> i32 {
1028 self.text_line_metrics.get().newline_length
1029 }
1030
1031 fn overhang_after(&self) -> f64 {
1032 self.text_line_metrics.get().overhang_after
1033 }
1034
1035 fn overhang_leading(&self) -> f64 {
1036 self.text_line_metrics.get().overhang_leading
1037 }
1038
1039 fn overhang_trailing(&self) -> f64 {
1040 self.text_line_metrics.get().overhang_trailing
1041 }
1042
1043 fn trailing_whitespace_length(&self) -> i32 {
1044 self.text_line_metrics.get().trailing_whitespace_length
1045 }
1046
1047 fn start(&self) -> f64 {
1048 self.text_line_metrics.get().start
1049 }
1050
1051 fn width(&self) -> f64 {
1052 self.text_line_metrics.get().width
1053 }
1054
1055 fn width_including_trailing_whitespace(&self) -> f64 {
1056 self.text_line_metrics.get().width_including_trailing_whitespace
1057 }
1058
1059 fn draw(&self, drawing_context: &mut dyn ITextDrawingSink, line_origin: Point) {
1060 let mut current_x = line_origin.x + self.start();
1061 let current_y = line_origin.y;
1062
1063 let text_runs = self.text_runs.borrow();
1064
1065 for text_run in text_runs.iter() {
1066 if let Some(drawable_text_run) = text_run.as_drawable() {
1067 let offset_y = Self::get_baseline_offset(self, drawable_text_run);
1068
1069 drawable_text_run.draw(drawing_context, Point::new(current_x, current_y + offset_y));
1070
1071 current_x += drawable_text_run.size().width;
1072 }
1073 }
1074 }
1075
1076 fn collapse(
1077 self: Rc<Self>,
1078 collapsing_properties_list: &[Option<Rc<dyn TextCollapsingProperties>>],
1079 ) -> Rc<dyn TextLine> {
1080 if collapsing_properties_list.is_empty() {
1081 return self;
1082 }
1083
1084 let Some(collapsing_properties) = &collapsing_properties_list[0] else {
1085 return self;
1086 };
1087
1088 let Some(collapsed_runs) = collapsing_properties.collapse(&*self) else {
1089 return self;
1090 };
1091
1092 let has_runs = !collapsed_runs.is_empty();
1093
1094 let collapsed_line = TextLineImpl::new(
1095 collapsed_runs,
1096 self.first_text_source_index,
1097 self.length,
1098 self.paragraph_width,
1099 self.paragraph_properties.clone(),
1100 self.resolved_flow_direction,
1101 self.text_line_break(),
1102 true,
1103 );
1104
1105 if has_runs {
1106 collapsed_line.finalize_line();
1107 }
1108
1109 collapsed_line
1110 }
1111
1112 fn justify(&self, justification_properties: &dyn JustificationProperties) {
1113 justification_properties.justify(self);
1114
1115 self.text_line_metrics.set(self.create_line_metrics());
1116 }
1117
1118 fn get_character_hit_from_distance(&self, mut distance: f64) -> CharacterHit {
1119 let text_runs = self.text_runs.borrow();
1120
1121 if text_runs.is_empty() {
1122 return CharacterHit::new(self.first_text_source_index);
1123 }
1124
1125 distance -= self.start();
1126
1127 let text_runs_length = text_runs.len();
1128
1129 let mut last_index = text_runs_length as i32 - 1;
1130
1131 if text_runs[last_index as usize].as_text_end_of_line().is_some() {
1132 last_index -= 1;
1133 }
1134
1135 if last_index < 0 {
1136 return CharacterHit::new(self.first_text_source_index);
1137 }
1138
1139 if distance <= 0.0 {
1140 return Self::get_run_character_hit(&*text_runs[0], self.get_run_text_source_position(0), 0.0);
1141 }
1142
1143 if distance >= self.width_including_trailing_whitespace() {
1144 return Self::get_run_character_hit(
1145 &*text_runs[last_index as usize],
1146 self.get_run_text_source_position(last_index as usize),
1147 distance,
1148 );
1149 }
1150
1151 let mut character_hit = CharacterHit::default();
1153 let mut current_distance = 0.0;
1154
1155 let mut i = 0usize;
1156
1157 while i as i32 <= last_index {
1158 let mut current_run = &text_runs[i];
1159 let mut current_visual_index = i;
1160
1161 if current_run
1162 .downcast_ref::<ShapedTextRun>()
1163 .is_some_and(|shaped_run| !shaped_run.shaped_buffer().is_left_to_right())
1164 {
1165 let mut right_to_left_index = i;
1166
1167 while right_to_left_index + 1 <= text_runs_length - 1 {
1168 right_to_left_index += 1;
1169
1170 let next_shaped = text_runs[right_to_left_index].downcast_ref::<ShapedTextRun>();
1171
1172 if next_shaped.is_none_or(|next_shaped| next_shaped.shaped_buffer().is_left_to_right()) {
1173 break;
1174 }
1175
1176 right_to_left_index += 1;
1177 }
1178
1179 let mut j = i;
1180
1181 while i <= right_to_left_index {
1184 if j > text_runs_length - 1 {
1185 break;
1186 }
1187
1188 current_run = &text_runs[j];
1189 current_visual_index = j;
1190
1191 let Some(shaped_run) = current_run.downcast_ref::<ShapedTextRun>() else {
1192 j += 1;
1193
1194 continue;
1195 };
1196
1197 let shaped_run_width = shaped_run.size().width;
1198
1199 if current_distance + shaped_run_width <= distance {
1200 current_distance += shaped_run_width;
1201
1202 j += 1;
1203
1204 continue;
1205 }
1206
1207 return Self::get_run_character_hit(
1208 &**current_run,
1209 self.get_run_text_source_position(j),
1210 distance - current_distance,
1211 );
1212 }
1213 }
1214
1215 character_hit = Self::get_run_character_hit(
1216 &**current_run,
1217 self.get_run_text_source_position(current_visual_index),
1218 distance - current_distance,
1219 );
1220
1221 if let Some(drawable_text_run) = current_run.as_drawable() {
1222 if i < text_runs_length - 1 && current_distance + drawable_text_run.size().width < distance {
1223 current_distance += drawable_text_run.size().width;
1224
1225 i += 1;
1226
1227 continue;
1228 }
1229 } else {
1230 i += 1;
1231
1232 continue;
1233 }
1234
1235 break;
1236 }
1237
1238 character_hit
1239 }
1240
1241 fn get_distance_from_character_hit(&self, character_hit: CharacterHit) -> f64 {
1242 let indexed_text_runs = match self.indexed_text_runs() {
1243 Some(indexed_text_runs) if !indexed_text_runs.is_empty() => indexed_text_runs,
1244 _ => return self.start(),
1245 };
1246
1247 let line_end = self.first_text_source_index + self.length;
1248
1249 let character_index = (character_hit.first_character_index() + character_hit.trailing_length()).min(line_end);
1250
1251 let mut current_position = self.first_text_source_index;
1252
1253 let mut current_text_run: Option<Rc<dyn TextRun>> = None;
1254
1255 let mut indexed_run_index = Self::find_indexed_run(&indexed_text_runs, current_position);
1256
1257 while current_position < line_end {
1258 let current_indexed_run = &indexed_text_runs[indexed_run_index];
1259
1260 current_text_run = current_indexed_run.text_run.borrow().clone();
1261
1262 let Some(text_run) = ¤t_text_run else {
1263 break;
1264 };
1265
1266 if current_indexed_run.text_source_character_index + text_run.length() <= character_hit.first_character_index()
1267 && current_position + text_run.length() < line_end
1268 {
1269 current_position += text_run.length();
1270
1271 indexed_run_index = Self::find_indexed_run(&indexed_text_runs, current_position);
1272
1273 continue;
1274 }
1275
1276 break;
1277 }
1278
1279 let Some(current_text_run) = current_text_run else {
1280 return self.start();
1281 };
1282
1283 let current_indexed_run = &indexed_text_runs[indexed_run_index];
1284
1285 let mut directional_width = 0.0;
1286 let first_run_index = current_indexed_run.run_index.get();
1287
1288 let current_direction = Self::get_run_direction(Some(&*current_text_run), self.resolved_flow_direction);
1289
1290 let current_x = self.start() + self.get_preceding_distance(first_run_index);
1291
1292 if let Some(current_drawable) = current_text_run.as_drawable() {
1293 directional_width = current_drawable.size().width;
1294 }
1295
1296 let last_run_index = Self::get_last_directional_run_index(
1297 &indexed_text_runs,
1298 indexed_run_index,
1299 current_direction,
1300 &mut directional_width,
1301 );
1302
1303 match current_direction {
1304 FlowDirection::RightToLeft => self
1305 .get_text_run_bounds_right_to_left(
1306 first_run_index,
1307 last_run_index,
1308 current_x + directional_width,
1309 character_index,
1310 current_position,
1311 1,
1312 )
1313 .0
1314 .rectangle()
1315 .right(),
1316 FlowDirection::LeftToRight => self
1317 .get_text_bounds_left_to_right(
1318 first_run_index,
1319 last_run_index,
1320 current_x,
1321 character_index,
1322 current_position,
1323 1,
1324 )
1325 .0
1326 .rectangle()
1327 .left(),
1328 }
1329 }
1330
1331 fn get_next_caret_character_hit(&self, character_hit: CharacterHit) -> CharacterHit {
1332 if self.text_runs.borrow().is_empty() || self.indexed_text_runs.borrow().is_none() {
1333 return CharacterHit::default();
1334 }
1335
1336 let mut current_character_hit = character_hit;
1337 let character_index = character_hit.first_character_index() + character_hit.trailing_length();
1338
1339 let (current_run, current_position) =
1340 self.get_run_at_character_index(character_index, LogicalDirection::Forward);
1341
1342 let mut next_character_hit = character_hit;
1343
1344 if let Some(current_run) = ¤t_run {
1345 if let Some(shaped_run) = current_run.downcast_ref::<ShapedTextRun>() {
1346 let glyph_run = shaped_run.glyph_run();
1347
1348 let offset = 0.max(current_position - glyph_run.metrics().first_cluster);
1349
1350 if character_hit.first_character_index() < current_position && offset > 0 {
1351 let (nearest, _) = glyph_run.find_nearest_character_hit(glyph_run.metrics().first_cluster);
1353
1354 next_character_hit = CharacterHit::with_trailing_length(
1355 nearest.first_character_index() + offset,
1356 nearest.trailing_length(),
1357 );
1358 } else {
1359 if offset > 0 {
1360 current_character_hit = CharacterHit::with_trailing_length(
1361 0.max(character_hit.first_character_index() - offset),
1362 character_hit.trailing_length(),
1363 );
1364 }
1365
1366 next_character_hit = glyph_run.get_next_caret_character_hit(current_character_hit);
1367
1368 if offset > 0 {
1369 next_character_hit = CharacterHit::with_trailing_length(
1370 next_character_hit.first_character_index() + offset,
1371 next_character_hit.trailing_length(),
1372 );
1373 }
1374 }
1375 } else {
1376 next_character_hit = CharacterHit::new(current_position + current_run.length());
1377 }
1378 }
1379
1380 if character_index == next_character_hit.first_character_index() + next_character_hit.trailing_length() {
1381 return character_hit;
1382 }
1383
1384 next_character_hit
1385 }
1386
1387 fn get_previous_caret_character_hit(&self, character_hit: CharacterHit) -> CharacterHit {
1388 self.get_previous_character_hit(character_hit, false)
1389 }
1390
1391 fn get_backspace_caret_character_hit(&self, character_hit: CharacterHit) -> CharacterHit {
1392 self.get_previous_character_hit(character_hit, true)
1393 }
1394
1395 fn get_text_bounds(&self, first_text_source_index: i32, text_length: i32) -> Vec<TextBounds> {
1397 if text_length == 0 {
1398 return Vec::new();
1399 }
1400
1401 let indexed_text_runs = match self.indexed_text_runs() {
1402 Some(indexed_text_runs) if !indexed_text_runs.is_empty() => indexed_text_runs,
1403 _ => return Vec::new(),
1404 };
1405
1406 let line_end = self.first_text_source_index + self.length;
1407
1408 let mut current_position = self.first_text_source_index;
1409 let mut remaining_length = text_length;
1410
1411 if first_text_source_index + text_length < self.first_text_source_index {
1413 let indexed_text_run = &indexed_text_runs[0];
1414 let current_direction =
1415 Self::get_run_direction(indexed_text_run.text_run.borrow().as_deref(), self.resolved_flow_direction);
1416
1417 return vec![TextBounds::new(Rect::new(0.0, 0.0, 0.0, self.height()), current_direction, Vec::new())];
1418 }
1419
1420 if first_text_source_index >= line_end {
1422 let indexed_text_run = &indexed_text_runs[indexed_text_runs.len() - 1];
1423 let current_direction =
1424 Self::get_run_direction(indexed_text_run.text_run.borrow().as_deref(), self.resolved_flow_direction);
1425
1426 return vec![TextBounds::new(
1427 Rect::new(self.width_including_trailing_whitespace(), 0.0, 0.0, self.height()),
1428 current_direction,
1429 Vec::new(),
1430 )];
1431 }
1432
1433 let mut result: Vec<TextBounds> = Vec::new();
1434
1435 while remaining_length > 0 && current_position < line_end {
1436 let indexed_run_index = Self::find_indexed_run(&indexed_text_runs, current_position);
1437
1438 let current_indexed_run = &indexed_text_runs[indexed_run_index];
1439
1440 let Some(current_text_run) = current_indexed_run.text_run.borrow().clone() else {
1441 break;
1442 };
1443
1444 let current_direction = Self::get_run_direction(Some(&*current_text_run), self.resolved_flow_direction);
1445
1446 if current_indexed_run.text_source_character_index + current_text_run.length() <= first_text_source_index {
1447 current_position += current_text_run.length();
1448
1449 continue;
1450 }
1451
1452 let current_x = self.start() + self.get_preceding_distance(current_indexed_run.run_index.get());
1453 let mut directional_width = 0.0;
1454
1455 if let Some(current_drawable) = current_text_run.as_drawable() {
1456 directional_width = current_drawable.size().width;
1457 }
1458
1459 let first_run_index = current_indexed_run.run_index.get();
1460 let last_run_index = Self::get_last_directional_run_index(
1461 &indexed_text_runs,
1462 indexed_run_index,
1463 current_direction,
1464 &mut directional_width,
1465 );
1466
1467 let (current_bounds, covered_length, new_position) = match current_direction {
1468 FlowDirection::RightToLeft => self.get_text_run_bounds_right_to_left(
1469 first_run_index,
1470 last_run_index,
1471 current_x + directional_width,
1472 first_text_source_index,
1473 current_position,
1474 remaining_length,
1475 ),
1476 FlowDirection::LeftToRight => self.get_text_bounds_left_to_right(
1477 first_run_index,
1478 last_run_index,
1479 current_x,
1480 first_text_source_index,
1481 current_position,
1482 remaining_length,
1483 ),
1484 };
1485
1486 current_position = new_position;
1487
1488 let unmerged_bounds = match result.last_mut() {
1489 Some(last_bounds) => Self::try_merge_with_last_bounds(current_bounds, last_bounds),
1490 None => Some(current_bounds),
1491 };
1492
1493 if let Some(current_bounds) = unmerged_bounds {
1494 result.push(current_bounds);
1495 }
1496
1497 if covered_length <= 0 {
1498 panic!("Covered length must be greater than zero.");
1499 }
1500
1501 remaining_length -= covered_length;
1502 }
1503
1504 result.sort_by(Self::text_bounds_comparer);
1505
1506 result
1507 }
1508
1509 fn dispose(&self) {
1510 for text_run in self.text_runs.borrow().iter() {
1511 if let Some(shaped_text_run) = text_run.downcast_ref::<ShapedTextRun>() {
1512 shaped_text_run.dispose();
1513 }
1514 }
1515 }
1516
1517 fn as_any(&self) -> &dyn Any {
1518 self
1519 }
1520}