Skip to main content

ferroui_base/media/text_formatting/
text_line_impl.rs

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
17/// The line produced by the text formatter.
18///
19/// The run list is only written by [`TextLineImpl::replace_text_run`] (used
20/// by justification) and by [`TextLineImpl::finalize_line`]; everything else
21/// reads it.
22pub 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    /// Orders two text bounds by their left edge.
67    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    /// The logical run table of the line; `None` before [`TextLineImpl::finalize_line`].
72    pub(crate) fn indexed_text_runs(&self) -> Option<Rc<[IndexedTextRun]>> {
73        self.indexed_text_runs.borrow().clone()
74    }
75
76    /// The bounds of the line (start, 0, width including trailing whitespace, height).
77    #[allow(dead_code)] // used by the text layout (a later step)
78    pub(crate) fn bounds(&self) -> Rect {
79        self.bounds.get()
80    }
81
82    /// The ink bounds of the line.
83    #[allow(dead_code)] // used by the text layout (a later step)
84    pub(crate) fn ink_bounds(&self) -> Rect {
85        self.ink_bounds.get()
86    }
87
88    /// The vertical offset that aligns a run in a line according to the
89    /// run's baseline alignment.
90    ///
91    /// Panics when the run has no properties (and therefore no baseline alignment).
92    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        // Find consecutive runs of same direction
237        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    /// Merges `current_bounds` into `last_bounds` when both have the same
269    /// direction and abut; gives `current_bounds` back otherwise.
270    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        // The two edges are computed by summing glyph advances along different paths, so
276        // abutting bounds can land an ULP apart - compare them the way the rest of layout
277        // compares coordinates, or a single directional span gets reported as two.
278        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) = &current_run {
320            if let Some(shaped_run) = current_run.downcast_ref::<ShapedTextRun>() {
321                let glyph_run = shaped_run.glyph_run();
322
323                // Determine the start of the first hit in local positions.
324                let run_offset = 0.max(character_index - current_position);
325
326                let first_cluster = glyph_run.metrics().first_cluster;
327
328                // Current position is a text source index and first cluster is relative to the GlyphRun's buffer.
329                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    /// Returns the bounds, the covered length and the new position.
386    #[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                        // Add potential TextEndOfParagraph
455                        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    /// Returns the bounds, the covered length and the new position.
488    #[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                        // Add potential TextEndOfParagraph
555                        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        // Determine the start of the first hit in local positions
603        let run_offset = 0.max(first_text_source_index - current_position);
604        let first_cluster = glyph_run.metrics().first_cluster;
605
606        // The start index needs to be relative to the first cluster
607        let mut start_index = first_cluster + run_offset;
608        let end_index = start_index + remaining_length;
609
610        // Current position is a text source index and first cluster is relative to the GlyphRun's buffer.
611        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        // Cluster boundary correction
618        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                // Remember the cluster correction offset
625                cluster_offset = start_index - cluster_start;
626
627                // Move to the start of the cluster
628                start_index -= cluster_offset;
629            }
630        }
631
632        // Find the visual start and end position of the hit
633        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            // We need the distance from right to left and get_distance_from_character_hit returns a
641            // distance from left to right so we need to adjust the offsets
642            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        // Find the start of the hit
649        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 the requested text range starts at the trailing edge we need to move at the end of the hit
653        if start_hit_index < start_index {
654            start_hit_index += start_hit.trailing_length();
655        }
656
657        // Find the next possible position that contains the end_index
658        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            // The hit is inside or at the trailing edge
662            nearest_end_hit.first_character_index() + nearest_end_hit.trailing_length()
663        } else {
664            // The hit is at the leading edge
665            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        // Normalize bounds
671        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        // We need to adjust the local position to the text source
678        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    /// Returns the run at the character index and the text position of the run.
718    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) = &current_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                // Align the ink bounds at the common baseline
823                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                // Align the bounds at the common baseline
832                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                // Clamp to the specified line height
851                height = line_height;
852                baseline = -ascent;
853            } else {
854                // Center the text vertically within the specified line height
855                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                    // This run has visible content before its own trailing whitespace, so it -
889                    // not an earlier run - is the true end of the line's visible content. An
890                    // earlier run's own trailing whitespace is interior to the line (followed by
891                    // this run's visible content) and must not be excluded from the width too.
892                    break;
893                }
894            }
895        }
896
897        let extent = ink_bounds.height;
898        // The height of overhanging pixels at the bottom
899        let overhang_after = ink_bounds.bottom() - height + half_line_gap;
900        // The width of overhanging pixels at the natural alignment point. Positive value means we are inside.
901        let overhang_leading = ink_bounds.left();
902        // The width of overhanging pixels at the end of the natural bounds. Positive value means we are inside.
903        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    /// Gets the text line offset x.
929    ///
930    /// * `width` — the line width.
931    /// * `width_including_trailing_whitespace` — the paragraph width including whitespace.
932    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
985/// Reference equality of two runs.
986fn 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        // process hit that happens within the line
1152        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                // The loop condition is upstream's (it tests `i`, not `j`); the loop
1182                // ends through the bounds check and the return below.
1183                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) = &current_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) = &current_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                    // Crossing from a previous run: find the nearest character hit at the first cluster
1352                    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    /// Returns no bounds for an empty range.
1396    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        // We can return early if the requested text range is before the line's text range.
1412        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        // We can return early if the requested text range is after the line's text range.
1421        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}