Skip to main content

zng_ext_font/
shaping.rs

1use std::{
2    cmp, fmt,
3    hash::{BuildHasher, Hash},
4    mem, ops,
5};
6
7use skrifa::MetadataProvider;
8use zng_app::widget::info::InlineSegmentInfo;
9use zng_ext_image::{ColorType, IMAGES, ImageDataFormat, ImageOptions, ImageSource, ImageVar};
10use zng_ext_l10n::{Lang, lang};
11use zng_layout::{
12    context::{InlineConstraintsLayout, InlineConstraintsMeasure, InlineSegmentPos, LayoutDirection, TextSegmentKind},
13    unit::{Align, FactorUnits, Px, PxBox, PxConstraints2d, PxPoint, PxRect, PxSize, about_eq, euclid},
14};
15use zng_txt::Txt;
16use zng_view_api::font::{GlyphIndex, GlyphInstance};
17
18use crate::{
19    BidiLevel, CaretIndex, Font, FontList, HYPHENATION, Hyphens, Justify, LineBreak, ParagraphBreak, SegmentedText, TextSegment, WordBreak,
20    font_features::RFontFeatures,
21};
22
23/// Reasons why a font might fail to load a glyph.
24#[derive(Clone, Copy, PartialEq, Debug)]
25#[non_exhaustive]
26pub enum GlyphLoadingError {
27    /// The font didn't contain a glyph with that ID.
28    NoSuchGlyph,
29    /// A platform function returned an error.
30    PlatformError,
31}
32impl std::error::Error for GlyphLoadingError {}
33impl fmt::Display for GlyphLoadingError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        use GlyphLoadingError::*;
36        match self {
37            NoSuchGlyph => write!(f, "no such glyph"),
38            PlatformError => write!(f, "platform error"),
39        }
40    }
41}
42
43/// Extra configuration for [`shape_text`](Font::shape_text).
44#[derive(Debug, Clone)]
45#[non_exhaustive]
46pub struct TextShapingArgs {
47    /// Extra spacing to add after each character.
48    pub letter_spacing: Px,
49
50    /// Extra spacing to add after each space (U+0020 SPACE).
51    pub word_spacing: Px,
52
53    /// Height of each line.
54    ///
55    /// Default can be computed using [`FontMetrics::line_height`].
56    ///
57    /// [`FontMetrics::line_height`]: crate::FontMetrics::line_height
58    pub line_height: Px,
59
60    /// Extra spacing added in between lines.
61    pub line_spacing: Px,
62
63    /// Replacement space between paragraphs.
64    pub paragraph_spacing: Px,
65
66    /// Extra spacing in paragraph lines start.
67    ///
68    /// The `bool` indicates if spacing is inverted (hang), applied to all paragraph lines except the first.
69    pub paragraph_indent: (Px, bool),
70
71    /// Primary language of the text.
72    pub lang: Lang,
73
74    /// Text flow direction.
75    pub direction: LayoutDirection,
76
77    /// Don't use font ligatures.
78    pub ignore_ligatures: bool,
79
80    /// Don't use font letter spacing.
81    pub disable_kerning: bool,
82
83    /// Width of the TAB character.
84    pub tab_x_advance: Px,
85
86    /// Inline constraints for initial text shaping and wrap.
87    pub inline_constraints: Option<InlineConstraintsMeasure>,
88
89    /// Finalized font features.
90    pub font_features: RFontFeatures,
91
92    /// Maximum line width.
93    ///
94    /// Is `Px::MAX` when text wrap is disabled.
95    pub max_width: Px,
96
97    /// Definition of paragraphs.
98    pub paragraph_break: ParagraphBreak,
99
100    /// Line break config for Chinese, Japanese, or Korean text.
101    pub line_break: LineBreak,
102
103    /// World break config.
104    ///
105    /// This value is only considered if it is impossible to fit the word to a line.
106    pub word_break: WordBreak,
107
108    /// Hyphen breaks config.
109    pub hyphens: Hyphens,
110
111    /// Character rendered when text is hyphenated by break.
112    pub hyphen_char: Txt,
113
114    /// Obscure the text with the replacement char.
115    pub obscuring_char: Option<char>,
116}
117impl Default for TextShapingArgs {
118    fn default() -> Self {
119        TextShapingArgs {
120            letter_spacing: Px(0),
121            word_spacing: Px(0),
122            line_height: Px(0),
123            line_spacing: Px(0),
124            paragraph_spacing: Px(0),
125            paragraph_indent: (Px(0), false),
126            lang: lang!(und),
127            direction: LayoutDirection::LTR,
128            ignore_ligatures: false,
129            disable_kerning: false,
130            tab_x_advance: Px(0),
131            inline_constraints: None,
132            font_features: RFontFeatures::default(),
133            max_width: Px::MAX,
134            paragraph_break: Default::default(),
135            line_break: Default::default(),
136            word_break: Default::default(),
137            hyphens: Default::default(),
138            hyphen_char: Txt::from_char('-'),
139            obscuring_char: None,
140        }
141    }
142}
143
144/// Configuration for [`ShapedText::reshape_lines`].
145#[derive(Debug, Clone, Default)]
146#[non_exhaustive]
147pub struct TextReshapingArgs {
148    /// Layout constraints.
149    pub constraints: PxConstraints2d,
150    /// Inline layout constraints.
151    pub inline_constraints: Option<InlineConstraintsLayout>,
152    /// Text alignment inside the available space.
153    pub align: Align,
154    /// Text alignment inside the available space when it overflows.
155    pub overflow_align: Align,
156    /// Text flow direction.
157    pub direction: LayoutDirection,
158
159    /// Line height.
160    pub line_height: Px,
161    /// Spacing between lines of the same paragraph.
162    pub line_spacing: Px,
163    /// Spacing between paragraphs.
164    pub paragraph_spacing: Px,
165    /// Extra spacing in paragraph lines start.
166    ///
167    /// The `bool` indicates if spacing is inverted (hang), applied to all paragraph lines except the first.
168    pub paragraph_indent: (Px, bool),
169    /// How paragraphs are defined.
170    pub paragraph_break: ParagraphBreak,
171}
172
173/// Defines a range of segments in a [`ShapedText`] that form a line.
174#[derive(Debug, Clone, Copy, PartialEq)]
175struct LineRange {
176    /// Exclusive segment index, is the `segments.len()` for the last line and the index of the first
177    /// segment after the line break for other lines.
178    end: usize,
179    /// Pixel width of the line.
180    width: f32,
181    /// Applied align offset to the right.
182    x_offset: f32,
183    directions: LayoutDirections,
184}
185
186/// Defines the font of a range of glyphs in a [`ShapedText`].
187#[derive(Clone)]
188struct FontRange {
189    font: Font,
190    /// Exclusive glyph range end.
191    end: usize,
192}
193impl PartialEq for FontRange {
194    fn eq(&self, other: &Self) -> bool {
195        self.font == other.font && self.end == other.end
196    }
197}
198impl fmt::Debug for FontRange {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        f.debug_struct("FontInfo")
201            .field("font", &self.font.face().display_name().name())
202            .field("end", &self.end)
203            .finish()
204    }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq)]
208struct GlyphSegment {
209    pub text: TextSegment,
210    /// glyph exclusive end.
211    pub end: usize,
212    /// Segment offset in the line.
213    pub x: f32,
214    /// Advance/width of segment.
215    pub advance: f32,
216}
217
218/// `Vec<GlyphSegment>` with helper methods.
219#[derive(Debug, Default, Clone, PartialEq)]
220struct GlyphSegmentVec(Vec<GlyphSegment>);
221impl GlyphSegmentVec {
222    /// Exclusive glyphs range of the segment.
223    fn glyphs(&self, index: usize) -> IndexRange {
224        let start = if index == 0 { 0 } else { self.0[index - 1].end };
225        let end = self.0[index].end;
226        IndexRange(start, end)
227    }
228
229    /// Exclusive glyphs range from an exclusive range of segments.
230    fn glyphs_range(&self, range: IndexRange) -> IndexRange {
231        let IndexRange(start, end) = range;
232
233        if end == 0 {
234            return IndexRange(0, 0);
235        }
236
237        let start = if start == 0 { 0 } else { self.0[start - 1].end };
238        let end = self.0[end - 1].end;
239
240        IndexRange(start, end)
241    }
242}
243
244/// `Vec<LineRange>` with helper methods.
245#[derive(Debug, Default, Clone, PartialEq)]
246struct LineRangeVec(Vec<LineRange>);
247impl LineRangeVec {
248    /// Exclusive segments range of the line.
249    fn segs(&self, index: usize) -> IndexRange {
250        let end = self.0[index].end;
251        let start = if index == 0 { 0 } else { self.0[index - 1].end };
252        IndexRange(start, end)
253    }
254
255    /// Line width.
256    fn width(&self, index: usize) -> f32 {
257        self.0[index].width
258    }
259
260    /// Line x offset.
261    fn x_offset(&self, index: usize) -> f32 {
262        self.0[index].x_offset
263    }
264
265    /// Iter segment ranges.
266    fn iter_segs(&self) -> impl Iterator<Item = (f32, IndexRange)> + '_ {
267        self.iter_segs_skip(0)
268    }
269
270    /// Iter segment ranges starting at a line.
271    fn iter_segs_skip(&self, start_line: usize) -> impl Iterator<Item = (f32, IndexRange)> + '_ {
272        let mut start = self.segs(start_line).start();
273        self.0[start_line..].iter().map(move |l| {
274            let r = IndexRange(start, l.end);
275            start = l.end;
276            (l.width, r)
277        })
278    }
279
280    /// Returns `true` if there is more then one line.
281    fn is_multi(&self) -> bool {
282        self.0.len() > 1
283    }
284
285    fn first_mut(&mut self) -> &mut LineRange {
286        &mut self.0[0]
287    }
288
289    fn last(&self) -> LineRange {
290        self.0[self.0.len() - 1]
291    }
292
293    fn last_mut(&mut self) -> &mut LineRange {
294        let l = self.0.len() - 1;
295        &mut self.0[l]
296    }
297}
298
299/// `Vec<FontRange>` with helper methods.
300#[derive(Debug, Default, Clone, PartialEq)]
301struct FontRangeVec(Vec<FontRange>);
302impl FontRangeVec {
303    /// Iter glyph ranges.
304    fn iter_glyphs(&self) -> impl Iterator<Item = (&Font, IndexRange)> + '_ {
305        let mut start = 0;
306        self.0.iter().map(move |f| {
307            let r = IndexRange(start, f.end);
308            start = f.end;
309            (&f.font, r)
310        })
311    }
312
313    /// Iter glyph ranges clipped by `glyphs_range`.
314    fn iter_glyphs_clip(&self, glyphs_range: IndexRange) -> impl Iterator<Item = (&Font, IndexRange)> + '_ {
315        let mut start = glyphs_range.start();
316        let end = glyphs_range.end();
317        let first_font = self.0.iter().position(|f| f.end > start).unwrap_or(self.0.len().saturating_sub(1));
318
319        self.0[first_font..].iter().map_while(move |f| {
320            let i = f.end.min(end);
321
322            if i > start {
323                let r = IndexRange(start, i);
324                start = i;
325                Some((&f.font, r))
326            } else {
327                None
328            }
329        })
330    }
331
332    /// Returns a reference to the font.
333    fn font(&self, index: usize) -> &Font {
334        &self.0[index].font
335    }
336}
337
338#[derive(Clone)]
339struct GlyphImage(ImageVar);
340impl PartialEq for GlyphImage {
341    fn eq(&self, other: &Self) -> bool {
342        self.0.var_eq(&other.0)
343    }
344}
345impl fmt::Debug for GlyphImage {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        write!(f, "GlyphImage(_)")
348    }
349}
350
351/// Output of [text layout].
352///
353/// [text layout]: Font::shape_text
354#[derive(Debug, Clone, PartialEq)]
355pub struct ShapedText {
356    // glyphs are in text order by segments and in visual (LTR) order within segments.
357    glyphs: Vec<GlyphInstance>,
358    // char byte index of each glyph in the segment that covers it.
359    clusters: Vec<u32>,
360    // segments of `glyphs` and `clusters`.
361    segments: GlyphSegmentVec,
362    lines: LineRangeVec,
363    fonts: FontRangeVec,
364    // sorted map of `glyphs` index -> image.
365    images: Vec<(u32, GlyphImage)>,
366
367    line_height: Px,
368    line_spacing: Px,
369    paragraph_spacing: Px,
370    paragraph_break: ParagraphBreak,
371
372    orig_line_height: Px,
373    orig_line_spacing: Px,
374    orig_first_line: PxSize,
375    orig_last_line: PxSize,
376
377    // offsets from the line_height bottom
378    baseline: Px,
379    overline: Px,
380    strikethrough: Px,
381    underline: Px,
382    underline_descent: Px,
383
384    /// vertical align offset applied.
385    mid_offset: f32,
386    align_size: PxSize,
387    align: Align,
388    justify: Justify,    // applied justify if `align` is FILL_X
389    justified: Vec<f32>, // each line has up to 3 values here, depending on first/last segs are trimmed
390    overflow_align: Align,
391    direction: LayoutDirection,
392
393    // inline layout values
394    is_inlined: bool,
395    first_wrapped: bool,
396    first_line: PxRect,
397    mid_clear: Px,
398    mid_size: PxSize,
399    last_line: PxRect,
400
401    has_colored_glyphs: bool,
402}
403
404/// Represents normal and colored glyphs in [`ShapedText::colored_glyphs`].
405pub enum ShapedColoredGlyphs<'a> {
406    /// Sequence of not colored glyphs, use the base color to fill.
407    Normal(&'a [GlyphInstance]),
408    /// Colored glyph.
409    Colored {
410        /// Point that must be used for all `glyphs`.
411        point: euclid::Point2D<f32, Px>,
412        /// The glyph that is replaced by `glyphs`.
413        ///
414        /// Must be used as fallback if any `glyphs` cannot be rendered.
415        base_glyph: GlyphIndex,
416
417        /// The colored glyph components.
418        glyphs: super::ColorGlyph<'a>,
419    },
420}
421
422/// Represents normal and image glyphs in [`ShapedText::image_glyphs`].
423pub enum ShapedImageGlyphs<'a> {
424    /// Sequence of not image glyphs.
425    Normal(&'a [GlyphInstance]),
426    /// Image glyph.
427    Image {
428        /// Origin and size of the image in the shaped text.
429        ///
430        /// The size is empty is the image has not loaded yet.
431        rect: euclid::Rect<f32, Px>,
432        /// The glyph that is replaced by `img`.
433        ///
434        /// Must be used as fallback if the `img` cannot be rendered.
435        base_glyph: GlyphIndex,
436        /// The image.
437        img: &'a ImageVar,
438    },
439}
440
441impl ShapedText {
442    /// New empty text.
443    pub fn new(font: &Font) -> Self {
444        font.shape_text(&SegmentedText::new("", LayoutDirection::LTR), &TextShapingArgs::default())
445    }
446
447    /// Glyphs by font.
448    ///
449    /// The glyphs are in text order by segments and in visual order (LTR) within segments, so
450    /// the RTL text "لما " will have the space glyph first, then "’álif", "miim", "láam".
451    ///
452    /// All glyph points are set as offsets to the top-left of the text full text.
453    ///
454    /// Note that multiple glyphs can map to the same char and multiple chars can map to the same glyph.
455    pub fn glyphs(&self) -> impl Iterator<Item = (&Font, &[GlyphInstance])> {
456        self.fonts.iter_glyphs().map(move |(f, r)| (f, &self.glyphs[r.iter()]))
457    }
458
459    /// Glyphs in a range by font.
460    ///
461    /// Similar output to [`glyphs`], but only glyphs in the `range`.
462    ///
463    /// [`glyphs`]: Self::glyphs
464    pub fn glyphs_slice(&self, range: impl ops::RangeBounds<usize>) -> impl Iterator<Item = (&Font, &[GlyphInstance])> {
465        self.glyphs_slice_impl(IndexRange::from_bounds(range))
466    }
467    fn glyphs_slice_impl(&self, range: IndexRange) -> impl Iterator<Item = (&Font, &[GlyphInstance])> {
468        self.fonts.iter_glyphs_clip(range).map(move |(f, r)| (f, &self.glyphs[r.iter()]))
469    }
470
471    /// If the shaped text has any Emoji glyph associated with a font that has color palettes.
472    pub fn has_colored_glyphs(&self) -> bool {
473        self.has_colored_glyphs
474    }
475
476    /// If the shaped text has any Emoji glyph associated with a pixel image.
477    pub fn has_images(&self) -> bool {
478        !self.images.is_empty()
479    }
480
481    /// Glyphs by font and palette color.
482    pub fn colored_glyphs(&self) -> impl Iterator<Item = (&Font, ShapedColoredGlyphs<'_>)> {
483        ColoredGlyphsIter {
484            glyphs: self.glyphs(),
485            maybe_colored: None,
486        }
487    }
488
489    /// Glyphs in a range by font and palette color.
490    pub fn colored_glyphs_slice(&self, range: impl ops::RangeBounds<usize>) -> impl Iterator<Item = (&Font, ShapedColoredGlyphs<'_>)> {
491        ColoredGlyphsIter {
492            glyphs: self.glyphs_slice_impl(IndexRange::from_bounds(range)),
493            maybe_colored: None,
494        }
495    }
496
497    /// Glyphs by font and associated image.
498    pub fn image_glyphs(&self) -> impl Iterator<Item = (&Font, ShapedImageGlyphs<'_>)> {
499        ImageGlyphsIter {
500            glyphs: self.glyphs(),
501            glyphs_i: 0,
502            images: &self.images,
503            maybe_img: None,
504        }
505    }
506
507    /// Glyphs in a range by font and associated image.
508    pub fn image_glyphs_slice(&self, range: impl ops::RangeBounds<usize>) -> impl Iterator<Item = (&Font, ShapedImageGlyphs<'_>)> {
509        let range = IndexRange::from_bounds(range);
510        ImageGlyphsIter {
511            glyphs_i: range.start() as _,
512            glyphs: self.glyphs_slice_impl(range),
513            images: &self.images,
514            maybe_img: None,
515        }
516    }
517
518    /// Glyphs by font in the range.
519    fn glyphs_range(&self, range: IndexRange) -> impl Iterator<Item = (&Font, &[GlyphInstance])> {
520        self.fonts.iter_glyphs_clip(range).map(|(f, r)| (f, &self.glyphs[r.iter()]))
521    }
522
523    /// Index of each char byte in the segment range.
524    /// The first char in the segment is 0.
525    fn clusters_range(&self, range: IndexRange) -> &[u32] {
526        &self.clusters[range.iter()]
527    }
528
529    fn seg_glyphs_with_x_advance(
530        &self,
531        seg_idx: usize,
532        glyphs_range: IndexRange,
533    ) -> impl Iterator<Item = (&Font, impl Iterator<Item = (GlyphInstance, f32)> + '_)> + '_ {
534        let mut gi = glyphs_range.start();
535        let seg_x = if gi < self.glyphs.len() { self.glyphs[gi].point.x } else { 0.0 };
536        let seg_advance = self.segments.0[seg_idx].advance;
537        self.glyphs_range(glyphs_range).map(move |(font, glyphs)| {
538            let g_adv = glyphs.iter().map(move |g| {
539                gi += 1;
540
541                let adv = if gi == glyphs_range.end() {
542                    (seg_x + seg_advance) - g.point.x
543                } else {
544                    self.glyphs[gi].point.x - g.point.x
545                };
546                (*g, adv)
547            });
548
549            (font, g_adv)
550        })
551    }
552
553    fn seg_cluster_glyphs_with_x_advance(
554        &self,
555        seg_idx: usize,
556        glyphs_range: IndexRange,
557    ) -> impl Iterator<Item = (&Font, impl Iterator<Item = (u32, &[GlyphInstance], f32)>)> {
558        let mut gi = glyphs_range.start();
559        let seg_x = if gi < self.glyphs.len() { self.glyphs[gi].point.x } else { 0.0 };
560        let seg_advance = self.segments.0[seg_idx].advance;
561        let seg_clusters = self.clusters_range(glyphs_range);
562        let mut cluster_i = 0;
563
564        self.glyphs_range(glyphs_range).map(move |(font, glyphs)| {
565            let clusters = &seg_clusters[cluster_i..cluster_i + glyphs.len()];
566            cluster_i += glyphs.len();
567
568            struct Iter<'a> {
569                clusters: &'a [u32],
570                glyphs: &'a [GlyphInstance],
571            }
572            impl<'a> Iterator for Iter<'a> {
573                type Item = (u32, &'a [GlyphInstance]);
574
575                fn next(&mut self) -> Option<Self::Item> {
576                    if let Some(c) = self.clusters.first() {
577                        let end = self.clusters.iter().rposition(|rc| rc == c).unwrap();
578                        let glyphs = &self.glyphs[..=end];
579                        self.clusters = &self.clusters[end + 1..];
580                        self.glyphs = &self.glyphs[end + 1..];
581                        Some((*c, glyphs))
582                    } else {
583                        None
584                    }
585                }
586            }
587
588            let g_adv = Iter { clusters, glyphs }.map(move |(c, gs)| {
589                gi += gs.len();
590
591                let adv = if gi == glyphs_range.end() {
592                    (seg_x + seg_advance) - gs[0].point.x
593                } else {
594                    self.glyphs[gi].point.x - gs[0].point.x
595                };
596                (c, gs, adv)
597            });
598
599            (font, g_adv)
600        })
601    }
602
603    /// Bounding box size, the width is the longest line or the first or
604    /// last line width + absolute offset, the height is the bottom-most point of the last line.
605    pub fn size(&self) -> PxSize {
606        let first_width = self.first_line.origin.x.abs() + self.first_line.size.width;
607        let last_width = self.last_line.origin.x.abs() + self.last_line.size.width;
608        self.mid_size()
609            .max(PxSize::new(first_width.max(last_width), self.last_line.max_y()))
610    }
611
612    /// Size of the text, if it is not inlined.
613    pub fn block_size(&self) -> PxSize {
614        if self.lines.0.is_empty() {
615            PxSize::zero()
616        } else if self.lines.0.len() == 1 {
617            self.first_line.size
618        } else {
619            let mut s = PxSize::new(
620                self.first_line.size.width.max(self.last_line.size.width),
621                self.first_line.size.height + self.line_spacing + self.last_line.size.height,
622            );
623            if self.lines.0.len() > 2 {
624                s.width = s.width.max(self.mid_size.width);
625                s.height += self.mid_size.height + self.line_spacing;
626            }
627            s
628        }
629    }
630
631    /// Gets the first line that overflows the `max_height`. A line overflows when the line `PxRect::max_y`
632    /// is greater than `max_height`.
633    pub fn overflow_line(&self, max_height: Px) -> Option<ShapedLine<'_>> {
634        let mut y = self.first_line.max_y();
635        if y > max_height {
636            self.line(0)
637        } else if self.lines.0.len() > 1 {
638            let mid_lines = self.lines.0.len() - 2;
639            for i in 0..=mid_lines {
640                y += self.line_spacing;
641                y += self.line_height;
642                if y > max_height {
643                    return self.line(i + 1);
644                }
645            }
646
647            if self.last_line.max_y() > max_height {
648                self.line(self.lines.0.len() - 1)
649            } else {
650                None
651            }
652        } else {
653            None
654        }
655    }
656
657    fn update_mid_size(&mut self) {
658        self.mid_size = if self.lines.0.len() <= 2 {
659            PxSize::zero()
660        } else {
661            let mid_lines = &self.lines.0[1..self.lines.0.len() - 1];
662            PxSize::new(
663                Px(mid_lines.iter().map(|l| l.width).max_by(f32_cmp).unwrap_or_default().ceil() as i32),
664                Px(mid_lines.len() as i32) * self.line_height + Px((mid_lines.len() - 1) as i32) * self.line_spacing,
665            )
666        };
667    }
668
669    fn update_first_last_lines(&mut self) {
670        if self.lines.0.is_empty() {
671            self.first_line = PxRect::zero();
672            self.last_line = PxRect::zero();
673            self.align_size = PxSize::zero();
674        } else {
675            self.first_line = PxRect::from_size(PxSize::new(Px(self.lines.first_mut().width.ceil() as i32), self.line_height));
676
677            if self.lines.0.len() > 1 {
678                self.last_line.size = PxSize::new(Px(self.lines.last().width.ceil() as i32), self.line_height);
679                self.last_line.origin = PxPoint::new(Px(0), self.first_line.max_y() + self.line_spacing);
680                if self.lines.0.len() > 2 {
681                    self.last_line.origin.y += self.mid_size.height + self.line_spacing;
682                }
683            } else {
684                self.last_line = self.first_line;
685            }
686            self.align_size = self.block_size();
687        }
688    }
689
690    /// Bounding box of the mid-lines, that is the lines except the first and last.
691    pub fn mid_size(&self) -> PxSize {
692        self.mid_size
693    }
694
695    /// If the text first and last lines is defined externally by the inline layout.
696    ///
697    /// When this is `true` the shaped text only defines aligns horizontally and only the mid-lines. The vertical
698    /// offset is defined by the first line rectangle plus the [`mid_clear`].
699    ///
700    /// [`mid_clear`]: Self::mid_clear
701    pub fn is_inlined(&self) -> bool {
702        self.is_inlined
703    }
704
705    /// Last applied alignment.
706    ///
707    /// If the text is inlined only the mid-lines are aligned, and only horizontally.
708    pub fn align(&self) -> Align {
709        self.align
710    }
711
712    /// Last applied justify.
713    ///
714    /// This is the resolved mode, it is never `Auto`.
715    ///
716    /// [`align`]: Self::align
717    pub fn justify_mode(&self) -> Option<Justify> {
718        match self.justify {
719            Justify::Auto => None,
720            m => {
721                debug_assert!(self.align.is_fill_x());
722                Some(m)
723            }
724        }
725    }
726
727    /// Last applied overflow alignment.
728    ///
729    /// Only used in dimensions of the text that overflow [`align_size`].
730    ///
731    /// [`align_size`]: Self::align_size
732    pub fn overflow_align(&self) -> Align {
733        self.overflow_align
734    }
735
736    /// Last applied alignment area.
737    ///
738    /// The lines are aligned inside this size. If the text is inlined only the mid-lines are aligned and only horizontally.
739    pub fn align_size(&self) -> PxSize {
740        self.align_size
741    }
742
743    /// Last applied alignment direction.
744    ///
745    /// Note that the glyph and word directions is defined by the [`TextShapingArgs::lang`] and the computed
746    /// direction is in [`ShapedSegment::direction`].
747    pub fn direction(&self) -> LayoutDirection {
748        self.direction
749    }
750
751    /// Last applied extra spacing between the first and second lines to clear the full width of the second line in the
752    /// parent inline layout.
753    pub fn mid_clear(&self) -> Px {
754        self.mid_clear
755    }
756
757    /// Reshape text lines.
758    ///
759    /// Reshape text lines without re-wrapping, this is more efficient then fully reshaping every glyph, but may
760    /// cause overflow if called with constraints incompatible with the ones used during the full text shaping.
761    ///
762    /// The general process of shaping text is to generate a shaped-text without align during *measure*, and then reuse
763    /// this shaped text every layout that does not invalidate any property that affects the text wrap.
764    ///
765    /// Note that this method clears justify fill, of `align` is fill X you must call [`reshape_lines_justify`] after to refill.
766    ///
767    /// [`reshape_lines_justify`]: Self::reshape_lines_justify
768    pub fn reshape_lines(&mut self, args: &TextReshapingArgs) {
769        self.clear_justify_impl(args.align.is_fill_x());
770        self.reshape_line_height_and_spacing(args.line_height, args.line_spacing);
771
772        let is_inlined = args.inline_constraints.is_some();
773
774        let align_x = args.align.x(args.direction);
775        let align_y = if is_inlined { 0.fct() } else { args.align.y() };
776        let overflow_align_x = args.overflow_align.x(args.direction);
777        let overflow_align_y = if is_inlined { 0.fct() } else { args.overflow_align.y() };
778
779        let (first, mid, last, first_segs, last_segs) = if let Some(l) = &args.inline_constraints {
780            (l.first, l.mid_clear, l.last, &*l.first_segs, &*l.last_segs)
781        } else {
782            // calculate our own first & last
783            let block_size = self.block_size();
784            let align_size = args.constraints.fill_size_or(block_size);
785
786            let mut first = PxRect::from_size(self.line(0).map(|l| l.rect().size).unwrap_or_default());
787            let mut last = PxRect::from_size(
788                self.line(self.lines_len().saturating_sub(1))
789                    .map(|l| l.rect().size)
790                    .unwrap_or_default(),
791            );
792            last.origin.y = block_size.height - last.size.height;
793
794            match first.size.width.cmp(&align_size.width) {
795                cmp::Ordering::Less => first.origin.x = (align_size.width - first.size.width) * align_x,
796                cmp::Ordering::Equal => {}
797                cmp::Ordering::Greater => first.origin.x = (align_size.width - first.size.width) * overflow_align_x,
798            }
799            match last.size.width.cmp(&align_size.width) {
800                cmp::Ordering::Less => last.origin.x = (align_size.width - last.size.width) * align_x,
801                cmp::Ordering::Equal => {}
802                cmp::Ordering::Greater => last.origin.x = (align_size.width - last.size.width) * overflow_align_x,
803            }
804
805            match block_size.height.cmp(&align_size.height) {
806                cmp::Ordering::Less => {
807                    let align_y = (align_size.height - block_size.height) * align_y;
808                    first.origin.y += align_y;
809                    last.origin.y += align_y;
810                }
811                cmp::Ordering::Equal => {}
812                cmp::Ordering::Greater => {
813                    let align_y = (align_size.height - block_size.height) * overflow_align_y;
814                    first.origin.y += align_y;
815                    last.origin.y += align_y;
816                }
817            }
818
819            static EMPTY: Vec<InlineSegmentPos> = vec![];
820            (first, Px(0), last, &EMPTY, &EMPTY)
821        };
822
823        if !self.lines.0.is_empty() {
824            if self.first_line != first {
825                let first_offset = (first.origin - self.first_line.origin).cast::<f32>().cast_unit();
826
827                let first_range = self.lines.segs(0);
828                let first_glyphs = self.segments.glyphs_range(first_range);
829
830                for g in &mut self.glyphs[first_glyphs.iter()] {
831                    g.point += first_offset;
832                }
833
834                let first_line = self.lines.first_mut();
835                first_line.x_offset = first.origin.x.0 as f32;
836                // width is the same measured, unless the parent inliner changed it to fill,
837                // in that case we need the original width in `reshape_lines_justify`.
838                // first_line.width = first.size.width.0 as f32;
839            }
840            if !first_segs.is_empty() {
841                // parent set first_segs.
842                let first_range = self.lines.segs(0);
843                if first_range.len() == first_segs.len() {
844                    for i in first_range.iter() {
845                        let seg_offset = first_segs[i].x - self.segments.0[i].x;
846                        let glyphs = self.segments.glyphs(i);
847                        for g in &mut self.glyphs[glyphs.iter()] {
848                            g.point.x += seg_offset;
849                        }
850                        self.segments.0[i].x = first_segs[i].x;
851                    }
852                } else {
853                    #[cfg(debug_assertions)]
854                    {
855                        tracing::error!("expected {} segments in `first_segs`, was {}", first_range.len(), first_segs.len());
856                    }
857                }
858            }
859        }
860
861        if self.lines.0.len() > 1 {
862            if self.last_line != last {
863                // last changed and it is not first
864
865                let last_offset = (last.origin - self.last_line.origin).cast::<f32>().cast_unit();
866
867                let last_range = self.lines.segs(self.lines.0.len() - 1);
868                let last_glyphs = self.segments.glyphs_range(last_range);
869
870                for g in &mut self.glyphs[last_glyphs.iter()] {
871                    g.point += last_offset;
872                }
873
874                let last_line = self.lines.last_mut();
875                last_line.x_offset = last.origin.x.0 as f32;
876                // width is the same measured, unless the parent inliner changed it to justify, that is handled later.
877                // last_line.width = last.size.width.0 as f32;
878            }
879            if !last_segs.is_empty() {
880                // parent set last_segs.
881                let last_range = self.lines.segs(self.lines.0.len() - 1);
882
883                if last_range.len() == last_segs.len() {
884                    for i in last_range.iter() {
885                        let li = i - last_range.start();
886
887                        let seg_offset = last_segs[li].x - self.segments.0[i].x;
888                        let glyphs = self.segments.glyphs(i);
889                        for g in &mut self.glyphs[glyphs.iter()] {
890                            g.point.x += seg_offset;
891                        }
892                        self.segments.0[i].x = last_segs[li].x;
893                    }
894                } else {
895                    #[cfg(debug_assertions)]
896                    {
897                        tracing::error!("expected {} segments in `last_segs`, was {}", last_range.len(), last_segs.len());
898                    }
899                }
900            }
901        }
902
903        self.first_line = first;
904        self.last_line = last;
905
906        let block_size = self.block_size();
907        let align_size = args.constraints.fill_size_or(block_size);
908
909        if self.lines.0.len() > 2 {
910            // has mid-lines
911
912            let mid_offset = euclid::vec2::<f32, Px>(
913                0.0,
914                match block_size.height.cmp(&align_size.height) {
915                    cmp::Ordering::Less => (align_size.height - block_size.height).0 as f32 * align_y + mid.0 as f32,
916                    cmp::Ordering::Equal => mid.0 as f32,
917                    cmp::Ordering::Greater => (align_size.height - block_size.height).0 as f32 * overflow_align_y + mid.0 as f32,
918                },
919            );
920            let y_transform = mid_offset.y - self.mid_offset;
921            let align_width = align_size.width.0 as f32;
922
923            let skip_last = self.lines.0.len() - 2;
924            let mut line_start = self.lines.0[0].end;
925            for line in &mut self.lines.0[1..=skip_last] {
926                let x_offset = if line.width < align_width {
927                    (align_width - line.width) * align_x
928                } else {
929                    (align_width - line.width) * overflow_align_x
930                };
931                let x_transform = x_offset - line.x_offset;
932
933                let glyphs = self.segments.glyphs_range(IndexRange(line_start, line.end));
934                for g in &mut self.glyphs[glyphs.iter()] {
935                    g.point.x += x_transform;
936                    g.point.y += y_transform;
937                }
938                line.x_offset = x_offset;
939
940                line_start = line.end;
941            }
942
943            let y_transform_px = Px(y_transform as i32);
944            self.underline -= y_transform_px;
945            self.baseline -= y_transform_px;
946            self.overline -= y_transform_px;
947            self.strikethrough -= y_transform_px;
948            self.underline_descent -= y_transform_px;
949            self.mid_offset = mid_offset.y;
950        }
951
952        // apply baseline to the content only,
953        let baseline_offset =
954            if self.align.is_baseline() { -self.baseline } else { Px(0) } + if args.align.is_baseline() { self.baseline } else { Px(0) };
955        if baseline_offset != Px(0) {
956            let baseline_offset = baseline_offset.0 as f32;
957            for g in &mut self.glyphs {
958                g.point.y += baseline_offset;
959            }
960        }
961
962        self.align_size = align_size;
963        self.align = args.align;
964        self.direction = args.direction;
965        self.is_inlined = is_inlined;
966
967        self.debug_assert_ranges();
968    }
969    fn reshape_line_height_and_spacing(&mut self, line_height: Px, line_spacing: Px) {
970        let mut update_height = false;
971
972        if self.line_height != line_height {
973            let offset_y = (line_height - self.line_height).0 as f32;
974            let mut offset = 0.0;
975            let center = offset_y / 2.0;
976
977            self.first_line.origin.y += Px(center as i32);
978
979            for (_, r) in self.lines.iter_segs() {
980                let r = self.segments.glyphs_range(r);
981                for g in &mut self.glyphs[r.iter()] {
982                    g.point.y += offset + center;
983                }
984
985                offset += offset_y;
986            }
987
988            self.line_height = line_height;
989            update_height = true;
990        }
991
992        if self.line_spacing != line_spacing {
993            if self.lines.is_multi() {
994                let offset_y = (line_spacing - self.line_spacing).0 as f32;
995                let mut offset = offset_y;
996
997                for (_, r) in self.lines.iter_segs_skip(1) {
998                    let r = self.segments.glyphs_range(r);
999
1000                    for g in &mut self.glyphs[r.iter()] {
1001                        g.point.y += offset;
1002                    }
1003
1004                    offset += offset_y;
1005                }
1006                offset -= offset_y;
1007
1008                self.last_line.origin.y += Px(offset as i32);
1009
1010                update_height = true;
1011            }
1012            self.line_spacing = line_spacing;
1013        }
1014
1015        if update_height {
1016            self.update_mid_size();
1017
1018            if !self.is_inlined {
1019                self.update_first_last_lines();
1020            }
1021        }
1022    }
1023
1024    /// Restore text to initial shape.
1025    pub fn clear_reshape(&mut self) {
1026        self.reshape_lines(&TextReshapingArgs::default());
1027    }
1028
1029    fn justify_lines_range(&self) -> ops::Range<usize> {
1030        let mut range = 0..self.lines_len();
1031
1032        if !self.is_inlined {
1033            // skip last line
1034            range.end = range.end.saturating_sub(1);
1035        }
1036        // else inlined fills the first and last line rects
1037
1038        range
1039    }
1040
1041    /// Replace the applied [`justify_mode`], if the [`align`] is fill X.
1042    ///
1043    /// [`justify_mode`]: Self::justify_mode
1044    /// [`align`]: Self::align
1045    pub fn reshape_lines_justify(&mut self, mode: Justify, lang: &Lang) {
1046        self.clear_justify_impl(true);
1047
1048        if !self.align.is_fill_x() {
1049            return;
1050        }
1051
1052        let mode = mode.resolve(lang);
1053
1054        let range = self.justify_lines_range();
1055
1056        let fill_width = self.align_size.width.0 as f32;
1057        let last_li = range.end.saturating_sub(1);
1058
1059        for li in range.clone() {
1060            let mut count;
1061            let mut space;
1062            let mut line_seg_range;
1063            let mut offset = 0.0;
1064            let mut last_is_space = false;
1065
1066            let mut fill_width = fill_width;
1067            if self.is_inlined {
1068                // inlining parent provides the fill space for the first and last segment
1069                if li == 0 {
1070                    fill_width = self.first_line.width().0 as f32;
1071                } else if li == last_li {
1072                    fill_width = self.last_line.width().0 as f32;
1073                }
1074            }
1075
1076            {
1077                // line scope
1078                let line = self.line(li).unwrap();
1079
1080                // count of space insert points
1081                count = match mode {
1082                    Justify::InterWord => line.segs().filter(|s| s.kind().is_space()).count(),
1083                    Justify::InterLetter => line
1084                        .segs()
1085                        .map(|s| {
1086                            if s.kind().is_space() {
1087                                s.clusters_count().saturating_sub(1).max(1)
1088                            } else if s.kind().is_word() {
1089                                s.clusters_count().saturating_sub(1)
1090                            } else {
1091                                0
1092                            }
1093                        })
1094                        .sum(),
1095                    Justify::Auto => unreachable!(),
1096                };
1097
1098                // space to distribute
1099                space = fill_width - self.lines.0[li].width;
1100
1101                line_seg_range = 0..line.segs_len();
1102
1103                // trim spaces at start and end
1104                let mut first_is_space = false;
1105
1106                if let Some(s) = line.seg(0)
1107                    && s.kind().is_space()
1108                    && (!self.is_inlined || li > 0 || self.first_line.origin.x == Px(0))
1109                {
1110                    // trim start, unless it inlining and the first seg is actually a continuation of another text on the same row
1111                    first_is_space = true;
1112                    count -= 1;
1113                    space += s.advance();
1114                }
1115                if let Some(s) = line.seg(line.segs_len().saturating_sub(1))
1116                    && s.kind().is_space()
1117                    && (!self.is_inlined || li < range.end - 1 || about_eq(self.first_line.size.width.0 as f32, fill_width, 1.0))
1118                {
1119                    // trim end, unless its inlining and the last seg continues
1120                    last_is_space = true;
1121                    count -= 1;
1122                    space += s.advance();
1123                }
1124                if first_is_space {
1125                    line_seg_range.start += 1;
1126                    let gsi = self.line(li).unwrap().seg_range.start();
1127                    let adv = mem::take(&mut self.segments.0[gsi].advance);
1128                    offset -= adv;
1129                    self.justified.push(adv);
1130                }
1131                if last_is_space {
1132                    line_seg_range.end = line_seg_range.end.saturating_sub(1);
1133                    let gsi = self.line(li).unwrap().seg_range.end().saturating_sub(1);
1134                    let adv = mem::take(&mut self.segments.0[gsi].advance);
1135                    self.justified.push(adv);
1136                }
1137                if line_seg_range.start > line_seg_range.end {
1138                    line_seg_range = 0..0;
1139                }
1140            }
1141            let justify_advance = space / count as f32;
1142            self.justified.push(justify_advance);
1143
1144            for si in line_seg_range {
1145                let is_space;
1146                let glyphs_range;
1147                let gsi;
1148                {
1149                    let line = self.line(li).unwrap();
1150                    let seg = line.seg(si).unwrap();
1151
1152                    is_space = seg.kind().is_space();
1153                    glyphs_range = seg.glyphs_range();
1154                    gsi = line.seg_range.start() + si;
1155                }
1156
1157                let mut cluster = if self.clusters.is_empty() {
1158                    0
1159                } else {
1160                    self.clusters[glyphs_range.start()]
1161                };
1162                for gi in glyphs_range {
1163                    self.glyphs[gi].point.x += offset;
1164
1165                    if matches!(mode, Justify::InterLetter) && self.clusters[gi] != cluster {
1166                        cluster = self.clusters[gi];
1167                        offset += justify_advance;
1168                        self.segments.0[gsi].advance += justify_advance;
1169                    }
1170                }
1171
1172                let seg = &mut self.segments.0[gsi];
1173                seg.x += offset;
1174                if is_space {
1175                    offset += justify_advance;
1176                    seg.advance += justify_advance;
1177                }
1178            }
1179            if last_is_space {
1180                let gsi = self.line(li).unwrap().seg_range.end().saturating_sub(1);
1181                let seg = &mut self.segments.0[gsi];
1182                debug_assert_eq!(seg.advance, 0.0);
1183                seg.x += offset;
1184            }
1185            self.justified.shrink_to_fit();
1186        }
1187
1188        self.justify = mode;
1189    }
1190
1191    /// Remove the currently applied [`justify_mode`].
1192    ///
1193    /// [`justify_mode`]: Self::justify_mode
1194    pub fn clear_justify(&mut self) {
1195        self.clear_justify_impl(false)
1196    }
1197    fn clear_justify_impl(&mut self, keep_alloc: bool) {
1198        if self.justify_mode().is_none() {
1199            return;
1200        }
1201
1202        let range = self.justify_lines_range();
1203        debug_assert!(range.len() <= self.justified.len());
1204
1205        let mut justified_alloc = mem::take(&mut self.justified);
1206
1207        let mut justified = justified_alloc.drain(..);
1208        for li in range {
1209            let mut line_seg_range;
1210            let mut last_is_space = false;
1211
1212            let mut offset = 0.0;
1213
1214            {
1215                let line = self.line(li).unwrap();
1216
1217                line_seg_range = 0..line.segs_len();
1218
1219                // trim spaces at start and end
1220                let mut first_is_space = false;
1221
1222                if let Some(s) = line.seg(0) {
1223                    first_is_space = s.kind().is_space();
1224                }
1225                if let Some(s) = line.seg(line.segs_len().saturating_sub(1)) {
1226                    last_is_space = s.kind().is_space();
1227                }
1228                if first_is_space {
1229                    line_seg_range.start += 1;
1230                    let gsi = self.line(li).unwrap().seg_range.start();
1231
1232                    let adv = justified.next().unwrap();
1233                    self.segments.0[gsi].advance = adv;
1234                    offset -= adv;
1235                }
1236                if last_is_space {
1237                    line_seg_range.end = line_seg_range.end.saturating_sub(1);
1238                    let adv = justified.next().unwrap();
1239                    let gsi = self.line(li).unwrap().seg_range.end().saturating_sub(1);
1240                    self.segments.0[gsi].advance = adv;
1241                }
1242                if line_seg_range.start > line_seg_range.end {
1243                    line_seg_range = 0..0;
1244                }
1245            }
1246
1247            let justify_advance = justified.next().unwrap();
1248
1249            for si in line_seg_range {
1250                let is_space;
1251                let glyphs_range;
1252                let gsi;
1253                {
1254                    let line = self.line(li).unwrap();
1255                    let seg = line.seg(si).unwrap();
1256
1257                    is_space = seg.kind().is_space();
1258                    glyphs_range = seg.glyphs_range();
1259                    gsi = line.seg_range.start() + si;
1260                }
1261
1262                let mut cluster = if self.clusters.is_empty() {
1263                    0
1264                } else {
1265                    self.clusters[glyphs_range.start()]
1266                };
1267                for gi in glyphs_range {
1268                    self.glyphs[gi].point.x -= offset;
1269
1270                    if matches!(self.justify, Justify::InterLetter) && self.clusters[gi] != cluster {
1271                        cluster = self.clusters[gi];
1272                        offset += justify_advance;
1273                        self.segments.0[gsi].advance -= justify_advance;
1274                    }
1275                }
1276
1277                let seg = &mut self.segments.0[gsi];
1278                seg.x -= offset;
1279                if is_space {
1280                    offset += justify_advance;
1281                    seg.advance -= justify_advance;
1282                }
1283            }
1284            if last_is_space {
1285                let gsi = self.line(li).unwrap().seg_range.end().saturating_sub(1);
1286                self.segments.0[gsi].x -= offset;
1287            }
1288        }
1289
1290        self.justify = Justify::Auto;
1291
1292        if keep_alloc {
1293            drop(justified);
1294            self.justified = justified_alloc;
1295        }
1296    }
1297
1298    /// Defines what lines are first and last in paragraph.
1299    pub fn paragraph_break(&self) -> ParagraphBreak {
1300        self.paragraph_break
1301    }
1302
1303    /// Height of a single line.
1304    pub fn line_height(&self) -> Px {
1305        self.line_height
1306    }
1307
1308    /// Vertical spacing in between lines of the same paragraph.
1309    pub fn line_spacing(&self) -> Px {
1310        self.line_spacing
1311    }
1312
1313    /// Vertical spacing in between paragraphs.
1314    pub fn paragraph_spacing(&self) -> Px {
1315        self.paragraph_spacing
1316    }
1317
1318    /// Vertical offset from the line bottom up that is the text baseline.
1319    ///
1320    /// The *line bottom* is the [`line_height`].
1321    ///
1322    /// [`line_height`]: Self::line_height
1323    pub fn baseline(&self) -> Px {
1324        self.baseline
1325    }
1326
1327    /// Vertical offset from the line bottom up that is the overline placement.
1328    pub fn overline(&self) -> Px {
1329        self.overline
1330    }
1331
1332    /// Vertical offset from the line bottom up that is the strikethrough placement.
1333    pub fn strikethrough(&self) -> Px {
1334        self.strikethrough
1335    }
1336
1337    /// Vertical offset from the line bottom up that is the font defined underline placement.
1338    pub fn underline(&self) -> Px {
1339        self.underline
1340    }
1341
1342    /// Vertical offset from the line bottom up that is the underline placement when the option for
1343    /// clearing all glyph descents is selected.
1344    pub fn underline_descent(&self) -> Px {
1345        self.underline_descent
1346    }
1347
1348    /// No segments.
1349    pub fn is_empty(&self) -> bool {
1350        self.segments.0.is_empty()
1351    }
1352
1353    /// Iterate over [`ShapedLine`] selections split by [`LineBreak`] or wrap.
1354    ///
1355    /// [`LineBreak`]: TextSegmentKind::LineBreak
1356    pub fn lines(&self) -> impl Iterator<Item = ShapedLine<'_>> {
1357        let just_width = self.justify_mode().map(|_| self.align_size.width);
1358        self.lines.iter_segs().enumerate().map(move |(i, (w, r))| ShapedLine {
1359            text: self,
1360            seg_range: r,
1361            index: i,
1362            width: just_width.unwrap_or_else(|| Px(w.round() as i32)),
1363        })
1364    }
1365
1366    /// Returns the number of text lines.
1367    pub fn lines_len(&self) -> usize {
1368        self.lines.0.len()
1369    }
1370
1371    /// If the first line starts in a new inline row because it could not fit in the leftover inline space.
1372    pub fn first_wrapped(&self) -> bool {
1373        self.first_wrapped
1374    }
1375
1376    /// Gets the line by index.
1377    pub fn line(&self, line_idx: usize) -> Option<ShapedLine<'_>> {
1378        if line_idx >= self.lines.0.len() {
1379            None
1380        } else {
1381            self.lines.iter_segs_skip(line_idx).next().map(move |(w, r)| ShapedLine {
1382                text: self,
1383                seg_range: r,
1384                index: line_idx,
1385                width: Px(w.round() as i32),
1386            })
1387        }
1388    }
1389
1390    /// Create an empty [`ShapedText`] with the same metrics as `self`.
1391    pub fn empty(&self) -> ShapedText {
1392        ShapedText {
1393            glyphs: vec![],
1394            clusters: vec![],
1395            segments: GlyphSegmentVec(vec![]),
1396            lines: LineRangeVec(vec![LineRange {
1397                end: 0,
1398                width: 0.0,
1399                x_offset: 0.0,
1400                directions: LayoutDirections::empty(),
1401            }]),
1402            fonts: FontRangeVec(vec![FontRange {
1403                font: self.fonts.font(0).clone(),
1404                end: 0,
1405            }]),
1406            images: vec![],
1407            orig_line_height: self.orig_line_height,
1408            orig_line_spacing: self.orig_line_spacing,
1409            orig_first_line: PxSize::zero(),
1410            orig_last_line: PxSize::zero(),
1411            line_height: self.orig_line_height,
1412            line_spacing: self.orig_line_spacing,
1413            paragraph_spacing: self.paragraph_spacing,
1414            paragraph_break: self.paragraph_break,
1415            baseline: self.baseline,
1416            overline: self.overline,
1417            strikethrough: self.strikethrough,
1418            underline: self.underline,
1419            underline_descent: self.underline_descent,
1420            mid_offset: 0.0,
1421            align_size: PxSize::zero(),
1422            align: Align::TOP_LEFT,
1423            justify: Justify::Auto,
1424            justified: vec![],
1425            overflow_align: Align::TOP_LEFT,
1426            direction: LayoutDirection::LTR,
1427            first_wrapped: false,
1428            first_line: PxRect::zero(),
1429            mid_clear: Px(0),
1430            is_inlined: false,
1431            mid_size: PxSize::zero(),
1432            last_line: PxRect::zero(),
1433            has_colored_glyphs: false,
1434        }
1435    }
1436
1437    /// Check if any line can be better wrapped given the new wrap config.
1438    ///
1439    /// Note that a new [`ShapedText`] must be generated to *rewrap*.
1440    pub fn can_rewrap(&self, max_width: Px) -> bool {
1441        for line in self.lines() {
1442            if line.width > max_width || line.started_by_wrap() {
1443                return true;
1444            }
1445        }
1446        false
1447    }
1448
1449    fn debug_assert_ranges(&self) {
1450        #[cfg(debug_assertions)]
1451        {
1452            #[allow(unused)]
1453            macro_rules! trace_assert {
1454                ($cond:expr $(,)?) => {
1455                    #[allow(clippy::all)]
1456                    if !($cond) {
1457                        tracing::error!("{}", stringify!($cond));
1458                        return;
1459                    }
1460                };
1461                ($cond:expr, $($arg:tt)+) => {
1462                    #[allow(clippy::all)]
1463                    if !($cond) {
1464                        tracing::error!($($arg)*);
1465                        return;
1466                    }
1467                };
1468            }
1469
1470            let mut prev_seg_end = 0;
1471            for seg in &self.segments.0 {
1472                trace_assert!(seg.end >= prev_seg_end);
1473                prev_seg_end = seg.end;
1474            }
1475            trace_assert!(self.segments.0.last().map(|s| s.end == self.glyphs.len()).unwrap_or(true));
1476
1477            let mut prev_line_end = 0;
1478            for (i, line) in self.lines.0.iter().enumerate() {
1479                trace_assert!(line.end >= prev_line_end);
1480                trace_assert!(line.width >= 0.0);
1481
1482                let line_max = line.x_offset + line.width;
1483                let glyphs = self.segments.glyphs_range(IndexRange(prev_line_end, line.end));
1484                for g in &self.glyphs[glyphs.iter()] {
1485                    // false positive in cases of heavy use of combining chars
1486                    // only observed in "Zalgo" text, remove if we there is a legitimate
1487                    // Script that causing this error.
1488                    trace_assert!(
1489                        g.point.x <= line_max,
1490                        "glyph.x({:?}) > line[{i}].x+width({:?})",
1491                        g.point.x,
1492                        line_max
1493                    );
1494                }
1495
1496                let seg_width = self.segments.0[prev_line_end..line.end].iter().map(|s| s.advance).sum::<f32>();
1497                trace_assert!(
1498                    seg_width <= line.width,
1499                    "seg_width({:?}) > line[{i}].width({:?})",
1500                    seg_width,
1501                    line.width,
1502                );
1503
1504                prev_line_end = line.end;
1505            }
1506            trace_assert!(self.lines.0.last().map(|l| l.end == self.segments.0.len()).unwrap_or(true));
1507
1508            let mut prev_font_end = 0;
1509            for font in &self.fonts.0 {
1510                trace_assert!(font.end >= prev_font_end);
1511                prev_font_end = font.end;
1512            }
1513            trace_assert!(self.fonts.0.last().map(|f| f.end == self.glyphs.len()).unwrap_or(true));
1514        }
1515    }
1516
1517    /// Gets the top-middle origin for a caret visual that marks the insert `index` in the string.
1518    pub fn caret_origin(&self, caret: CaretIndex, full_text: &str) -> PxPoint {
1519        let index = caret.index;
1520        let mut end_line = None;
1521        for line in self.line(caret.line).into_iter().chain(self.lines()) {
1522            for seg in line.segs() {
1523                let txt_range = seg.text_range();
1524                if !txt_range.contains(&index) {
1525                    continue;
1526                }
1527                let local_index = index - txt_range.start;
1528                let is_rtl = seg.direction().is_rtl();
1529
1530                let seg_rect = seg.rect();
1531                let mut origin = seg_rect.origin;
1532
1533                let clusters = seg.clusters();
1534                let mut cluster_i = 0;
1535                let mut search_lig = true;
1536
1537                if is_rtl {
1538                    for (i, c) in clusters.iter().enumerate().rev() {
1539                        match (*c as usize).cmp(&local_index) {
1540                            cmp::Ordering::Less => {
1541                                cluster_i = i;
1542                            }
1543                            cmp::Ordering::Equal => {
1544                                cluster_i = i;
1545                                search_lig = false;
1546                                break;
1547                            }
1548                            cmp::Ordering::Greater => break,
1549                        }
1550                    }
1551                } else {
1552                    for (i, c) in clusters.iter().enumerate() {
1553                        match (*c as usize).cmp(&local_index) {
1554                            cmp::Ordering::Less => {
1555                                cluster_i = i;
1556                            }
1557                            cmp::Ordering::Equal => {
1558                                cluster_i = i;
1559                                search_lig = false;
1560                                break;
1561                            }
1562                            cmp::Ordering::Greater => break,
1563                        }
1564                    }
1565                }
1566
1567                let mut origin_x = origin.x.0 as f32;
1568
1569                // glyphs are always in display order (LTR) and map
1570                // to each cluster entry.
1571                //
1572                // in both LTR and RTL we sum advance until `cluster_i` is found,
1573                // but in RTL we sum *back* to the char (so it needs to be covered +1)
1574                let mut glyph_take = cluster_i;
1575                if is_rtl {
1576                    glyph_take += 1;
1577                }
1578
1579                let mut search_lig_data = None;
1580
1581                'outer: for (font, glyphs) in seg.glyphs_with_x_advance() {
1582                    for (g, advance) in glyphs {
1583                        search_lig_data = Some((font, g.index, advance));
1584
1585                        if glyph_take == 0 {
1586                            break 'outer;
1587                        }
1588                        origin_x += advance;
1589                        glyph_take -= 1;
1590                    }
1591                }
1592
1593                if search_lig && let Some((font, g_index, advance)) = search_lig_data {
1594                    let lig_start = txt_range.start + clusters[cluster_i] as usize;
1595                    let lig_end = if is_rtl {
1596                        if cluster_i == 0 {
1597                            txt_range.end
1598                        } else {
1599                            txt_range.start + clusters[cluster_i - 1] as usize
1600                        }
1601                    } else {
1602                        clusters
1603                            .get(cluster_i + 1)
1604                            .map(|c| txt_range.start + *c as usize)
1605                            .unwrap_or_else(|| txt_range.end)
1606                    };
1607
1608                    let maybe_lig = &full_text[lig_start..lig_end];
1609
1610                    let lig_len = unicode_segmentation::UnicodeSegmentation::grapheme_indices(maybe_lig, true).count();
1611                    if lig_len > 1 {
1612                        // is ligature
1613
1614                        let lig_taken = &full_text[lig_start..index];
1615                        let lig_taken = unicode_segmentation::UnicodeSegmentation::grapheme_indices(lig_taken, true).count();
1616
1617                        for (i, lig_advance) in font.ligature_caret_offsets(g_index).enumerate() {
1618                            if i == lig_taken {
1619                                // font provided ligature caret for index
1620                                origin_x += lig_advance;
1621                                search_lig = false;
1622                                break;
1623                            }
1624                        }
1625
1626                        if search_lig {
1627                            // synthetic lig. caret
1628                            let lig_advance = advance * (lig_taken as f32 / lig_len as f32);
1629
1630                            if is_rtl {
1631                                origin_x -= lig_advance;
1632                            } else {
1633                                origin_x += lig_advance;
1634                            }
1635                        }
1636                    }
1637                }
1638
1639                origin.x = Px(origin_x.round() as _);
1640                return origin;
1641            }
1642
1643            if line.index == caret.line && line.text_range().end == index && line.ended_by_wrap() {
1644                // is at the end of a wrap.
1645                end_line = Some(line.index);
1646                break;
1647            }
1648        }
1649
1650        // position at the end of the end_line.
1651        let line_end = end_line.unwrap_or_else(|| self.lines_len().saturating_sub(1));
1652        if let Some(line) = self.line(line_end) {
1653            let rect = line.rect();
1654            if self.direction().is_rtl() {
1655                // top-left of last line if it the text is RTL overall.
1656                PxPoint::new(rect.min_x(), rect.min_y())
1657            } else {
1658                // top-right of last line for LTR
1659                PxPoint::new(rect.max_x(), rect.min_y())
1660            }
1661        } else {
1662            PxPoint::zero()
1663        }
1664    }
1665
1666    /// Gets the line that contains the `y` offset or is nearest to it.
1667    pub fn nearest_line(&self, y: Px) -> Option<ShapedLine<'_>> {
1668        let first_line_max_y = self.first_line.max_y();
1669        if first_line_max_y >= y {
1670            self.line(0)
1671        } else if self.last_line.min_y() <= y {
1672            self.line(self.lines_len().saturating_sub(1))
1673        } else {
1674            let y = y - first_line_max_y;
1675            let line = (y / self.line_height()).0 as usize + 1;
1676            self.lines.iter_segs_skip(line).next().map(move |(w, r)| ShapedLine {
1677                text: self,
1678                seg_range: r,
1679                index: line,
1680                width: Px(w.round() as i32),
1681            })
1682        }
1683    }
1684
1685    /// Changes the caret line if the current line cannot contain the current char byte index.
1686    ///
1687    /// This retains the same line at ambiguous points at the end/start of wrapped lines.
1688    pub fn snap_caret_line(&self, mut caret: CaretIndex) -> CaretIndex {
1689        for line in self.lines() {
1690            let range = line.text_range();
1691
1692            if range.start == caret.index {
1693                // at start that can be by wrap
1694                if line.started_by_wrap() {
1695                    if caret.line >= line.index {
1696                        caret.line = line.index;
1697                    } else {
1698                        caret.line = line.index.saturating_sub(1);
1699                    }
1700                } else {
1701                    caret.line = line.index;
1702                }
1703                return caret;
1704            } else if range.contains(&caret.index) {
1705                // inside of line
1706                caret.line = line.index;
1707                return caret;
1708            }
1709        }
1710        caret.line = self.lines.0.len().saturating_sub(1);
1711        caret
1712    }
1713
1714    /// Gets a full overflow analysis.
1715    pub fn overflow_info(&self, max_size: PxSize, overflow_suffix_width: Px) -> Option<TextOverflowInfo> {
1716        // check y overflow
1717
1718        let (last_line, overflow_line) = match self.overflow_line(max_size.height) {
1719            Some(l) => {
1720                if l.index == 0 {
1721                    // all text overflows
1722                    return Some(TextOverflowInfo {
1723                        line: 0,
1724                        text_char: 0,
1725                        included_glyphs: smallvec::smallvec![],
1726                        suffix_origin: l.rect().origin.cast().cast_unit(),
1727                    });
1728                } else {
1729                    (self.line(l.index - 1).unwrap(), l.index)
1730                }
1731            }
1732            None => (self.line(self.lines_len().saturating_sub(1))?, self.lines_len()),
1733        };
1734
1735        // check x overflow
1736
1737        let max_width = max_size.width - overflow_suffix_width;
1738
1739        if last_line.width <= max_width {
1740            // No x overflow
1741            return if overflow_line < self.lines_len() {
1742                Some(TextOverflowInfo {
1743                    line: overflow_line,
1744                    text_char: last_line.text_range().end,
1745                    included_glyphs: smallvec::smallvec_inline![0..last_line.glyphs_range().end()],
1746                    suffix_origin: {
1747                        let r = last_line.rect();
1748                        let mut o = r.origin;
1749                        match self.direction {
1750                            LayoutDirection::LTR => o.x += r.width(),
1751                            LayoutDirection::RTL => o.x -= overflow_suffix_width,
1752                        }
1753                        o.cast().cast_unit()
1754                    },
1755                })
1756            } else {
1757                None
1758            };
1759        }
1760
1761        let directions = last_line.directions();
1762        if directions == LayoutDirections::BIDI {
1763            let mut included_glyphs = smallvec::SmallVec::<[ops::Range<usize>; 1]>::new_const();
1764
1765            let min_x = match self.direction {
1766                LayoutDirection::LTR => Px(0),
1767                LayoutDirection::RTL => last_line.rect().max_x() - max_width,
1768            };
1769            let max_x = min_x + max_width;
1770
1771            let mut end_seg = None;
1772
1773            for seg in last_line.segs() {
1774                let (x, width) = seg.x_width();
1775                let seg_max_x = x + width;
1776
1777                if x < max_x && seg_max_x >= min_x {
1778                    let mut glyphs_range = seg.glyphs_range().iter();
1779                    let mut text_range = seg.text_range();
1780                    if x < min_x {
1781                        if let Some((c, g)) = seg.overflow_char_glyph((width - (min_x - x)).0 as f32) {
1782                            glyphs_range.start += g + 1;
1783                            text_range.start += c;
1784                        }
1785                    } else if seg_max_x > max_x
1786                        && let Some((c, g)) = seg.overflow_char_glyph((width - seg_max_x - max_x).0 as f32)
1787                    {
1788                        glyphs_range.end -= g;
1789                        text_range.end -= c;
1790                    }
1791
1792                    if let Some(l) = included_glyphs.last_mut() {
1793                        if l.end == glyphs_range.start {
1794                            l.end = glyphs_range.end;
1795                        } else if glyphs_range.end == l.start {
1796                            l.start = glyphs_range.start;
1797                        } else {
1798                            included_glyphs.push(glyphs_range.clone());
1799                        }
1800                    } else {
1801                        included_glyphs.push(glyphs_range.clone());
1802                    }
1803
1804                    match self.direction {
1805                        LayoutDirection::LTR => {
1806                            if let Some((sx, se, gr, tr)) = &mut end_seg {
1807                                if x < *sx {
1808                                    *sx = x;
1809                                    *se = seg;
1810                                    *gr = glyphs_range;
1811                                    *tr = text_range;
1812                                }
1813                            } else {
1814                                end_seg = Some((x, seg, glyphs_range, text_range));
1815                            }
1816                        }
1817                        LayoutDirection::RTL => {
1818                            if let Some((smx, se, gr, tr)) = &mut end_seg {
1819                                if seg_max_x < *smx {
1820                                    *smx = seg_max_x;
1821                                    *se = seg;
1822                                    *gr = glyphs_range;
1823                                    *tr = text_range;
1824                                }
1825                            } else {
1826                                end_seg = Some((seg_max_x, seg, glyphs_range, text_range));
1827                            }
1828                        }
1829                    }
1830                }
1831            }
1832
1833            if let Some((_, seg, glyphs_range, text_range)) = end_seg {
1834                Some(match self.direction {
1835                    LayoutDirection::LTR => TextOverflowInfo {
1836                        line: overflow_line,
1837                        text_char: text_range.end,
1838                        included_glyphs,
1839                        suffix_origin: {
1840                            let r = seg.rect();
1841                            let seg_range = seg.glyphs_range().iter();
1842                            let mut o = r.origin.cast().cast_unit();
1843                            let mut w = r.width();
1844                            if seg_range != glyphs_range
1845                                && let Some(g) = seg.glyph(glyphs_range.end - seg_range.start)
1846                            {
1847                                o.x = g.1.point.x;
1848                                w = Px(0);
1849                            }
1850                            o.x += w.0 as f32;
1851                            o
1852                        },
1853                    },
1854                    LayoutDirection::RTL => TextOverflowInfo {
1855                        line: overflow_line,
1856                        text_char: text_range.start,
1857                        included_glyphs,
1858                        suffix_origin: {
1859                            let r = seg.rect();
1860                            let mut o = r.origin.cast().cast_unit();
1861                            let seg_range = seg.glyphs_range().iter();
1862                            if seg_range != glyphs_range
1863                                && let Some(g) = seg.glyph(glyphs_range.start - seg_range.start)
1864                            {
1865                                o.x = g.1.point.x;
1866                            }
1867                            o.x -= overflow_suffix_width.0 as f32;
1868                            o
1869                        },
1870                    },
1871                })
1872            } else {
1873                None
1874            }
1875        } else {
1876            // single direction overflow
1877            let mut max_width_f32 = max_width.0 as f32;
1878            for seg in last_line.segs() {
1879                let seg_advance = seg.advance();
1880                max_width_f32 -= seg_advance;
1881                if max_width_f32 <= 0.0 {
1882                    let seg_text_range = seg.text_range();
1883                    let seg_glyphs_range = seg.glyphs_range();
1884
1885                    if directions == LayoutDirections::RTL {
1886                        let (c, g) = match seg.overflow_char_glyph(seg_advance + max_width_f32) {
1887                            Some(r) => r,
1888                            None => (seg_text_range.len(), seg_glyphs_range.len()),
1889                        };
1890
1891                        return Some(TextOverflowInfo {
1892                            line: overflow_line,
1893                            text_char: seg_text_range.start + c,
1894                            included_glyphs: smallvec::smallvec![
1895                                0..seg_glyphs_range.start(),
1896                                seg_glyphs_range.start() + g + 1..seg_glyphs_range.end()
1897                            ],
1898                            suffix_origin: {
1899                                let mut o = if let Some(g) = seg.glyph(g + 1) {
1900                                    euclid::point2(g.1.point.x, seg.rect().origin.y.0 as f32)
1901                                } else {
1902                                    let rect = seg.rect();
1903                                    let mut o = rect.origin.cast().cast_unit();
1904                                    o.x += seg.advance();
1905                                    o
1906                                };
1907                                o.x -= overflow_suffix_width.0 as f32;
1908                                o
1909                            },
1910                        });
1911                    } else {
1912                        // LTR or empty
1913
1914                        let (c, g) = match seg.overflow_char_glyph((max_width - seg.x_width().0).0 as f32) {
1915                            Some(r) => r,
1916                            None => (seg_text_range.len(), seg_glyphs_range.len()),
1917                        };
1918
1919                        return Some(TextOverflowInfo {
1920                            line: overflow_line,
1921                            text_char: seg_text_range.start + c,
1922                            included_glyphs: smallvec::smallvec_inline![0..seg_glyphs_range.start() + g],
1923                            suffix_origin: {
1924                                if let Some(g) = seg.glyph(g) {
1925                                    euclid::point2(g.1.point.x, seg.rect().origin.y.0 as f32)
1926                                } else {
1927                                    let rect = seg.rect();
1928                                    let mut o = rect.origin.cast().cast_unit();
1929                                    o.x += seg.advance();
1930                                    o
1931                                }
1932                            },
1933                        });
1934                    }
1935                }
1936            }
1937            // no overflow, rounding issue?
1938            None
1939        }
1940    }
1941
1942    /// Rectangles of the text selected by `range`.
1943    ///
1944    /// The [`CaretIndex::line`] must be valid in the `range` value.
1945    pub fn highlight_rects(&self, range: ops::Range<CaretIndex>, full_txt: &str) -> impl Iterator<Item = PxRect> + '_ {
1946        let start_origin = self.caret_origin(range.start, full_txt).x;
1947        let end_origin = self.caret_origin(range.end, full_txt).x;
1948
1949        MergingRectIter::new(
1950            self.lines()
1951                .skip(range.start.line)
1952                .take(range.end.line + 1 - range.start.line)
1953                .flat_map(|l| l.segs())
1954                .skip_while(move |s| s.text_end() <= range.start.index)
1955                .take_while(move |s| s.text_start() < range.end.index)
1956                .map(move |s| {
1957                    let mut r = s.rect();
1958
1959                    if s.text_start() <= range.start.index {
1960                        // first segment in selection
1961
1962                        match s.direction() {
1963                            LayoutDirection::LTR => {
1964                                r.size.width = r.max_x() - start_origin;
1965                                r.origin.x = start_origin;
1966                            }
1967                            LayoutDirection::RTL => {
1968                                r.size.width = start_origin - r.origin.x;
1969                            }
1970                        }
1971                    }
1972                    if s.text_end() > range.end.index {
1973                        // last segment in selection
1974
1975                        match s.direction() {
1976                            LayoutDirection::LTR => {
1977                                r.size.width = end_origin - r.origin.x;
1978                            }
1979                            LayoutDirection::RTL => {
1980                                r.size.width = r.max_x() - end_origin;
1981                                r.origin.x = end_origin;
1982                            }
1983                        }
1984                    }
1985
1986                    r
1987                }),
1988        )
1989    }
1990
1991    /// Underlines of the text selected by `range`.
1992    ///
1993    /// Yields each line start point and width. The underline does not skip.
1994    ///
1995    /// The [`CaretIndex::line`] must be valid in the `range` value.
1996    pub fn highlight_underlines(&self, range: ops::Range<CaretIndex>, full_txt: &str) -> impl Iterator<Item = (PxPoint, Px)> + '_ {
1997        let offset = self.underline();
1998        self.highlight_rects(range, full_txt).map(move |r| {
1999            let mut origin = r.origin;
2000            origin.y = r.max_y() - offset;
2001            (origin, r.width())
2002        })
2003    }
2004
2005    /// Clip under/overline to a text `clip_range` area, if `clip_out` only lines outside the range are visible.
2006    pub fn clip_lines(
2007        &self,
2008        clip_range: ops::Range<CaretIndex>,
2009        clip_out: bool,
2010        txt: &str,
2011        lines: impl Iterator<Item = (PxPoint, Px)>,
2012    ) -> Vec<(PxPoint, Px)> {
2013        let clips: Vec<_> = self.highlight_rects(clip_range, txt).collect();
2014
2015        let mut out_lines = vec![];
2016
2017        if clip_out {
2018            let mut exclude_buf = vec![];
2019            for (origin, width) in lines {
2020                let line_max = origin.x + width;
2021
2022                for clip in clips.iter() {
2023                    if origin.y >= clip.origin.y && origin.y <= clip.max_y() {
2024                        // line contains
2025                        if origin.x < clip.max_x() && line_max > clip.origin.x {
2026                            // intersects
2027                            exclude_buf.push((clip.origin.x, clip.max_x()));
2028                        }
2029                    }
2030                }
2031
2032                if !exclude_buf.is_empty() {
2033                    // clips don't overlap, enforce LTR
2034                    exclude_buf.sort_by_key(|(s, _)| *s);
2035
2036                    if origin.x < exclude_buf[0].0 {
2037                        // bit before the first clip
2038                        out_lines.push((origin, exclude_buf[0].0 - origin.x));
2039                    }
2040                    let mut blank_start = exclude_buf[0].1;
2041                    for (clip_start, clip_end) in exclude_buf.drain(..).skip(1) {
2042                        if clip_start > blank_start {
2043                            // space between clips
2044                            if line_max > clip_start {
2045                                // bit in-between two clips
2046                                out_lines.push((PxPoint::new(blank_start, origin.y), line_max.min(clip_start) - blank_start));
2047                            }
2048                            blank_start = clip_end;
2049                        }
2050                    }
2051                    if line_max > blank_start {
2052                        // bit after the last clip
2053                        out_lines.push((PxPoint::new(blank_start, origin.y), line_max - blank_start));
2054                    }
2055                } else {
2056                    // not clipped
2057                    out_lines.push((origin, width));
2058                }
2059            }
2060        } else {
2061            let mut include_buf = vec![];
2062            for (origin, width) in lines {
2063                let line_max = origin.x + width;
2064
2065                for clip in clips.iter() {
2066                    if origin.y >= clip.origin.y && origin.y <= clip.max_y() {
2067                        // line contains
2068                        if origin.x < clip.max_x() && line_max > clip.origin.x {
2069                            // intersects
2070                            include_buf.push((clip.origin.x, clip.max_x()));
2071                        }
2072                    }
2073                }
2074
2075                if !include_buf.is_empty() {
2076                    include_buf.sort_by_key(|(s, _)| *s);
2077
2078                    for (clip_start, clip_end) in include_buf.drain(..) {
2079                        let start = clip_start.max(origin.x);
2080                        let end = clip_end.min(line_max);
2081
2082                        out_lines.push((PxPoint::new(start, origin.y), end - start));
2083                    }
2084
2085                    include_buf.clear();
2086                }
2087            }
2088        }
2089
2090        out_lines
2091    }
2092}
2093
2094struct ImageGlyphsIter<'a, G>
2095where
2096    G: Iterator<Item = (&'a Font, &'a [GlyphInstance])> + 'a,
2097{
2098    glyphs: G,
2099    glyphs_i: u32,
2100    images: &'a [(u32, GlyphImage)],
2101    maybe_img: Option<(&'a Font, &'a [GlyphInstance])>,
2102}
2103impl<'a, G> Iterator for ImageGlyphsIter<'a, G>
2104where
2105    G: Iterator<Item = (&'a Font, &'a [GlyphInstance])> + 'a,
2106{
2107    type Item = (&'a Font, ShapedImageGlyphs<'a>);
2108
2109    fn next(&mut self) -> Option<Self::Item> {
2110        loop {
2111            if let Some((font, glyphs)) = &mut self.maybe_img {
2112                // new glyph sequence or single emoji(maybe img)
2113
2114                // advance images to the next in or after glyph sequence
2115                while self.images.first().map(|(i, _)| *i < self.glyphs_i).unwrap_or(false) {
2116                    self.images = &self.images[1..];
2117                }
2118
2119                if let Some((i, img)) = self.images.first() {
2120                    // if there is still images
2121                    if *i == self.glyphs_i {
2122                        // if the next glyph is replaced by image
2123                        self.glyphs_i += 1;
2124                        let mut size = img.0.with(|i| i.size()).cast::<f32>();
2125                        let scale = font.size().0 as f32 / size.width.max(size.height);
2126                        size *= scale;
2127                        let r = (
2128                            *font,
2129                            ShapedImageGlyphs::Image {
2130                                rect: euclid::Rect::new(glyphs[0].point - euclid::vec2(0.0, size.height), size),
2131                                base_glyph: glyphs[0].index,
2132                                img: &img.0,
2133                            },
2134                        );
2135                        *glyphs = &glyphs[1..];
2136                        if glyphs.is_empty() {
2137                            self.maybe_img = None;
2138                        }
2139                        return Some(r);
2140                    } else {
2141                        // if the next glyph is not replaced by image, yield slice to end or next image
2142                        let normal = &glyphs[..glyphs.len().min(*i as _)];
2143                        self.glyphs_i += normal.len() as u32;
2144
2145                        *glyphs = &glyphs[normal.len()..];
2146                        let r = (*font, ShapedImageGlyphs::Normal(normal));
2147
2148                        if glyphs.is_empty() {
2149                            self.maybe_img = None;
2150                        }
2151                        return Some(r);
2152                    }
2153                } else {
2154                    // if there are no more images
2155                    let r = (*font, ShapedImageGlyphs::Normal(glyphs));
2156                    self.maybe_img = None;
2157                    return Some(r);
2158                }
2159            } else {
2160                let seq = self.glyphs.next()?;
2161                // all sequences can contain images
2162                self.maybe_img = Some(seq);
2163            }
2164        }
2165    }
2166}
2167
2168struct ColoredGlyphsIter<'a, G>
2169where
2170    G: Iterator<Item = (&'a Font, &'a [GlyphInstance])> + 'a,
2171{
2172    glyphs: G,
2173    maybe_colored: Option<(&'a Font, &'a [GlyphInstance])>,
2174}
2175impl<'a, G> Iterator for ColoredGlyphsIter<'a, G>
2176where
2177    G: Iterator<Item = (&'a Font, &'a [GlyphInstance])> + 'a,
2178{
2179    type Item = (&'a Font, ShapedColoredGlyphs<'a>);
2180
2181    fn next(&mut self) -> Option<Self::Item> {
2182        loop {
2183            if let Some((font, glyphs)) = self.maybe_colored {
2184                // maybe-colored iter
2185
2186                let color_glyphs = font.face().color_glyphs();
2187
2188                for (i, g) in glyphs.iter().enumerate() {
2189                    if let Some(c_glyphs) = color_glyphs.glyph(g.index) {
2190                        // colored yield
2191
2192                        let next_start = i + 1;
2193                        if next_start < glyphs.len() {
2194                            // continue maybe-colored iter
2195                            self.maybe_colored = Some((font, &glyphs[next_start..]));
2196                        } else {
2197                            // continue normal iter
2198                            self.maybe_colored = None;
2199                        }
2200
2201                        return Some((
2202                            font,
2203                            ShapedColoredGlyphs::Colored {
2204                                point: g.point,
2205                                base_glyph: g.index,
2206                                glyphs: c_glyphs,
2207                            },
2208                        ));
2209                    }
2210                }
2211                // enter normal iter
2212                self.maybe_colored = None;
2213
2214                // last normal in maybe-colored yield
2215                debug_assert!(!glyphs.is_empty());
2216                return Some((font, ShapedColoredGlyphs::Normal(glyphs)));
2217            } else if let Some((font, glyphs)) = self.glyphs.next() {
2218                // normal iter
2219
2220                let color_glyphs = font.face().color_glyphs();
2221                if color_glyphs.is_empty() {
2222                    return Some((font, ShapedColoredGlyphs::Normal(glyphs)));
2223                } else {
2224                    // enter maybe-colored iter
2225                    self.maybe_colored = Some((font, glyphs));
2226                    continue;
2227                }
2228            } else {
2229                return None;
2230            }
2231        }
2232    }
2233}
2234
2235/// Info about a shaped text overflow in constraint.
2236///
2237/// Can be computed using [`ShapedText::overflow_info`].
2238#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2239#[non_exhaustive]
2240pub struct TextOverflowInfo {
2241    /// First overflow line.
2242    ///
2243    /// All segments in this line and next lines are fully overflown. The previous line may
2244    /// be partially overflown, the lines before that are fully visible.
2245    ///
2246    /// Is the [`ShapedText::lines_len`] if the last line is fully visible.
2247    pub line: usize,
2248
2249    /// First overflow character in the text.
2250    ///
2251    /// Note that if overflow is not wrapping (single text line) the char may not cover all visible
2252    /// glyphs in the line if it is bidirectional.
2253    pub text_char: usize,
2254
2255    /// Glyphs not overflown in the last not overflown line.
2256    ///
2257    /// If the line is not bidirectional this will be a single range covering the not overflow glyphs,
2258    /// if it is bidi multiple ranges are possible due to bidi reordering.
2259    pub included_glyphs: smallvec::SmallVec<[ops::Range<usize>; 1]>,
2260
2261    /// Placement of the suffix (ellipses or custom).
2262    ///
2263    /// The suffix must be of the width given to [`ShapedText::overflow_info`] and the same line height
2264    /// as the text.
2265    pub suffix_origin: euclid::Point2D<f32, Px>,
2266}
2267
2268trait FontListRef {
2269    /// Shape segment, try fallback fonts if a glyph in the segment is not resolved.
2270    fn shape_segment<R>(
2271        &self,
2272        seg: &str,
2273        word_ctx_key: &WordContextKey,
2274        features: &[harfrust::Feature],
2275        out: impl FnOnce(&ShapedSegmentData, &Font) -> R,
2276    ) -> R;
2277}
2278impl FontListRef for [Font] {
2279    fn shape_segment<R>(
2280        &self,
2281        seg: &str,
2282        word_ctx_key: &WordContextKey,
2283        features: &[harfrust::Feature],
2284        out: impl FnOnce(&ShapedSegmentData, &Font) -> R,
2285    ) -> R {
2286        let mut out = Some(out);
2287        let last = self.len() - 1;
2288        for font in &self[..last] {
2289            let r = font.shape_segment(seg, word_ctx_key, features, |seg| {
2290                if seg.glyphs.iter().all(|g| g.index != 0) {
2291                    Some(out.take().unwrap()(seg, font))
2292                } else {
2293                    None
2294                }
2295            });
2296            if let Some(r) = r {
2297                return r;
2298            }
2299        }
2300        self[last].shape_segment(seg, word_ctx_key, features, move |seg| out.unwrap()(seg, &self[last]))
2301    }
2302}
2303
2304struct ShapedTextBuilder {
2305    out: ShapedText,
2306
2307    line_height: f32,
2308    line_spacing: f32,
2309    paragraph_spacing: f32,
2310    word_spacing: f32,
2311    letter_spacing: f32,
2312    paragraph_indent: (f32, bool),
2313    max_width: f32,
2314    break_words: bool,
2315    hyphen_glyphs: (ShapedSegmentData, Font),
2316    tab_x_advance: f32,
2317    tab_index: u32,
2318    hyphens: Hyphens,
2319    lang: Lang,
2320
2321    origin: euclid::Point2D<f32, ()>,
2322    allow_first_wrap: bool,
2323    first_line_max: f32,
2324    mid_clear_min: f32,
2325    max_line_x: f32,
2326    text_seg_end: usize,
2327    line_has_ltr: bool,
2328    line_has_rtl: bool,
2329}
2330impl ShapedTextBuilder {
2331    fn actual_max_width(&self) -> f32 {
2332        if self.out.lines.0.is_empty() && !self.out.first_wrapped {
2333            self.first_line_max.min(self.max_width)
2334        } else {
2335            self.max_width
2336        }
2337    }
2338
2339    fn shape_text(fonts: &[Font], text: &SegmentedText, config: &TextShapingArgs) -> ShapedText {
2340        let _span = tracing::trace_span!("shape_text").entered();
2341        let mut t = Self {
2342            out: ShapedText {
2343                glyphs: Default::default(),
2344                clusters: Default::default(),
2345                segments: Default::default(),
2346                lines: Default::default(),
2347                fonts: Default::default(),
2348                line_height: Default::default(),
2349                line_spacing: Default::default(),
2350                orig_line_height: Default::default(),
2351                orig_line_spacing: Default::default(),
2352                orig_first_line: Default::default(),
2353                orig_last_line: Default::default(),
2354                paragraph_break: Default::default(),
2355                paragraph_spacing: Default::default(),
2356                baseline: Default::default(),
2357                overline: Default::default(),
2358                strikethrough: Default::default(),
2359                underline: Default::default(),
2360                underline_descent: Default::default(),
2361                mid_offset: 0.0,
2362                align_size: PxSize::zero(),
2363                align: Align::TOP_LEFT,
2364                justify: Justify::Auto,
2365                justified: vec![],
2366                overflow_align: Align::TOP_LEFT,
2367                direction: LayoutDirection::LTR,
2368                first_wrapped: false,
2369                is_inlined: config.inline_constraints.is_some(),
2370                first_line: PxRect::zero(),
2371                mid_clear: Px(0),
2372                mid_size: PxSize::zero(),
2373                last_line: PxRect::zero(),
2374                has_colored_glyphs: false,
2375                images: vec![],
2376            },
2377
2378            line_height: 0.0,
2379            line_spacing: 0.0,
2380            paragraph_spacing: 0.0,
2381            word_spacing: 0.0,
2382            letter_spacing: 0.0,
2383            paragraph_indent: (0.0, false),
2384            max_width: 0.0,
2385            break_words: false,
2386            hyphen_glyphs: (ShapedSegmentData::default(), fonts[0].clone()),
2387            tab_x_advance: 0.0,
2388            tab_index: 0,
2389            hyphens: config.hyphens,
2390            lang: config.lang.clone(),
2391            allow_first_wrap: false,
2392
2393            origin: euclid::point2(0.0, 0.0),
2394            first_line_max: f32::INFINITY,
2395            mid_clear_min: 0.0,
2396            max_line_x: 0.0,
2397            text_seg_end: 0,
2398            line_has_ltr: false,
2399            line_has_rtl: false,
2400        };
2401
2402        let mut word_ctx_key = WordContextKey::new(&config.lang, config.direction, &config.font_features);
2403
2404        let metrics = fonts[0].metrics();
2405
2406        t.out.orig_line_height = config.line_height;
2407        t.out.orig_line_spacing = config.line_spacing;
2408        t.out.line_height = config.line_height;
2409        t.out.line_spacing = config.line_spacing;
2410        t.out.paragraph_spacing = config.paragraph_spacing;
2411        t.out.paragraph_break = config.paragraph_break;
2412
2413        t.line_height = config.line_height.0 as f32;
2414        t.line_spacing = config.line_spacing.0 as f32;
2415        t.paragraph_spacing = config.paragraph_spacing.0 as f32;
2416        t.paragraph_indent = (config.paragraph_indent.0.0 as f32, config.paragraph_indent.1);
2417        let baseline = metrics.ascent + metrics.line_gap / 2.0;
2418
2419        t.out.baseline = t.out.line_height - baseline;
2420        t.out.underline = t.out.baseline + metrics.underline_position;
2421        t.out.underline_descent = t.out.baseline + metrics.descent + Px(1);
2422        t.out.strikethrough = t.out.baseline + metrics.ascent / 3.0;
2423        t.out.overline = t.out.baseline + metrics.ascent;
2424
2425        let dft_line_height = metrics.line_height().0 as f32;
2426        let center_height = (t.line_height - dft_line_height) / 2.0;
2427
2428        t.origin = euclid::point2::<_, ()>(0.0, baseline.0 as f32 + center_height);
2429        if !t.paragraph_indent.1 {
2430            // not invert
2431            t.origin.x = t.paragraph_indent.0;
2432        }
2433
2434        t.max_line_x = 0.0;
2435        if let Some(inline) = config.inline_constraints {
2436            t.first_line_max = inline.first_max.0 as f32;
2437            t.mid_clear_min = inline.mid_clear_min.0 as f32;
2438            t.allow_first_wrap = true;
2439        } else {
2440            t.first_line_max = f32::INFINITY;
2441            t.mid_clear_min = 0.0;
2442            t.allow_first_wrap = false;
2443        }
2444
2445        t.letter_spacing = config.letter_spacing.0 as f32;
2446        t.word_spacing = config.word_spacing.0 as f32;
2447        t.tab_x_advance = config.tab_x_advance.0 as f32;
2448        t.tab_index = fonts[0].space_index();
2449
2450        t.max_width = if config.max_width == Px::MAX {
2451            f32::INFINITY
2452        } else {
2453            config.max_width.0 as f32
2454        };
2455
2456        t.break_words = match config.word_break {
2457            WordBreak::Normal => {
2458                lang!("ch").matches(&config.lang, true, false)
2459                    || lang!("jp").matches(&config.lang, true, false)
2460                    || lang!("ko").matches(&config.lang, true, false)
2461            }
2462            WordBreak::BreakAll => true,
2463            WordBreak::KeepAll => false,
2464        };
2465
2466        if !matches!(config.hyphens, Hyphens::None) && t.max_width.is_finite() && config.obscuring_char.is_none() {
2467            // "hyphen" can be any char and we need the x-advance for the wrap algorithm.
2468            t.hyphen_glyphs = fonts.shape_segment(config.hyphen_char.as_str(), &word_ctx_key, &config.font_features, |s, f| {
2469                (s.clone(), f.clone())
2470            });
2471        }
2472
2473        if let Some(c) = config.obscuring_char {
2474            t.push_obscured_text(fonts, &config.font_features, &mut word_ctx_key, text, c);
2475        } else {
2476            t.push_text(fonts, &config.font_features, &mut word_ctx_key, text);
2477        }
2478
2479        t.out.glyphs.shrink_to_fit();
2480        t.out.clusters.shrink_to_fit();
2481        t.out.segments.0.shrink_to_fit();
2482        t.out.lines.0.shrink_to_fit();
2483        t.out.fonts.0.shrink_to_fit();
2484        t.out.images.shrink_to_fit();
2485
2486        t.out.debug_assert_ranges();
2487        t.out
2488    }
2489
2490    fn push_obscured_text(
2491        &mut self,
2492        fonts: &[Font],
2493        features: &RFontFeatures,
2494        word_ctx_key: &mut WordContextKey,
2495        text: &SegmentedText,
2496        obscuring_char: char,
2497    ) {
2498        if text.is_empty() {
2499            self.push_last_line(text);
2500            self.push_font(&fonts[0]);
2501            return;
2502        }
2503
2504        let (glyphs, font) = fonts.shape_segment(Txt::from_char(obscuring_char).as_str(), word_ctx_key, features, |s, f| {
2505            (s.clone(), f.clone())
2506        });
2507
2508        for (seg, info) in text.iter() {
2509            let mut seg_glyphs = ShapedSegmentData::default();
2510            for (cluster, _) in seg.char_indices() {
2511                let i = seg_glyphs.glyphs.len();
2512                seg_glyphs.glyphs.extend(glyphs.glyphs.iter().copied());
2513                for g in &mut seg_glyphs.glyphs[i..] {
2514                    g.point.0 += seg_glyphs.x_advance;
2515                    g.cluster = cluster as u32;
2516                }
2517                seg_glyphs.x_advance += glyphs.x_advance;
2518            }
2519            self.push_glyphs(&seg_glyphs, self.letter_spacing);
2520            self.push_text_seg(seg, info);
2521        }
2522
2523        self.push_last_line(text);
2524
2525        self.push_font(&font);
2526    }
2527
2528    fn push_text(&mut self, fonts: &[Font], features: &RFontFeatures, word_ctx_key: &mut WordContextKey, text: &SegmentedText) {
2529        static LIG: [&[u8]; 4] = [b"liga", b"clig", b"dlig", b"hlig"];
2530        let ligature_enabled = fonts[0].face().has_ligatures()
2531            && features.iter().any(|f| {
2532                let tag = f.tag.to_be_bytes();
2533                LIG.iter().any(|l| *l == tag)
2534            });
2535
2536        let push_words_fn = if ligature_enabled {
2537            Self::push_word_segs_with_lig
2538        } else {
2539            Self::push_word_segs
2540        };
2541
2542        // iterate by groups of is_word
2543        let mut info_start = 0;
2544        let mut words_start = None;
2545        for (i, info) in text.segs().iter().enumerate() {
2546            if info.kind.is_word() {
2547                if words_start.is_none() {
2548                    words_start = Some(i);
2549                }
2550            } else {
2551                if let Some(s) = words_start.take() {
2552                    // push is_word group
2553                    push_words_fn(self, fonts, features, word_ctx_key, text, s, i);
2554                }
2555
2556                // push !_is_word
2557                let seg = &text.text()[info_start..info.end];
2558                self.push_single_seg(fonts, features, word_ctx_key, text, seg, *info);
2559            }
2560            info_start = info.end;
2561        }
2562        if let Some(s) = words_start.take() {
2563            // push is_word group
2564            push_words_fn(self, fonts, features, word_ctx_key, text, s, text.segs().len());
2565        }
2566        self.push_last_line(text);
2567
2568        self.push_font(&fonts[0]);
2569    }
2570    fn push_word_segs_with_lig(
2571        &mut self,
2572        fonts: &[Font],
2573        features: &RFontFeatures,
2574        word_ctx_key: &mut WordContextKey,
2575        text: &SegmentedText,
2576        words_start: usize,
2577        words_end: usize,
2578    ) {
2579        let seg_start = if words_start == 0 { 0 } else { text.segs()[words_start - 1].end };
2580        let end_info = text.segs()[words_end - 1];
2581        let seg_end = end_info.end;
2582        let seg = &text.text()[seg_start..seg_end];
2583
2584        if words_end - words_start == 1 {
2585            return self.push_single_seg(fonts, features, word_ctx_key, text, seg, end_info);
2586        }
2587
2588        // check if `is_word` sequence is a ligature that covers more than one word.
2589        let handled = fonts[0].shape_segment(seg, word_ctx_key, features, |shaped_seg| {
2590            let mut cluster_start = 0;
2591            let mut cluster_end = None;
2592            for g in shaped_seg.glyphs.iter() {
2593                if g.index == 0 {
2594                    // top font not used for at least one word in this sequence
2595                    return false;
2596                }
2597                if seg[cluster_start as usize..g.cluster as usize].chars().take(2).count() > 1 {
2598                    cluster_end = Some(g.index);
2599                    break;
2600                }
2601                cluster_start = g.cluster;
2602            }
2603
2604            if cluster_end.is_none() && seg[cluster_start as usize..].chars().take(2).count() > 1 {
2605                cluster_end = Some(seg.len() as u32);
2606            }
2607            if let Some(cluster_end) = cluster_end {
2608                // previous glyph is a ligature, check word boundaries.
2609                let cluster_start_in_txt = seg_start + cluster_start as usize;
2610                let cluster_end_in_txt = seg_start + cluster_end as usize;
2611
2612                let handle = text.segs()[words_start..words_end]
2613                    .iter()
2614                    .any(|info| info.end > cluster_start_in_txt && info.end <= cluster_end_in_txt);
2615
2616                if handle {
2617                    let max_width = self.actual_max_width();
2618                    if self.origin.x + shaped_seg.x_advance > max_width {
2619                        // need wrap
2620                        if shaped_seg.x_advance > max_width {
2621                            // need segment split
2622                            return false;
2623                        }
2624
2625                        self.push_line_break(true, text);
2626                        self.push_glyphs(shaped_seg, self.letter_spacing);
2627                    }
2628                    self.push_glyphs(shaped_seg, self.letter_spacing);
2629                    let mut seg = seg;
2630                    for info in &text.segs()[words_start..words_end] {
2631                        self.push_text_seg(seg, *info);
2632                        seg = "";
2633                    }
2634
2635                    return true;
2636                }
2637            }
2638
2639            false
2640        });
2641
2642        if !handled {
2643            self.push_word_segs(fonts, features, word_ctx_key, text, words_start, words_end);
2644        }
2645    }
2646    fn push_word_segs(
2647        &mut self,
2648        fonts: &[Font],
2649        features: &RFontFeatures,
2650        word_ctx_key: &mut WordContextKey,
2651        text: &SegmentedText,
2652        words_start: usize,
2653        words_end: usize,
2654    ) {
2655        let seg_start = if words_start == 0 { 0 } else { text.segs()[words_start - 1].end };
2656        let end_info = text.segs()[words_end - 1];
2657        let seg_end = end_info.end;
2658        let seg = &text.text()[seg_start..seg_end];
2659
2660        if words_end - words_start == 1 {
2661            return self.push_single_seg(fonts, features, word_ctx_key, text, seg, end_info);
2662        }
2663
2664        let word_segs = move || {
2665            let mut seg_start = seg_start;
2666            (words_start..words_end).map(move |i| {
2667                let info = text.segs()[i];
2668                let seg = &text.text()[seg_start..info.end];
2669                seg_start = info.end;
2670
2671                (seg, info)
2672            })
2673        };
2674
2675        let max_width = self.actual_max_width();
2676        let mut line_break = false;
2677        let mut x_advance = 0.0;
2678        for (seg, info) in word_segs() {
2679            debug_assert!(info.kind.is_word());
2680            word_ctx_key.direction = info.direction();
2681            fonts.shape_segment(seg, word_ctx_key, features, |shaped_seg, _| {
2682                x_advance += shaped_seg.x_advance;
2683            });
2684            if self.origin.x + x_advance > max_width {
2685                // need wrap
2686                if x_advance > max_width {
2687                    // need word segments split
2688                    for (seg, info) in word_segs() {
2689                        self.push_single_seg(fonts, features, word_ctx_key, text, seg, info);
2690                    }
2691                    return;
2692                }
2693                // else
2694                line_break = true;
2695            }
2696        }
2697
2698        if line_break {
2699            self.push_line_break(true, text);
2700        }
2701        for (seg, info) in word_segs() {
2702            word_ctx_key.direction = info.direction();
2703            fonts.shape_segment(seg, word_ctx_key, features, |shaped_seg, font| {
2704                self.push_glyphs(shaped_seg, self.letter_spacing);
2705                self.push_text_seg(seg, info);
2706                if matches!(info.kind, TextSegmentKind::Emoji) {
2707                    self.push_emoji_seg(shaped_seg, font);
2708                }
2709                self.push_font(font);
2710            });
2711        }
2712    }
2713    fn push_single_seg(
2714        &mut self,
2715        fonts: &[Font],
2716        features: &RFontFeatures,
2717        word_ctx_key: &mut WordContextKey,
2718        text: &SegmentedText,
2719        seg: &str,
2720        info: TextSegment,
2721    ) {
2722        word_ctx_key.direction = info.direction();
2723        if info.kind.is_word() {
2724            let max_width = self.actual_max_width();
2725
2726            fonts.shape_segment(seg, word_ctx_key, features, |shaped_seg, font| {
2727                if self.origin.x + shaped_seg.x_advance > max_width {
2728                    // need wrap
2729                    if shaped_seg.x_advance > max_width {
2730                        // need segment split
2731
2732                        // try to hyphenate
2733                        let hyphenated = self.push_hyphenate(seg, font, shaped_seg, info, text);
2734
2735                        if !hyphenated && self.break_words {
2736                            // break word
2737                            self.push_split_seg(shaped_seg, seg, info, self.letter_spacing, text);
2738                        } else if !hyphenated {
2739                            let current_start = if self.out.lines.0.is_empty() {
2740                                0
2741                            } else {
2742                                self.out.lines.last().end
2743                            };
2744                            if !self.out.segments.0[current_start..].is_empty() {
2745                                self.push_line_break(true, text);
2746                            } else if current_start == 0 && self.allow_first_wrap {
2747                                self.out.first_wrapped = true;
2748                            }
2749                            self.push_glyphs(shaped_seg, self.letter_spacing);
2750                            self.push_text_seg(seg, info);
2751                        }
2752                    } else {
2753                        self.push_line_break(true, text);
2754                        self.push_glyphs(shaped_seg, self.letter_spacing);
2755                        self.push_text_seg(seg, info);
2756                    }
2757                } else {
2758                    // don't need wrap
2759                    self.push_glyphs(shaped_seg, self.letter_spacing);
2760                    self.push_text_seg(seg, info);
2761                }
2762
2763                if matches!(info.kind, TextSegmentKind::Emoji) {
2764                    self.push_emoji_seg(shaped_seg, font);
2765                }
2766
2767                self.push_font(font);
2768            });
2769        } else if info.kind.is_space() {
2770            if matches!(info.kind, TextSegmentKind::Tab) {
2771                let max_width = self.actual_max_width();
2772                for (i, _) in seg.char_indices() {
2773                    if self.origin.x + self.tab_x_advance > max_width {
2774                        // normal wrap, advance overflow
2775                        self.push_line_break(true, text);
2776                    }
2777                    let point = euclid::point2(self.origin.x, self.origin.y);
2778                    self.origin.x += self.tab_x_advance;
2779                    self.out.glyphs.push(GlyphInstance::new(self.tab_index, point));
2780                    self.out.clusters.push(i as u32);
2781                }
2782
2783                self.push_text_seg(seg, info);
2784                self.push_font(&fonts[0]);
2785            } else {
2786                let max_width = self.actual_max_width();
2787                fonts.shape_segment(seg, word_ctx_key, features, |shaped_seg, font| {
2788                    if self.origin.x + shaped_seg.x_advance > max_width {
2789                        // need wrap
2790                        if seg.len() > 2 {
2791                            // split spaces
2792                            self.push_split_seg(shaped_seg, seg, info, self.word_spacing, text);
2793                        } else {
2794                            self.push_line_break(true, text);
2795                            self.push_glyphs(shaped_seg, self.word_spacing);
2796                            self.push_text_seg(seg, info);
2797                        }
2798                    } else {
2799                        self.push_glyphs(shaped_seg, self.word_spacing);
2800                        self.push_text_seg(seg, info);
2801                    }
2802
2803                    self.push_font(font);
2804                });
2805            }
2806        } else if info.kind.is_line_break() {
2807            self.push_text_seg(seg, info);
2808            self.push_line_break(false, text);
2809        } else {
2810            self.push_text_seg(seg, info)
2811        }
2812    }
2813    fn push_emoji_seg(&mut self, shaped_seg: &ShapedSegmentData, font: &Font) {
2814        if !font.face().color_glyphs().is_empty() {
2815            self.out.has_colored_glyphs = true;
2816        }
2817        if (font.face().has_raster_images() || (cfg!(feature = "svg") && font.face().has_svg_images()))
2818            && let Some(ttf) = font.face().raw()
2819        {
2820            for (i, g) in shaped_seg.glyphs.iter().enumerate() {
2821                use read_fonts::TableProvider as _;
2822                use skrifa::MetadataProvider as _;
2823
2824                let id = skrifa::GlyphId::new(g.index as _);
2825                let ppm = skrifa::instance::Size::new(font.size().0 as f32);
2826                let glyphs_i = self.out.glyphs.len() - shaped_seg.glyphs.len() + i;
2827                if let Some(img) = ttf.bitmap_strikes().glyph_for_size(ppm, id) {
2828                    self.push_glyph_raster(glyphs_i as _, img);
2829                } else if cfg!(feature = "svg")
2830                    && let Ok(img) = ttf.svg()
2831                    && let Ok(Some(img)) = img.glyph_data(id)
2832                {
2833                    self.push_glyph_svg(glyphs_i as _, img);
2834                }
2835            }
2836        }
2837    }
2838
2839    fn push_glyph_raster(&mut self, glyphs_i: u32, img: skrifa::bitmap::BitmapGlyph) {
2840        let size = PxSize::new(Px(img.width as _), Px(img.height as _));
2841
2842        let (data, fmt) = match img.data {
2843            skrifa::bitmap::BitmapData::Bgra(d) => {
2844                let mut bgra = d.to_vec();
2845                for c in bgra.chunks_exact_mut(4) {
2846                    let (b, g, r, a) = (c[0], c[1], c[2], c[3]);
2847                    let unp = if a == 255 {
2848                        [b, g, r]
2849                    } else {
2850                        [
2851                            (b as u32 * 255 / a as u32) as u8,
2852                            (g as u32 * 255 / a as u32) as u8,
2853                            (r as u32 * 255 / a as u32) as u8,
2854                        ]
2855                    };
2856                    c.copy_from_slice(&unp);
2857                }
2858                let bgra_fmt = ImageDataFormat::Bgra8 {
2859                    size,
2860                    density: None,
2861                    original_color_type: ColorType::BGRA8,
2862                };
2863                (bgra, bgra_fmt)
2864            }
2865            skrifa::bitmap::BitmapData::Png(d) => (d.to_vec(), ImageDataFormat::from("png")),
2866            skrifa::bitmap::BitmapData::Mask(d) => {
2867                let d = match d.decode(img.width, img.height) {
2868                    Ok(g) => g,
2869                    Err(e) => {
2870                        tracing::error!("cannot decode glyph bitmap mask, {e:?}");
2871                        vec![0; img.width as usize * img.height as usize]
2872                    }
2873                };
2874                (d, ImageDataFormat::A8 { size })
2875            }
2876        };
2877        self.push_glyph_img(glyphs_i, ImageSource::from((data, fmt)));
2878    }
2879
2880    fn push_glyph_svg(&mut self, glyphs_i: u32, img: &[u8]) {
2881        self.push_glyph_img(glyphs_i, ImageSource::from((img, ImageDataFormat::from("svg"))));
2882    }
2883
2884    fn push_glyph_img(&mut self, glyphs_i: u32, source: ImageSource) {
2885        let img = IMAGES.image(source, ImageOptions::cache(), None);
2886
2887        self.out.images.push((glyphs_i, GlyphImage(img)));
2888    }
2889
2890    fn push_last_line(&mut self, text: &SegmentedText) {
2891        let directions = self.finish_current_line_bidi(text);
2892        self.out.lines.0.push(LineRange {
2893            end: self.out.segments.0.len(),
2894            width: self.origin.x,
2895            x_offset: 0.0,
2896            directions,
2897        });
2898
2899        self.out.update_mid_size();
2900        self.out.update_first_last_lines();
2901        self.out.orig_first_line = self.out.first_line.size;
2902        self.out.orig_last_line = self.out.last_line.size;
2903        if self.out.is_inlined && self.out.lines.0.len() > 1 {
2904            self.out.last_line.origin.y += self.out.mid_clear;
2905        }
2906    }
2907
2908    fn push_hyphenate(&mut self, seg: &str, font: &Font, shaped_seg: &ShapedSegmentData, info: TextSegment, text: &SegmentedText) -> bool {
2909        if !matches!(self.hyphens, Hyphens::Auto) {
2910            return false;
2911        }
2912
2913        let split_points = HYPHENATION.hyphenate(&self.lang, seg);
2914        self.push_hyphenate_pt(&split_points, 0, font, shaped_seg, seg, info, text)
2915    }
2916
2917    #[allow(clippy::too_many_arguments)]
2918    fn push_hyphenate_pt(
2919        &mut self,
2920        split_points: &[usize],
2921        split_points_sub: usize,
2922        font: &Font,
2923        shaped_seg: &ShapedSegmentData,
2924        seg: &str,
2925        info: TextSegment,
2926        text: &SegmentedText,
2927    ) -> bool {
2928        if split_points.is_empty() {
2929            return false;
2930        }
2931
2932        // find the split that fits more letters and hyphen
2933        let mut end_glyph = 0;
2934        let mut end_point_i = 0;
2935        let max_width = self.actual_max_width();
2936        for (i, point) in split_points.iter().enumerate() {
2937            let mut point = *point - split_points_sub;
2938            let mut width = 0.0;
2939            let mut c = u32::MAX;
2940            let mut gi = 0;
2941            // find the first glyph in the cluster at the char byte index `point`
2942            for (i, g) in shaped_seg.glyphs.iter().enumerate() {
2943                width = g.point.0;
2944                if g.cluster != c {
2945                    // advanced cluster, advance point
2946                    if point == 0 {
2947                        break;
2948                    }
2949                    c = g.cluster;
2950                    point -= 1;
2951                }
2952                gi = i;
2953            }
2954
2955            if self.origin.x + width + self.hyphen_glyphs.0.x_advance > max_width {
2956                // fragment+hyphen is to large
2957                if end_glyph == 0 {
2958                    // found no candidate, there is no way to avoid overflow, use smallest option
2959                    end_glyph = gi + 1;
2960                    end_point_i = i + 1;
2961                }
2962                break;
2963            } else {
2964                // found candidate fragment
2965                end_glyph = gi + 1;
2966                end_point_i = i + 1;
2967            }
2968        }
2969
2970        // split and push the first half + hyphen
2971        let end_glyph_x = shaped_seg.glyphs[end_glyph].point.0;
2972        let (glyphs_a, glyphs_b) = shaped_seg.glyphs.split_at(end_glyph);
2973
2974        if glyphs_a.is_empty() || glyphs_b.is_empty() {
2975            debug_assert!(false, "invalid hyphenation split");
2976            return false;
2977        }
2978        let end_cluster = glyphs_b[0].cluster;
2979        let (seg_a, seg_b) = seg.split_at(end_cluster as usize);
2980
2981        self.push_glyphs(
2982            &ShapedSegmentData {
2983                glyphs: glyphs_a.to_vec(),
2984                x_advance: end_glyph_x,
2985                y_advance: glyphs_a.iter().map(|g| g.point.1).sum(),
2986            },
2987            self.word_spacing,
2988        );
2989        self.push_font(font);
2990
2991        self.push_glyphs(&self.hyphen_glyphs.0.clone(), 0.0);
2992        self.push_font(&self.hyphen_glyphs.1.clone());
2993
2994        self.push_text_seg(seg_a, info);
2995
2996        self.push_line_break(true, text);
2997
2998        // adjust the second half to a new line
2999        let mut shaped_seg_b = ShapedSegmentData {
3000            glyphs: glyphs_b.to_vec(),
3001            x_advance: shaped_seg.x_advance - end_glyph_x,
3002            y_advance: glyphs_b.iter().map(|g| g.point.1).sum(),
3003        };
3004        for g in &mut shaped_seg_b.glyphs {
3005            g.point.0 -= end_glyph_x;
3006            g.cluster -= seg_a.len() as u32;
3007        }
3008
3009        if shaped_seg_b.x_advance > self.actual_max_width() {
3010            // second half still does not fit, try to hyphenate again.
3011            if self.push_hyphenate_pt(
3012                &split_points[end_point_i..],
3013                split_points_sub + seg_a.len(),
3014                font,
3015                &shaped_seg_b,
3016                seg_b,
3017                info,
3018                text,
3019            ) {
3020                return true;
3021            }
3022        }
3023
3024        // push second half
3025        self.push_glyphs(&shaped_seg_b, self.word_spacing);
3026        self.push_text_seg(seg_b, info);
3027        true
3028    }
3029
3030    fn push_glyphs(&mut self, shaped_seg: &ShapedSegmentData, spacing: f32) {
3031        self.out.glyphs.extend(shaped_seg.glyphs.iter().map(|gi| {
3032            let r = GlyphInstance::new(gi.index, euclid::point2(gi.point.0 + self.origin.x, gi.point.1 + self.origin.y));
3033            self.origin.x += spacing;
3034            r
3035        }));
3036        self.out.clusters.extend(shaped_seg.glyphs.iter().map(|gi| gi.cluster));
3037
3038        self.origin.x += shaped_seg.x_advance;
3039        self.origin.y += shaped_seg.y_advance;
3040    }
3041
3042    fn push_line_break(&mut self, soft: bool, text: &SegmentedText) {
3043        if self.out.glyphs.is_empty() && self.allow_first_wrap && soft {
3044            self.out.first_wrapped = true;
3045        } else {
3046            let directions = self.finish_current_line_bidi(text);
3047
3048            self.out.lines.0.push(LineRange {
3049                end: self.out.segments.0.len(),
3050                width: self.origin.x,
3051                x_offset: 0.0,
3052                directions,
3053            });
3054
3055            if self.out.lines.0.len() == 1 {
3056                self.out.first_line = PxRect::from_size(PxSize::new(Px(self.origin.x as i32), Px(self.line_height as i32)));
3057
3058                if !self.out.first_wrapped {
3059                    let mid_clear = (self.mid_clear_min - self.line_height).max(0.0).round();
3060                    self.origin.y += mid_clear;
3061                    self.out.mid_clear = Px(mid_clear as i32);
3062                    self.out.mid_offset = mid_clear;
3063                }
3064            }
3065
3066            self.max_line_x = self.origin.x.max(self.max_line_x);
3067
3068            let is_paragraph_break = match self.out.paragraph_break {
3069                ParagraphBreak::None => false,
3070                ParagraphBreak::Line => !soft,
3071            };
3072
3073            self.origin.x = if self.paragraph_indent.1 != is_paragraph_break {
3074                // first_line && !invert_indent || !first_line && invert_indent
3075                self.paragraph_indent.0
3076            } else {
3077                0.0
3078            };
3079
3080            self.origin.y += if is_paragraph_break {
3081                self.paragraph_spacing
3082            } else {
3083                self.line_height + self.line_spacing
3084            };
3085        }
3086    }
3087
3088    #[must_use]
3089    fn finish_current_line_bidi(&mut self, text: &SegmentedText) -> LayoutDirections {
3090        if self.line_has_rtl {
3091            let seg_start = if self.out.lines.0.is_empty() {
3092                0
3093            } else {
3094                self.out.lines.last().end
3095            };
3096
3097            if self.line_has_ltr {
3098                // mixed direction
3099
3100                let line_segs = seg_start..self.out.segments.0.len();
3101
3102                // compute visual order and offset segments.
3103                let mut x = 0.0;
3104                for i in text.reorder_line_to_ltr(line_segs) {
3105                    let g_range = self.out.segments.glyphs(i);
3106                    if g_range.iter().is_empty() {
3107                        continue;
3108                    }
3109
3110                    let glyphs = &mut self.out.glyphs[g_range.iter()];
3111                    let offset = x - self.out.segments.0[i].x;
3112                    self.out.segments.0[i].x = x;
3113                    for g in glyphs {
3114                        g.point.x += offset;
3115                    }
3116                    x += self.out.segments.0[i].advance;
3117                }
3118            } else {
3119                // entire line RTL
3120                let line_width = self.origin.x;
3121
3122                let mut x = line_width;
3123                for i in seg_start..self.out.segments.0.len() {
3124                    x -= self.out.segments.0[i].advance;
3125
3126                    let g_range = self.out.segments.glyphs(i);
3127
3128                    let glyphs = &mut self.out.glyphs[g_range.iter()];
3129                    let offset = x - self.out.segments.0[i].x;
3130                    self.out.segments.0[i].x = x;
3131                    for g in glyphs {
3132                        g.point.x += offset;
3133                    }
3134                }
3135            }
3136        }
3137
3138        let mut d = LayoutDirections::empty();
3139        d.set(LayoutDirections::LTR, self.line_has_ltr);
3140        d.set(LayoutDirections::RTL, self.line_has_rtl);
3141
3142        self.line_has_ltr = false;
3143        self.line_has_rtl = false;
3144
3145        d
3146    }
3147
3148    pub fn push_text_seg(&mut self, seg: &str, info: TextSegment) {
3149        let g_len = if let Some(l) = self.out.segments.0.last() {
3150            self.out.glyphs.len() - l.end
3151        } else {
3152            self.out.glyphs.len()
3153        };
3154        if g_len > 0 {
3155            self.line_has_ltr |= info.level.is_ltr();
3156            self.line_has_rtl |= info.level.is_rtl();
3157        }
3158
3159        self.text_seg_end += seg.len();
3160
3161        let is_first_of_line =
3162            (!self.out.lines.0.is_empty() && self.out.lines.last().end == self.out.segments.0.len()) || self.out.segments.0.is_empty();
3163        let x = if is_first_of_line {
3164            0.0
3165        } else {
3166            // not first segment of line
3167            self.out.segments.0.last().map(|s| s.x + s.advance).unwrap_or(0.0)
3168        };
3169        self.out.segments.0.push(GlyphSegment {
3170            text: TextSegment {
3171                end: self.text_seg_end,
3172                ..info
3173            },
3174            end: self.out.glyphs.len(),
3175            x,
3176            advance: self.origin.x - x,
3177        });
3178    }
3179
3180    pub fn push_split_seg(&mut self, shaped_seg: &ShapedSegmentData, seg: &str, info: TextSegment, spacing: f32, text: &SegmentedText) {
3181        let mut end_glyph = 0;
3182        let mut end_glyph_x = 0.0;
3183        let max_width = self.actual_max_width();
3184        for (i, g) in shaped_seg.glyphs.iter().enumerate() {
3185            if self.origin.x + g.point.0 > max_width {
3186                break;
3187            }
3188            end_glyph = i;
3189            end_glyph_x = g.point.0;
3190        }
3191
3192        let (glyphs_a, glyphs_b) = shaped_seg.glyphs.split_at(end_glyph);
3193
3194        if glyphs_a.is_empty() || glyphs_b.is_empty() {
3195            // failed split
3196            self.push_line_break(true, text);
3197            self.push_glyphs(shaped_seg, spacing);
3198            self.push_text_seg(seg, info);
3199        } else {
3200            let (seg_a, seg_b) = seg.split_at(glyphs_b[0].cluster as usize);
3201
3202            let shaped_seg_a = ShapedSegmentData {
3203                glyphs: glyphs_a.to_vec(),
3204                x_advance: end_glyph_x,
3205                y_advance: glyphs_a.iter().map(|g| g.point.1).sum(),
3206            };
3207            self.push_glyphs(&shaped_seg_a, spacing);
3208            self.push_text_seg(seg_a, info);
3209            self.push_line_break(true, text);
3210
3211            let mut shaped_seg_b = ShapedSegmentData {
3212                glyphs: glyphs_b.to_vec(),
3213                x_advance: shaped_seg.x_advance - end_glyph_x,
3214                y_advance: glyphs_b.iter().map(|g| g.point.1).sum(),
3215            };
3216            for g in &mut shaped_seg_b.glyphs {
3217                g.point.0 -= shaped_seg_a.x_advance;
3218                g.cluster -= seg_a.len() as u32;
3219            }
3220
3221            if shaped_seg_b.x_advance <= max_width {
3222                self.push_glyphs(&shaped_seg_b, spacing);
3223                self.push_text_seg(seg_b, info);
3224            } else {
3225                self.push_split_seg(&shaped_seg_b, seg_b, info, spacing, text);
3226            }
3227        }
3228    }
3229
3230    fn push_font(&mut self, font: &Font) {
3231        if let Some(last) = self.out.fonts.0.last_mut() {
3232            if &last.font == font {
3233                last.end = self.out.glyphs.len();
3234                return;
3235            } else if last.end == self.out.glyphs.len() {
3236                return;
3237            }
3238        }
3239        self.out.fonts.0.push(FontRange {
3240            font: font.clone(),
3241            end: self.out.glyphs.len(),
3242        })
3243    }
3244}
3245
3246bitflags! {
3247    /// Identifies what direction segments a [`ShapedLine`] has.
3248    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3249    #[serde(transparent)]
3250    pub struct LayoutDirections: u8 {
3251        /// Line has left-to-right segments.
3252        const LTR = 1;
3253        /// Line has right-to-left segments.
3254        const RTL = 2;
3255        /// Line as both left-to-right and right-to-left segments.
3256        ///
3257        /// When this is the case the line segments positions may be re-ordered.
3258        const BIDI = Self::LTR.bits() | Self::RTL.bits();
3259    }
3260}
3261
3262/// Represents a line selection of a [`ShapedText`].
3263#[derive(Clone, Copy)]
3264pub struct ShapedLine<'a> {
3265    text: &'a ShapedText,
3266    // range of segments of this line.
3267    seg_range: IndexRange,
3268    index: usize,
3269    width: Px,
3270}
3271impl fmt::Debug for ShapedLine<'_> {
3272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3273        f.debug_struct("ShapedLine")
3274            .field("seg_range", &self.seg_range)
3275            .field("index", &self.index)
3276            .field("width", &self.width)
3277            .finish_non_exhaustive()
3278    }
3279}
3280impl<'a> ShapedLine<'a> {
3281    /// Height of the line.
3282    pub fn height(&self) -> Px {
3283        if self.index == 0 {
3284            self.text.first_line.height()
3285        } else if self.index == self.text.lines.0.len() - 1 {
3286            self.text.last_line.height()
3287        } else {
3288            self.text.line_height
3289        }
3290    }
3291
3292    /// Width of the line.
3293    pub fn width(&self) -> Px {
3294        if self.index == 0 {
3295            self.text.first_line.width()
3296        } else if self.index == self.text.lines.0.len() - 1 {
3297            self.text.last_line.width()
3298        } else {
3299            self.width
3300        }
3301    }
3302
3303    /// Bounds of the line.
3304    pub fn rect(&self) -> PxRect {
3305        if self.index == 0 {
3306            return self.text.first_line;
3307        }
3308        if self.index == self.text.lines.0.len() - 1 {
3309            return self.text.last_line;
3310        }
3311
3312        let size = PxSize::new(self.width, self.text.line_height);
3313        let origin = PxPoint::new(
3314            Px(self.text.lines.0[self.index].x_offset as i32),
3315            self.text.line_height * Px((self.index - 1) as i32) + self.text.first_line.max_y() + self.text.mid_clear,
3316        );
3317        PxRect::new(origin, size)
3318    }
3319
3320    /// Initial size of the line, before any line reshaping.
3321    ///
3322    /// This can be different then the current [`rect`] size if the parent inline changed the size, usually to inject
3323    /// blank spaces to justify the text or to visually insert a bidirectional fragment of another widget.
3324    ///
3325    /// [`rect`]: Self::rect
3326    pub fn original_size(&self) -> PxSize {
3327        if self.index == 0 {
3328            return self.text.orig_first_line;
3329        }
3330        if self.index == self.text.lines.0.len() - 1 {
3331            return self.text.orig_last_line;
3332        }
3333        PxSize::new(self.width, self.text.line_height)
3334    }
3335
3336    /// Gets if this line is the first of a paragraph.
3337    ///
3338    /// Paragraphs are defined by the [`paragraph_break`] value.
3339    ///
3340    /// [`paragraph_break`]: ShapedText::paragraph_break
3341    pub fn is_paragraph_start(&self) -> bool {
3342        match self.text.paragraph_break {
3343            ParagraphBreak::None => self.index == 0,
3344            ParagraphBreak::Line => !self.started_by_wrap(),
3345        }
3346    }
3347
3348    /// Gets if this line is the last of a paragraph.
3349    ///
3350    /// Paragraphs are defined by the [`paragraph_break`] value.
3351    ///
3352    /// [`paragraph_break`]: ShapedText::paragraph_break
3353    pub fn is_paragraph_end(&self) -> bool {
3354        match self.text.paragraph_break {
3355            ParagraphBreak::None => self.index == self.text.lines.0.len() - 1,
3356            ParagraphBreak::Line => !self.ended_by_wrap(),
3357        }
3358    }
3359
3360    /// Full overline, start point + width.
3361    pub fn overline(&self) -> (PxPoint, Px) {
3362        self.decoration_line(self.text.overline)
3363    }
3364
3365    /// Full strikethrough line, start point + width.
3366    pub fn strikethrough(&self) -> (PxPoint, Px) {
3367        self.decoration_line(self.text.strikethrough)
3368    }
3369
3370    /// Full underline, not skipping.
3371    ///
3372    /// The *y* is defined by the font metrics.
3373    ///
3374    /// Returns start point + width.
3375    pub fn underline(&self) -> (PxPoint, Px) {
3376        self.decoration_line(self.text.underline)
3377    }
3378
3379    /// Full underline, not skipping.
3380    ///
3381    /// The *y* is the baseline + descent + 1px.
3382    ///
3383    /// Returns start point + width.
3384    pub fn underline_descent(&self) -> (PxPoint, Px) {
3385        self.decoration_line(self.text.underline_descent)
3386    }
3387
3388    /// Underline, skipping spaces.
3389    ///
3390    /// The *y* is defined by the font metrics.
3391    ///
3392    /// Returns and iterator of start point + width for each word.
3393    pub fn underline_skip_spaces(&self) -> impl Iterator<Item = (PxPoint, Px)> + 'a {
3394        MergingLineIter::new(self.segs().filter(|s| s.kind().is_word()).map(|s| s.underline()))
3395    }
3396
3397    /// Underline, skipping spaces.
3398    ///
3399    /// The *y* is the baseline + descent + 1px.
3400    ///
3401    /// Returns and iterator of start point + width for each word.
3402    pub fn underline_descent_skip_spaces(&self) -> impl Iterator<Item = (PxPoint, Px)> + 'a {
3403        MergingLineIter::new(self.segs().filter(|s| s.kind().is_word()).map(|s| s.underline_descent()))
3404    }
3405
3406    /// Underline, skipping glyph descends that intersect the underline.
3407    ///
3408    /// The *y* is defined by the font metrics.
3409    ///
3410    /// Returns an iterator of start point + width for continuous underline.
3411    pub fn underline_skip_glyphs(&self, thickness: Px) -> impl Iterator<Item = (PxPoint, Px)> + 'a {
3412        MergingLineIter::new(self.segs().flat_map(move |s| s.underline_skip_glyphs(thickness)))
3413    }
3414
3415    /// Underline, skipping spaces and glyph descends that intersect the underline
3416    ///
3417    /// The *y* is defined by font metrics.
3418    ///
3419    /// Returns an iterator of start point + width for continuous underline.
3420    pub fn underline_skip_glyphs_and_spaces(&self, thickness: Px) -> impl Iterator<Item = (PxPoint, Px)> + 'a {
3421        MergingLineIter::new(
3422            self.segs()
3423                .filter(|s| s.kind().is_word())
3424                .flat_map(move |s| s.underline_skip_glyphs(thickness)),
3425        )
3426    }
3427
3428    fn decoration_line(&self, bottom_up_offset: Px) -> (PxPoint, Px) {
3429        let r = self.rect();
3430        let y = r.max_y() - bottom_up_offset;
3431        (PxPoint::new(r.origin.x, y), self.width)
3432    }
3433
3434    fn segments(&self) -> &'a [GlyphSegment] {
3435        &self.text.segments.0[self.seg_range.iter()]
3436    }
3437
3438    /// Glyphs in the line.
3439    ///
3440    /// The glyphs are in text order by segments and in visual order (LTR) within segments, so
3441    /// the RTL text "لما " will have the space glyph first, then "’álif", "miim", "láam".
3442    ///
3443    /// All glyph points are set as offsets to the top-left of the text full text.
3444    pub fn glyphs(&self) -> impl Iterator<Item = (&'a Font, &'a [GlyphInstance])> + 'a {
3445        let r = self.glyphs_range();
3446        self.text.glyphs_range(r)
3447    }
3448
3449    /// Glyphs in the line paired with the *x-advance*.
3450    pub fn glyphs_with_x_advance(&self) -> impl Iterator<Item = (&'a Font, impl Iterator<Item = (GlyphInstance, f32)> + 'a)> + 'a {
3451        self.segs().flat_map(|s| s.glyphs_with_x_advance())
3452    }
3453
3454    fn glyphs_range(&self) -> IndexRange {
3455        self.text.segments.glyphs_range(self.seg_range)
3456    }
3457
3458    /// Iterate over word and space segments in this line.
3459    pub fn segs(&self) -> impl DoubleEndedIterator<Item = ShapedSegment<'a>> + ExactSizeIterator + use<'a> {
3460        let text = self.text;
3461        let line_index = self.index;
3462        self.seg_range.iter().map(move |i| ShapedSegment {
3463            text,
3464            line_index,
3465            index: i,
3466        })
3467    }
3468
3469    /// Number of segments in this line.
3470    pub fn segs_len(&self) -> usize {
3471        self.seg_range.len()
3472    }
3473
3474    /// Get the segment by index.
3475    ///
3476    /// The first segment of the line is `0`.
3477    pub fn seg(&self, seg_idx: usize) -> Option<ShapedSegment<'_>> {
3478        if self.seg_range.len() > seg_idx {
3479            Some(ShapedSegment {
3480                text: self.text,
3481                line_index: self.index,
3482                index: seg_idx + self.seg_range.start(),
3483            })
3484        } else {
3485            None
3486        }
3487    }
3488
3489    /// Returns `true` if this line was started by the wrap algorithm.
3490    ///
3491    /// If this is `false` then the line is the first or the previous line ends in a [`LineBreak`].
3492    ///
3493    /// [`LineBreak`]: TextSegmentKind::LineBreak
3494    pub fn started_by_wrap(&self) -> bool {
3495        self.index > 0 && {
3496            let prev_line = self.text.lines.segs(self.index - 1);
3497            self.text.segments.0[prev_line.iter()]
3498                .last()
3499                .map(|s| !matches!(s.text.kind, TextSegmentKind::LineBreak))
3500                .unwrap() // only last line can be empty
3501        }
3502    }
3503
3504    /// Returns `true` if this line was ended by the wrap algorithm.
3505    ///
3506    /// If this is `false` then the line is the last or ends in a [`LineBreak`].
3507    ///
3508    /// [`LineBreak`]: TextSegmentKind::LineBreak
3509    pub fn ended_by_wrap(&self) -> bool {
3510        // not last and not ended in line-break.
3511        self.index < self.text.lines.0.len() - 1
3512            && self
3513                .segments()
3514                .last()
3515                .map(|s| !matches!(s.text.kind, TextSegmentKind::LineBreak))
3516                .unwrap() // only last line can be empty
3517    }
3518
3519    /// Returns the line or first previous line that is not [`started_by_wrap`].
3520    ///
3521    /// [`started_by_wrap`]: Self::started_by_wrap
3522    pub fn actual_line_start(&self) -> Self {
3523        let mut r = *self;
3524        while r.started_by_wrap() {
3525            r = r.text.line(r.index - 1).unwrap();
3526        }
3527        r
3528    }
3529
3530    /// Returns the line or first next line that is not [`ended_by_wrap`].
3531    ///
3532    /// [`ended_by_wrap`]: Self::ended_by_wrap
3533    pub fn actual_line_end(&self) -> Self {
3534        let mut r = *self;
3535        while r.ended_by_wrap() {
3536            r = r.text.line(r.index + 1).unwrap();
3537        }
3538        r
3539    }
3540
3541    /// Get the text bytes range of this line in the original text.
3542    pub fn text_range(&self) -> ops::Range<usize> {
3543        let start = self.seg_range.start();
3544        let start = if start == 0 { 0 } else { self.text.segments.0[start - 1].text.end };
3545        let end = self.seg_range.end();
3546        let end = if end == 0 { 0 } else { self.text.segments.0[end - 1].text.end };
3547
3548        start..end
3549    }
3550
3551    /// Get the text bytes range of this line in the original text, excluding the line break
3552    /// to keep [`end`] in the same line.
3553    ///
3554    /// [`end`]: ops::Range<usize>::end
3555    pub fn text_caret_range(&self) -> ops::Range<usize> {
3556        let start = self.seg_range.start();
3557        let start = if start == 0 { 0 } else { self.text.segments.0[start - 1].text.end };
3558        let end = self.seg_range.end();
3559        let end = if end == 0 {
3560            0
3561        } else if self.seg_range.start() == end {
3562            start
3563        } else {
3564            let seg = &self.text.segments.0[end - 1];
3565            if !matches!(seg.text.kind, TextSegmentKind::LineBreak) {
3566                seg.text.end
3567            } else {
3568                // start of LineBreak segment
3569                if end == 1 { 0 } else { self.text.segments.0[end - 2].text.end }
3570            }
3571        };
3572
3573        start..end
3574    }
3575
3576    /// Gets the text range of the actual line, joining shaped lines that are started by wrap.
3577    pub fn actual_text_range(&self) -> ops::Range<usize> {
3578        let start = self.actual_line_start().text_range().start;
3579        let end = self.actual_line_end().text_range().end;
3580        start..end
3581    }
3582
3583    /// Gets the text range of the actual line, excluding the line break at the end.
3584    pub fn actual_text_caret_range(&self) -> ops::Range<usize> {
3585        let start = self.actual_line_start().text_range().start;
3586        let end = self.actual_line_end().text_caret_range().end;
3587        start..end
3588    }
3589
3590    /// Select the string represented by this line.
3591    ///
3592    /// The `full_text` must be equal to the original text that was used to generate the parent [`ShapedText`].
3593    pub fn text<'s>(&self, full_text: &'s str) -> &'s str {
3594        let r = self.text_range();
3595
3596        let start = r.start.min(full_text.len());
3597        let end = r.end.min(full_text.len());
3598
3599        &full_text[start..end]
3600    }
3601
3602    /// Gets the segment that contains `x` or is nearest to it.
3603    pub fn nearest_seg(&self, x: Px) -> Option<ShapedSegment<'a>> {
3604        let mut min = None;
3605        let mut min_dist = Px::MAX;
3606        for seg in self.segs() {
3607            let (seg_x, width) = seg.x_width();
3608            if x >= seg_x {
3609                let seg_max_x = seg_x + width;
3610                if x < seg_max_x {
3611                    return Some(seg);
3612                }
3613            }
3614            let dist = (x - seg_x).abs();
3615            if min_dist > dist {
3616                min = Some(seg);
3617                min_dist = dist;
3618            }
3619        }
3620        min
3621    }
3622
3623    /// Gets the line index.
3624    pub fn index(&self) -> usize {
3625        self.index
3626    }
3627
3628    /// Layout directions of segments in this line.
3629    pub fn directions(&self) -> LayoutDirections {
3630        self.text.lines.0[self.index].directions
3631    }
3632}
3633
3634/// Merges lines defined by `(PxPoint, Px)`, assuming the `y` is equal.
3635struct MergingLineIter<I> {
3636    iter: I,
3637    line: Option<(PxPoint, Px)>,
3638}
3639impl<I> MergingLineIter<I> {
3640    pub fn new(iter: I) -> Self {
3641        MergingLineIter { iter, line: None }
3642    }
3643}
3644impl<I: Iterator<Item = (PxPoint, Px)>> Iterator for MergingLineIter<I> {
3645    type Item = I::Item;
3646
3647    fn next(&mut self) -> Option<Self::Item> {
3648        loop {
3649            match self.iter.next() {
3650                Some(line) => {
3651                    if let Some(prev_line) = &mut self.line {
3652                        fn min_x((origin, _width): (PxPoint, Px)) -> Px {
3653                            origin.x
3654                        }
3655                        fn max_x((origin, width): (PxPoint, Px)) -> Px {
3656                            origin.x + width
3657                        }
3658
3659                        if prev_line.0.y == line.0.y && min_x(*prev_line) <= max_x(line) && max_x(*prev_line) >= min_x(line) {
3660                            let x = min_x(*prev_line).min(min_x(line));
3661                            prev_line.1 = max_x(*prev_line).max(max_x(line)) - x;
3662                            prev_line.0.x = x;
3663                        } else {
3664                            let cut = mem::replace(prev_line, line);
3665                            return Some(cut);
3666                        }
3667                    } else {
3668                        self.line = Some(line);
3669                        continue;
3670                    }
3671                }
3672                None => return self.line.take(),
3673            }
3674        }
3675    }
3676}
3677
3678struct MergingRectIter<I> {
3679    iter: I,
3680    rect: Option<PxBox>,
3681}
3682impl<I> MergingRectIter<I> {
3683    pub fn new(iter: I) -> Self {
3684        MergingRectIter { iter, rect: None }
3685    }
3686}
3687impl<I: Iterator<Item = PxRect>> Iterator for MergingRectIter<I> {
3688    type Item = I::Item;
3689
3690    fn next(&mut self) -> Option<Self::Item> {
3691        loop {
3692            match self.iter.next() {
3693                Some(rect) => {
3694                    let rect = rect.to_box2d();
3695                    if let Some(prev_rect) = &mut self.rect {
3696                        if prev_rect.min.y == rect.min.y
3697                            && prev_rect.max.y == rect.max.y
3698                            && prev_rect.min.x <= rect.max.x
3699                            && prev_rect.max.x >= rect.min.x
3700                        {
3701                            prev_rect.min.x = prev_rect.min.x.min(rect.min.x);
3702                            prev_rect.max.x = prev_rect.max.x.max(rect.max.x);
3703                            continue;
3704                        } else {
3705                            let cut = mem::replace(prev_rect, rect);
3706                            return Some(cut.to_rect());
3707                        }
3708                    } else {
3709                        self.rect = Some(rect);
3710                        continue;
3711                    }
3712                }
3713                None => return self.rect.take().map(|r| r.to_rect()),
3714            }
3715        }
3716    }
3717}
3718
3719/// Represents a word or space selection of a [`ShapedText`].
3720#[derive(Clone, Copy)]
3721pub struct ShapedSegment<'a> {
3722    text: &'a ShapedText,
3723    line_index: usize,
3724    index: usize,
3725}
3726impl fmt::Debug for ShapedSegment<'_> {
3727    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3728        f.debug_struct("ShapedSegment")
3729            .field("line_index", &self.line_index)
3730            .field("index", &self.index)
3731            .finish_non_exhaustive()
3732    }
3733}
3734impl<'a> ShapedSegment<'a> {
3735    /// Segment kind.
3736    pub fn kind(&self) -> TextSegmentKind {
3737        self.text.segments.0[self.index].text.kind
3738    }
3739
3740    /// Segment bidi level.
3741    pub fn level(&self) -> BidiLevel {
3742        self.text.segments.0[self.index].text.level
3743    }
3744
3745    /// Layout direction of glyphs in the segment.
3746    pub fn direction(&self) -> LayoutDirection {
3747        self.text.segments.0[self.index].text.direction()
3748    }
3749
3750    /// If the segment contains the last glyph of the line.
3751    pub fn has_last_glyph(&self) -> bool {
3752        let seg_glyphs = self.text.segments.glyphs(self.index);
3753        let s = self.text.lines.segs(self.line_index);
3754        let line_glyphs = self.text.segments.glyphs_range(s);
3755        seg_glyphs.end() == line_glyphs.end()
3756    }
3757
3758    fn glyphs_range(&self) -> IndexRange {
3759        self.text.segments.glyphs(self.index)
3760    }
3761
3762    /// Glyphs in the word or space.
3763    ///
3764    /// The glyphs are in visual order (LTR) within segments, so
3765    /// the RTL text "لما" will yield "’álif", "miim", "láam".
3766    ///
3767    /// All glyph points are set as offsets to the top-left of the text full text.
3768    ///
3769    /// Note that multiple glyphs can map to the same char and multiple chars can map to the same glyph, you can use the [`clusters`]
3770    /// map to find the char for each glyph. Some font ligatures also bridge multiple segments, in this case only the first shaped
3771    /// segment has glyphs the subsequent ones are empty.
3772    ///
3773    /// [`clusters`]: Self::clusters
3774    pub fn glyphs(&self) -> impl Iterator<Item = (&'a Font, &'a [GlyphInstance])> {
3775        let r = self.glyphs_range();
3776        self.text.glyphs_range(r)
3777    }
3778
3779    /// Gets the specific glyph and font.
3780    pub fn glyph(&self, index: usize) -> Option<(&'a Font, GlyphInstance)> {
3781        let mut r = self.glyphs_range();
3782        r.0 += index;
3783        self.text.glyphs_range(r).next().map(|(f, g)| (f, g[0]))
3784    }
3785
3786    /// Map glyph -> char.
3787    ///
3788    /// Each [`glyphs`] glyph pairs with an entry in this slice that is the char byte index in [`text`]. If
3789    /// a font ligature bridges multiple segments only the first segment will have a non-empty map.
3790    ///
3791    /// [`glyphs`]: Self::glyphs
3792    /// [`text`]: Self::text
3793    pub fn clusters(&self) -> &[u32] {
3794        let r = self.glyphs_range();
3795        self.text.clusters_range(r)
3796    }
3797
3798    /// Count the deduplicated [`clusters`].
3799    ///
3800    /// [`clusters`]: Self::clusters
3801    pub fn clusters_count(&self) -> usize {
3802        let mut c = u32::MAX;
3803        let mut count = 0;
3804        for &i in self.clusters() {
3805            if i != c {
3806                c = i;
3807                count += 1;
3808            }
3809        }
3810        count
3811    }
3812
3813    /// Number of next segments that are empty because their text is included in a ligature
3814    /// glyph or glyphs started in this segment.
3815    pub fn ligature_segs_count(&self) -> usize {
3816        let range = self.glyphs_range();
3817        if range.iter().is_empty() {
3818            0
3819        } else {
3820            self.text.segments.0[self.index + 1..]
3821                .iter()
3822                .filter(|s| s.end == range.end())
3823                .count()
3824        }
3825    }
3826
3827    /// Glyphs in the segment, paired with the *x-advance*.
3828    ///
3829    /// Yields `(Font, [(glyph, advance)])`.
3830    pub fn glyphs_with_x_advance(
3831        &self,
3832    ) -> impl Iterator<Item = (&'a Font, impl Iterator<Item = (GlyphInstance, f32)> + use<'a>)> + use<'a> {
3833        let r = self.glyphs_range();
3834        self.text.seg_glyphs_with_x_advance(self.index, r)
3835    }
3836
3837    /// Glyphs per cluster in the segment, paired with the *x-advance* of the cluster.
3838    ///
3839    /// Yields `(Font, [(cluster, [glyph], advance)])`.
3840    pub fn cluster_glyphs_with_x_advance(
3841        &self,
3842    ) -> impl Iterator<Item = (&'a Font, impl Iterator<Item = (u32, &'a [GlyphInstance], f32)> + use<'a>)> + use<'a> {
3843        let r = self.glyphs_range();
3844        self.text.seg_cluster_glyphs_with_x_advance(self.index, r)
3845    }
3846
3847    /// Gets the segment x offset and advance.
3848    pub fn x_width(&self) -> (Px, Px) {
3849        let IndexRange(start, end) = self.glyphs_range();
3850
3851        let is_line_break = start == end && matches!(self.kind(), TextSegmentKind::LineBreak);
3852
3853        let start_x = match self.direction() {
3854            LayoutDirection::LTR => {
3855                if is_line_break || start == self.text.glyphs.len() {
3856                    let x = self.text.lines.x_offset(self.line_index);
3857                    let w = self.text.lines.width(self.line_index);
3858                    return (Px((x + w) as i32), Px(0));
3859                }
3860                self.text.glyphs[start].point.x
3861            }
3862            LayoutDirection::RTL => {
3863                if is_line_break || start == self.text.glyphs.len() {
3864                    let x = self.text.lines.x_offset(self.line_index);
3865                    return (Px(x as i32), Px(0));
3866                }
3867
3868                self.text.glyphs[start..end]
3869                    .iter()
3870                    .map(|g| g.point.x)
3871                    .min_by(f32::total_cmp)
3872                    .unwrap_or(0.0)
3873            }
3874        };
3875
3876        (Px(start_x.floor() as i32), Px(self.advance().ceil() as i32))
3877    }
3878
3879    /// Segment exact *width* in pixels.
3880    pub fn advance(&self) -> f32 {
3881        self.text.segments.0[self.index].advance
3882    }
3883
3884    /// Bounds of the word or spaces.
3885    pub fn rect(&self) -> PxRect {
3886        let (x, width) = self.x_width();
3887        let size = PxSize::new(width, self.text.line_height);
3888
3889        let y = if self.line_index == 0 {
3890            self.text.first_line.origin.y
3891        } else if self.line_index == self.text.lines.0.len() - 1 {
3892            self.text.last_line.origin.y
3893        } else {
3894            self.text.line_height * Px((self.line_index - 1) as i32) + self.text.first_line.max_y() + self.text.mid_clear
3895        };
3896        PxRect::new(PxPoint::new(x, y), size)
3897    }
3898
3899    /// Gets the first char and glyph with advance that overflows `max_width`.
3900    pub fn overflow_char_glyph(&self, max_width_px: f32) -> Option<(usize, usize)> {
3901        if self.advance() > max_width_px {
3902            match self.direction() {
3903                LayoutDirection::LTR => {
3904                    let mut x = 0.0;
3905                    let mut g = 0;
3906                    for (_, c) in self.cluster_glyphs_with_x_advance() {
3907                        for (cluster, glyphs, advance) in c {
3908                            x += advance;
3909                            if x > max_width_px {
3910                                return Some((cluster as usize, g));
3911                            }
3912                            g += glyphs.len();
3913                        }
3914                    }
3915                }
3916                LayoutDirection::RTL => {
3917                    let mut g = 0;
3918                    let mut rev = smallvec::SmallVec::<[_; 10]>::new();
3919                    for (_, c) in self.cluster_glyphs_with_x_advance() {
3920                        for (cluster, glyphs, advance) in c {
3921                            rev.push((cluster, g, advance));
3922                            g += glyphs.len();
3923                        }
3924                    }
3925
3926                    let mut x = 0.0;
3927                    for (c, g, advance) in rev.into_iter().rev() {
3928                        x += advance;
3929                        if x > max_width_px {
3930                            return Some((c as usize, g));
3931                        }
3932                    }
3933                }
3934            }
3935        }
3936        None
3937    }
3938
3939    /// Segment info for widget inline segments.
3940    pub fn inline_info(&self) -> InlineSegmentInfo {
3941        let (x, width) = self.x_width();
3942        InlineSegmentInfo::new(x, width)
3943    }
3944
3945    fn decoration_line(&self, bottom_up_offset: Px) -> (PxPoint, Px) {
3946        let (x, width) = self.x_width();
3947        let y = (self.text.line_height * Px((self.line_index as i32) + 1)) - bottom_up_offset;
3948        (PxPoint::new(x, y), width)
3949    }
3950
3951    /// Overline spanning the word or spaces, start point + width.
3952    pub fn overline(&self) -> (PxPoint, Px) {
3953        self.decoration_line(self.text.overline)
3954    }
3955
3956    /// Strikethrough spanning the word or spaces, start point + width.
3957    pub fn strikethrough(&self) -> (PxPoint, Px) {
3958        self.decoration_line(self.text.strikethrough)
3959    }
3960
3961    /// Underline spanning the word or spaces, not skipping.
3962    ///
3963    /// The *y* is defined by the font metrics.
3964    ///
3965    /// Returns start point + width.
3966    pub fn underline(&self) -> (PxPoint, Px) {
3967        self.decoration_line(self.text.underline)
3968    }
3969
3970    /// Underline spanning the word or spaces, skipping glyph descends that intercept the line.
3971    ///
3972    /// Returns an iterator of start point + width for underline segments.
3973    pub fn underline_skip_glyphs(&self, thickness: Px) -> impl Iterator<Item = (PxPoint, Px)> + use<'a> {
3974        let y = (self.text.line_height * Px((self.line_index as i32) + 1)) - self.text.underline;
3975        let (x, _) = self.x_width();
3976
3977        let line_y = -(self.text.baseline - self.text.underline).0 as f32;
3978        let line_y_range = (line_y, line_y - thickness.0 as f32);
3979
3980        // space around glyph descends, thickness clamped to a minimum of 1px and a maximum of 0.2em (same as Firefox).
3981        let padding = (thickness.0 as f32).clamp(1.0, (self.text.fonts.font(0).size().0 as f32 * 0.2).max(1.0));
3982
3983        // no yield, only sadness
3984        struct UnderlineSkipGlyphs<'a, I, J> {
3985            line_y_range: (f32, f32),
3986            y: Px,
3987            padding: f32,
3988            min_width: Px,
3989
3990            iter: I,
3991            resume: Option<(&'a Font, J)>,
3992            x: f32,
3993            width: f32,
3994        }
3995        impl<I, J> UnderlineSkipGlyphs<'_, I, J> {
3996            fn line(&self) -> Option<(PxPoint, Px)> {
3997                fn f32_to_px(px: f32) -> Px {
3998                    Px(px.round() as i32)
3999                }
4000                let r = (PxPoint::new(f32_to_px(self.x), self.y), f32_to_px(self.width));
4001                if r.1 >= self.min_width { Some(r) } else { None }
4002            }
4003        }
4004        impl<'a, I, J> Iterator for UnderlineSkipGlyphs<'a, I, J>
4005        where
4006            I: Iterator<Item = (&'a Font, J)>,
4007            J: Iterator<Item = (GlyphInstance, f32)>,
4008        {
4009            type Item = (PxPoint, Px);
4010
4011            fn next(&mut self) -> Option<Self::Item> {
4012                loop {
4013                    let continuation = self.resume.take().or_else(|| self.iter.next());
4014                    if let Some((font, mut glyphs_with_adv)) = continuation {
4015                        for (g, a) in &mut glyphs_with_adv {
4016                            if let Some((ex_start, ex_end)) = font.h_line_hits(g.index, self.line_y_range) {
4017                                self.width += ex_start - self.padding;
4018                                let r = self.line();
4019                                self.x += self.width + self.padding + ex_end + self.padding;
4020                                self.width = a - (ex_start + ex_end) - self.padding;
4021
4022                                if r.is_some() {
4023                                    self.resume = Some((font, glyphs_with_adv));
4024                                    return r;
4025                                }
4026                            } else {
4027                                self.width += a;
4028                                // continue
4029                            }
4030                        }
4031                    } else {
4032                        let r = self.line();
4033                        self.width = 0.0;
4034                        return r;
4035                    }
4036                }
4037            }
4038        }
4039        UnderlineSkipGlyphs {
4040            line_y_range,
4041            y,
4042            padding,
4043            min_width: Px((padding / 2.0).max(1.0).ceil() as i32),
4044
4045            iter: self.glyphs_with_x_advance(),
4046            resume: None,
4047            x: x.0 as f32,
4048            width: 0.0,
4049        }
4050    }
4051
4052    /// Underline spanning the word or spaces, not skipping.
4053    ///
4054    /// The *y* is the baseline + descent + 1px.
4055    ///
4056    /// Returns start point + width.
4057    pub fn underline_descent(&self) -> (PxPoint, Px) {
4058        self.decoration_line(self.text.underline_descent)
4059    }
4060
4061    /// Get the text bytes range of this segment in the original text.
4062    pub fn text_range(&self) -> ops::Range<usize> {
4063        self.text_start()..self.text_end()
4064    }
4065
4066    /// Get the text byte range start of this segment in the original text.
4067    pub fn text_start(&self) -> usize {
4068        if self.index == 0 {
4069            0
4070        } else {
4071            self.text.segments.0[self.index - 1].text.end
4072        }
4073    }
4074
4075    /// Get the text byte range end of this segment in the original text.
4076    pub fn text_end(&self) -> usize {
4077        self.text.segments.0[self.index].text.end
4078    }
4079
4080    /// Get the text bytes range of the `glyph_range` in this segment's [`text`].
4081    ///
4082    /// [`text`]: Self::text
4083    pub fn text_glyph_range(&self, glyph_range: impl ops::RangeBounds<usize>) -> ops::Range<usize> {
4084        let included_start = match glyph_range.start_bound() {
4085            ops::Bound::Included(i) => Some(*i),
4086            ops::Bound::Excluded(i) => Some(*i + 1),
4087            ops::Bound::Unbounded => None,
4088        };
4089        let excluded_end = match glyph_range.end_bound() {
4090            ops::Bound::Included(i) => Some(*i - 1),
4091            ops::Bound::Excluded(i) => Some(*i),
4092            ops::Bound::Unbounded => None,
4093        };
4094
4095        let glyph_range_start = self.glyphs_range().start();
4096        let glyph_to_char = |g| self.text.clusters[glyph_range_start + g] as usize;
4097
4098        match (included_start, excluded_end) {
4099            (None, None) => IndexRange(0, self.text_range().len()),
4100            (None, Some(end)) => IndexRange(0, glyph_to_char(end)),
4101            (Some(start), None) => IndexRange(glyph_to_char(start), self.text_range().len()),
4102            (Some(start), Some(end)) => IndexRange(glyph_to_char(start), glyph_to_char(end)),
4103        }
4104        .iter()
4105    }
4106
4107    /// Select the string represented by this segment.
4108    ///
4109    /// The `full_text` must be equal to the original text that was used to generate the parent [`ShapedText`].
4110    pub fn text<'s>(&self, full_text: &'s str) -> &'s str {
4111        let r = self.text_range();
4112        let start = r.start.min(full_text.len());
4113        let end = r.end.min(full_text.len());
4114        &full_text[start..end]
4115    }
4116
4117    /// Gets the insert index in the segment text that is nearest to `x`.
4118    pub fn nearest_char_index(&self, x: Px, full_text: &str) -> usize {
4119        let txt_range = self.text_range();
4120        let is_rtl = self.direction().is_rtl();
4121        let x = x.0 as f32;
4122
4123        let seg_clusters = self.clusters();
4124
4125        for (font, clusters) in self.cluster_glyphs_with_x_advance() {
4126            for (cluster, glyphs, advance) in clusters {
4127                let found = x < glyphs[0].point.x || glyphs[0].point.x + advance > x;
4128                if !found {
4129                    continue;
4130                }
4131                let cluster_i = seg_clusters.iter().position(|&c| c == cluster).unwrap();
4132
4133                let char_a = txt_range.start + cluster as usize;
4134                let char_b = if is_rtl {
4135                    if cluster_i == 0 {
4136                        txt_range.end
4137                    } else {
4138                        txt_range.start + seg_clusters[cluster_i - 1] as usize
4139                    }
4140                } else {
4141                    let next_cluster = cluster_i + glyphs.len();
4142                    if next_cluster == seg_clusters.len() {
4143                        txt_range.end
4144                    } else {
4145                        txt_range.start + seg_clusters[next_cluster] as usize
4146                    }
4147                };
4148
4149                if char_b - char_a > 1 && glyphs.len() == 1 {
4150                    // maybe ligature
4151
4152                    let text = &full_text[char_a..char_b];
4153
4154                    let mut lig_parts = smallvec::SmallVec::<[u16; 6]>::new_const();
4155                    for (i, _) in unicode_segmentation::UnicodeSegmentation::grapheme_indices(text, true) {
4156                        lig_parts.push(i as u16);
4157                    }
4158
4159                    if lig_parts.len() > 1 {
4160                        // is ligature
4161
4162                        let x = x - glyphs[0].point.x;
4163
4164                        let mut split = true;
4165                        for (i, font_caret) in font.ligature_caret_offsets(glyphs[0].index).enumerate() {
4166                            if i == lig_parts.len() {
4167                                break;
4168                            }
4169                            split = false;
4170
4171                            if font_caret > x {
4172                                // found font defined caret
4173                                return char_a + lig_parts[i] as usize;
4174                            }
4175                        }
4176                        if split {
4177                            // no font caret, ligature glyph is split in equal parts
4178                            let lig_part = advance / lig_parts.len() as f32;
4179                            let mut lig_x = lig_part;
4180                            if is_rtl {
4181                                for c in lig_parts.into_iter().rev() {
4182                                    if lig_x > x {
4183                                        // fond
4184                                        return char_a + c as usize;
4185                                    }
4186                                    lig_x += lig_part;
4187                                }
4188                            } else {
4189                                for c in lig_parts {
4190                                    if lig_x > x {
4191                                        return char_a + c as usize;
4192                                    }
4193                                    lig_x += lig_part;
4194                                }
4195                            }
4196                        }
4197                    }
4198                }
4199                // not ligature
4200
4201                let middle_x = glyphs[0].point.x + advance / 2.0;
4202
4203                return if is_rtl {
4204                    if x <= middle_x { char_b } else { char_a }
4205                } else if x <= middle_x {
4206                    char_a
4207                } else {
4208                    char_b
4209                };
4210            }
4211        }
4212
4213        let mut start = is_rtl;
4214        if matches!(self.kind(), TextSegmentKind::LineBreak) {
4215            start = !start;
4216        }
4217        if start { txt_range.start } else { txt_range.end }
4218    }
4219
4220    /// Gets the segment index in the line.
4221    pub fn index(&self) -> usize {
4222        self.index - self.text.lines.segs(self.line_index).start()
4223    }
4224}
4225
4226const WORD_CACHE_MAX_LEN: usize = 32;
4227const WORD_CACHE_MAX_ENTRIES: usize = 10_000;
4228
4229#[derive(Hash, PartialEq, Eq)]
4230pub(super) struct WordCacheKey<S> {
4231    string: S,
4232    ctx_key: WordContextKey,
4233}
4234#[derive(Hash)]
4235struct WordCacheKeyRef<'a, S> {
4236    string: &'a S,
4237    ctx_key: &'a WordContextKey,
4238}
4239
4240#[derive(Hash, PartialEq, Eq, Clone)]
4241pub(super) struct WordContextKey {
4242    lang: unic_langid::subtags::Language,
4243    script: Option<unic_langid::subtags::Script>,
4244    direction: LayoutDirection,
4245    features: Box<[usize]>,
4246}
4247impl WordContextKey {
4248    pub fn new(lang: &Lang, direction: LayoutDirection, font_features: &RFontFeatures) -> Self {
4249        let is_64 = mem::size_of::<usize>() == mem::size_of::<u64>();
4250
4251        let mut features = vec![];
4252
4253        if !font_features.is_empty() {
4254            features.reserve(font_features.len() * if is_64 { 3 } else { 4 });
4255            for feature in font_features {
4256                let tag = u32::from_be_bytes(feature.tag.to_be_bytes());
4257                if is_64 {
4258                    let mut h = tag as u64;
4259                    h |= (feature.value as u64) << 32;
4260                    features.push(h as usize);
4261                } else {
4262                    features.push(tag as usize);
4263                    features.push(feature.value as usize);
4264                }
4265
4266                features.push(feature.start as usize);
4267                features.push(feature.end as usize);
4268            }
4269        }
4270
4271        WordContextKey {
4272            lang: lang.language,
4273            script: lang.script,
4274            direction,
4275            features: features.into_boxed_slice(),
4276        }
4277    }
4278
4279    pub fn harfbuzz_lang(&self) -> Option<harfrust::Language> {
4280        self.lang.as_str().parse().ok()
4281    }
4282
4283    pub fn harfbuzz_script(&self) -> Option<harfrust::Script> {
4284        let t: u32 = self.script?.into();
4285        let t = t.to_le_bytes(); // Script is a TinyStr4 that uses LE
4286        harfrust::Script::from_iso15924_tag(skrifa::Tag::new(&[t[0], t[1], t[2], t[3]]))
4287    }
4288
4289    pub fn harfbuzz_direction(&self) -> harfrust::Direction {
4290        into_harf_direction(self.direction)
4291    }
4292}
4293
4294#[derive(Debug, Clone, Default)]
4295pub(super) struct ShapedSegmentData {
4296    glyphs: Vec<ShapedGlyph>,
4297    x_advance: f32,
4298    y_advance: f32,
4299}
4300#[derive(Debug, Clone, Copy)]
4301struct ShapedGlyph {
4302    /// glyph index
4303    index: u32,
4304    /// char index
4305    cluster: u32,
4306    point: (f32, f32),
4307}
4308
4309impl Font {
4310    fn buffer_segment(&self, segment: &str, key: &WordContextKey) -> harfrust::UnicodeBuffer {
4311        let mut buffer = harfrust::UnicodeBuffer::new();
4312        buffer.set_direction(key.harfbuzz_direction());
4313        buffer.set_cluster_level(harfrust::BufferClusterLevel::MonotoneCharacters);
4314
4315        if let Some(lang) = key.harfbuzz_lang() {
4316            buffer.set_language(lang);
4317        }
4318        if let Some(script) = key.harfbuzz_script() {
4319            buffer.set_script(script);
4320        }
4321
4322        buffer.push_str(segment);
4323        buffer
4324    }
4325
4326    fn shape_segment_no_cache(&self, seg: &str, key: &WordContextKey, features: &[harfrust::Feature]) -> ShapedSegmentData {
4327        let buffer = if let Some(font) = self.face().raw()
4328            && let Some(shaper_data) = &self.0.shaper_cache
4329        {
4330            let buffer = self.buffer_segment(seg, key);
4331            let shaper_builder = shaper_data.shaper(&font);
4332            let shaper = shaper_builder.build();
4333            shaper.shape(buffer, harfrust::ShapeOptions::new().features(features))
4334        } else {
4335            return ShapedSegmentData {
4336                glyphs: vec![],
4337                x_advance: 0.0,
4338                y_advance: 0.0,
4339            };
4340        };
4341
4342        let size_scale = self.0.size.0 as f32 / self.0.metrics.units_per_em as f32;
4343        let to_layout = |p: i32| p as f32 * size_scale;
4344
4345        let mut w_x_advance = 0.0;
4346        let mut w_y_advance = 0.0;
4347
4348        let glyphs: Vec<_> = buffer
4349            .glyph_infos()
4350            .iter()
4351            .zip(buffer.glyph_positions())
4352            .map(|(i, p)| {
4353                let x_offset = to_layout(p.x_offset);
4354                let y_offset = -to_layout(p.y_offset);
4355                let x_advance = to_layout(p.x_advance);
4356                let y_advance = to_layout(p.y_advance);
4357
4358                let point = (w_x_advance + x_offset, w_y_advance + y_offset);
4359                w_x_advance += x_advance;
4360                w_y_advance += y_advance;
4361
4362                ShapedGlyph {
4363                    index: i.glyph_id,
4364                    cluster: i.cluster,
4365                    point,
4366                }
4367            })
4368            .collect();
4369
4370        ShapedSegmentData {
4371            glyphs,
4372            x_advance: w_x_advance,
4373            y_advance: w_y_advance,
4374        }
4375    }
4376
4377    fn shape_segment<R>(
4378        &self,
4379        seg: &str,
4380        word_ctx_key: &WordContextKey,
4381        features: &[harfrust::Feature],
4382        out: impl FnOnce(&ShapedSegmentData) -> R,
4383    ) -> R {
4384        if !(1..=WORD_CACHE_MAX_LEN).contains(&seg.len()) || self.face().is_empty() {
4385            let seg = self.shape_segment_no_cache(seg, word_ctx_key, features);
4386            out(&seg)
4387        } else if let Some(small) = Self::to_small_word(seg) {
4388            // try cached
4389            let cache = self.0.small_word_cache.read();
4390
4391            let hash = cache.hasher().hash_one(WordCacheKeyRef {
4392                string: &small,
4393                ctx_key: word_ctx_key,
4394            });
4395
4396            if let Some((_, seg)) = cache
4397                .raw_entry()
4398                .from_hash(hash, |e| e.string == small && &e.ctx_key == word_ctx_key)
4399            {
4400                return out(seg);
4401            }
4402            drop(cache);
4403
4404            // shape and cache, can end-up shaping the same word here, but that is better then write locking
4405            let seg = self.shape_segment_no_cache(seg, word_ctx_key, features);
4406            let key = WordCacheKey {
4407                string: small,
4408                ctx_key: word_ctx_key.clone(),
4409            };
4410            let r = out(&seg);
4411            let mut cache = self.0.small_word_cache.write();
4412            if cache.len() > WORD_CACHE_MAX_ENTRIES {
4413                cache.clear();
4414            }
4415            cache.insert(key, seg);
4416            r
4417        } else {
4418            // try cached
4419            let cache = self.0.word_cache.read();
4420
4421            let hash = cache.hasher().hash_one(WordCacheKeyRef {
4422                string: &seg,
4423                ctx_key: word_ctx_key,
4424            });
4425
4426            if let Some((_, seg)) = cache
4427                .raw_entry()
4428                .from_hash(hash, |e| e.string.as_str() == seg && &e.ctx_key == word_ctx_key)
4429            {
4430                return out(seg);
4431            }
4432            drop(cache);
4433
4434            // shape and cache, can end-up shaping the same word here, but that is better then write locking
4435            let string = seg.to_owned();
4436            let seg = self.shape_segment_no_cache(seg, word_ctx_key, features);
4437            let key = WordCacheKey {
4438                string,
4439                ctx_key: word_ctx_key.clone(),
4440            };
4441            let r = out(&seg);
4442            let mut cache = self.0.word_cache.write();
4443            if cache.len() > WORD_CACHE_MAX_ENTRIES {
4444                cache.clear();
4445            }
4446            cache.insert(key, seg);
4447            r
4448        }
4449    }
4450
4451    /// Glyph index for the space `' '` character.
4452    pub fn space_index(&self) -> GlyphIndex {
4453        self.shape_space().0
4454    }
4455
4456    /// Returns the horizontal advance of the space `' '` character.
4457    pub fn space_x_advance(&self) -> Px {
4458        self.shape_space().1
4459    }
4460
4461    fn shape_space(&self) -> (GlyphIndex, Px) {
4462        let mut id = 0;
4463        let mut adv = 0.0;
4464        self.shape_segment(
4465            " ",
4466            &WordContextKey {
4467                lang: unic_langid::subtags::Language::from_bytes(b"und").unwrap(),
4468                script: None,
4469                direction: LayoutDirection::LTR,
4470                features: Box::new([]),
4471            },
4472            &[],
4473            |r| {
4474                id = r.glyphs.last().map(|g| g.index).unwrap_or(0);
4475                adv = r.x_advance;
4476            },
4477        );
4478        (id, Px(adv as _))
4479    }
4480
4481    /// Calculates a [`ShapedText`].
4482    pub fn shape_text(self: &Font, text: &SegmentedText, config: &TextShapingArgs) -> ShapedText {
4483        ShapedTextBuilder::shape_text(std::slice::from_ref(self), text, config)
4484    }
4485
4486    /// Sends the sized vector path for a glyph to `sink`.
4487    ///
4488    /// Returns the glyph bounds if a full outline was sent to the sink.
4489    pub fn outline(&self, glyph_id: GlyphIndex, sink: &mut impl OutlineSink) -> Option<PxRect> {
4490        struct AdapterSink<'a, S> {
4491            sink: &'a mut S,
4492            bounds: euclid::Box2D<f32, Px>,
4493        }
4494        impl<S> AdapterSink<'_, S> {
4495            fn point(&mut self, x: f32, y: f32) -> euclid::Point2D<f32, Px> {
4496                let p = euclid::point2(x, y);
4497                self.bounds.min = self.bounds.min.min(p);
4498                self.bounds.max = self.bounds.max.max(p);
4499                p
4500            }
4501        }
4502        impl<S: OutlineSink> skrifa::outline::OutlinePen for AdapterSink<'_, S> {
4503            fn move_to(&mut self, x: f32, y: f32) {
4504                let p = self.point(x, y);
4505                self.sink.move_to(p)
4506            }
4507
4508            fn line_to(&mut self, x: f32, y: f32) {
4509                let p = self.point(x, y);
4510                self.sink.line_to(p)
4511            }
4512
4513            fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
4514                let ctrl = self.point(x1, y1);
4515                let to = self.point(x, y);
4516                self.sink.quadratic_curve_to(ctrl, to)
4517            }
4518
4519            fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
4520                let l_from = self.point(x1, y1);
4521                let l_to = self.point(x2, y2);
4522                let to = self.point(x, y);
4523                self.sink.cubic_curve_to((l_from, l_to), to)
4524            }
4525
4526            fn close(&mut self) {
4527                self.sink.close()
4528            }
4529        }
4530        let size = skrifa::instance::Size::new(self.size().0 as f32);
4531
4532        let f = self.face().raw()?;
4533        let o = f.outline_glyphs().get(skrifa::GlyphId::new(glyph_id))?;
4534        let mut sink = AdapterSink {
4535            sink,
4536            bounds: euclid::Box2D::zero(),
4537        };
4538        o.draw(
4539            skrifa::outline::DrawSettings::unhinted(size, skrifa::instance::LocationRef::default()),
4540            &mut sink,
4541        )
4542        .ok()?;
4543        Some(sink.bounds.to_rect().cast())
4544    }
4545
4546    /// Ray cast an horizontal line across the glyph and returns the entry and exit hits.
4547    ///
4548    /// The `line_y_range` are two vertical offsets relative to the baseline, the offsets define
4549    /// the start and inclusive end of the horizontal line, that is, `(underline, underline + thickness)`, note
4550    /// that positions under the baseline are negative so a 2px underline set 1px under the baseline becomes `(-1.0, -3.0)`.
4551    ///
4552    /// Returns `Ok(Some(x_enter, x_exit))` where the two values are x-advances, returns `None` if there is no hit.
4553    /// The first x-advance is from the left typographic border to the first hit on the outline,
4554    /// the second x-advance is from the first across the outline to the exit hit.
4555    pub fn h_line_hits(&self, glyph_id: GlyphIndex, line_y_range: (f32, f32)) -> Option<(f32, f32)> {
4556        // Algorithm:
4557        //
4558        // - Ignore curves, everything is direct line.
4559        // - If a line-y crosses `line_y_range` register the min-x and max-x from the two points.
4560        // - Same if a line is inside `line_y_range`.
4561        struct InterceptsSink {
4562            start: Option<euclid::Point2D<f32, Px>>,
4563            current: euclid::Point2D<f32, Px>,
4564            under: (bool, bool),
4565
4566            line_y_range: (f32, f32),
4567            hit: Option<(f32, f32)>,
4568        }
4569        impl OutlineSink for InterceptsSink {
4570            fn move_to(&mut self, to: euclid::Point2D<f32, Px>) {
4571                self.start = Some(to);
4572                self.current = to;
4573                self.under = (to.y < self.line_y_range.0, to.y < self.line_y_range.1);
4574            }
4575
4576            fn line_to(&mut self, to: euclid::Point2D<f32, Px>) {
4577                let under = (to.y < self.line_y_range.0, to.y < self.line_y_range.1);
4578
4579                if self.under != under || under == (true, false) {
4580                    // crossed one or two y-range boundaries or both points are inside
4581                    self.under = under;
4582
4583                    let (x0, x1) = if self.current.x < to.x {
4584                        (self.current.x, to.x)
4585                    } else {
4586                        (to.x, self.current.x)
4587                    };
4588                    if let Some((min, max)) = &mut self.hit {
4589                        *min = min.min(x0);
4590                        *max = max.max(x1);
4591                    } else {
4592                        self.hit = Some((x0, x1));
4593                    }
4594                }
4595
4596                self.current = to;
4597                self.under = under;
4598            }
4599
4600            fn quadratic_curve_to(&mut self, _: euclid::Point2D<f32, Px>, to: euclid::Point2D<f32, Px>) {
4601                self.line_to(to);
4602            }
4603
4604            fn cubic_curve_to(&mut self, _: (euclid::Point2D<f32, Px>, euclid::Point2D<f32, Px>), to: euclid::Point2D<f32, Px>) {
4605                self.line_to(to);
4606            }
4607
4608            fn close(&mut self) {
4609                if let Some(s) = self.start.take()
4610                    && s != self.current
4611                {
4612                    self.line_to(s);
4613                }
4614            }
4615        }
4616        let mut sink = InterceptsSink {
4617            start: None,
4618            current: euclid::point2(0.0, 0.0),
4619            under: (false, false),
4620
4621            line_y_range,
4622            hit: None,
4623        };
4624        self.outline(glyph_id, &mut sink)?;
4625
4626        sink.hit.map(|(a, b)| (a, b - a))
4627    }
4628}
4629
4630/// Receives Bézier path rendering commands from [`Font::outline`].
4631///
4632/// The points are relative to the baseline, negative values under, positive over.
4633pub trait OutlineSink {
4634    /// Moves the pen to a point.
4635    fn move_to(&mut self, to: euclid::Point2D<f32, Px>);
4636    /// Draws a line to a point.
4637    fn line_to(&mut self, to: euclid::Point2D<f32, Px>);
4638    /// Draws a quadratic Bézier curve to a point.
4639    fn quadratic_curve_to(&mut self, ctrl: euclid::Point2D<f32, Px>, to: euclid::Point2D<f32, Px>);
4640    /// Draws a cubic Bézier curve to a point.
4641    ///
4642    /// The `ctrl` is a line (from, to).
4643    fn cubic_curve_to(&mut self, ctrl: (euclid::Point2D<f32, Px>, euclid::Point2D<f32, Px>), to: euclid::Point2D<f32, Px>);
4644    /// Closes the path, returning to the first point in it.
4645    fn close(&mut self);
4646}
4647
4648impl FontList {
4649    /// Calculates a [`ShapedText`] using the [best](FontList::best) font in this list and the other fonts as fallback.
4650    pub fn shape_text(&self, text: &SegmentedText, config: &TextShapingArgs) -> ShapedText {
4651        ShapedTextBuilder::shape_text(self, text, config)
4652    }
4653}
4654
4655/// Like [`std::ops::Range<usize>`], but implements [`Copy`].
4656#[derive(Clone, Copy)]
4657struct IndexRange(pub usize, pub usize);
4658impl fmt::Debug for IndexRange {
4659    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4660        write!(f, "{}..{}", self.0, self.1)
4661    }
4662}
4663impl IntoIterator for IndexRange {
4664    type Item = usize;
4665
4666    type IntoIter = std::ops::Range<usize>;
4667
4668    fn into_iter(self) -> Self::IntoIter {
4669        self.iter()
4670    }
4671}
4672impl From<IndexRange> for std::ops::Range<usize> {
4673    fn from(c: IndexRange) -> Self {
4674        c.iter()
4675    }
4676}
4677impl From<std::ops::Range<usize>> for IndexRange {
4678    fn from(r: std::ops::Range<usize>) -> Self {
4679        IndexRange(r.start, r.end)
4680    }
4681}
4682impl IndexRange {
4683    pub fn from_bounds(bounds: impl ops::RangeBounds<usize>) -> Self {
4684        // start..end
4685        let start = match bounds.start_bound() {
4686            ops::Bound::Included(&i) => i,
4687            ops::Bound::Excluded(&i) => i + 1,
4688            ops::Bound::Unbounded => 0,
4689        };
4690        let end = match bounds.end_bound() {
4691            ops::Bound::Included(&i) => i + 1,
4692            ops::Bound::Excluded(&i) => i,
4693            ops::Bound::Unbounded => 0,
4694        };
4695        Self(start, end)
4696    }
4697
4698    /// Into `Range<usize>`.
4699    pub fn iter(self) -> std::ops::Range<usize> {
4700        self.0..self.1
4701    }
4702
4703    /// `self.0`
4704    pub fn start(self) -> usize {
4705        self.0
4706    }
4707
4708    /// `self.1`
4709    pub fn end(self) -> usize {
4710        self.1
4711    }
4712
4713    /// `self.end - self.start`
4714    pub fn len(self) -> usize {
4715        self.end() - self.start()
4716    }
4717}
4718impl std::ops::RangeBounds<usize> for IndexRange {
4719    fn start_bound(&self) -> std::ops::Bound<&usize> {
4720        std::ops::Bound::Included(&self.0)
4721    }
4722
4723    fn end_bound(&self) -> std::ops::Bound<&usize> {
4724        std::ops::Bound::Excluded(&self.1)
4725    }
4726}
4727
4728/// `f32` comparison, panics for `NaN`.
4729pub fn f32_cmp(a: &f32, b: &f32) -> std::cmp::Ordering {
4730    a.partial_cmp(b).unwrap()
4731}
4732
4733fn into_harf_direction(d: LayoutDirection) -> harfrust::Direction {
4734    match d {
4735        LayoutDirection::LTR => harfrust::Direction::LeftToRight,
4736        LayoutDirection::RTL => harfrust::Direction::RightToLeft,
4737    }
4738}
4739
4740#[cfg(test)]
4741mod tests {
4742    use crate::{
4743        FONTS, Font, FontName, FontStretch, FontStyle, FontWeight, ParagraphBreak, SegmentedText, TextReshapingArgs, TextShapingArgs,
4744        WordContextKey,
4745    };
4746    use zng_app::APP;
4747    use zng_ext_l10n::lang;
4748    use zng_layout::{
4749        context::LayoutDirection,
4750        unit::{Px, PxConstraints2d},
4751    };
4752
4753    fn test_font() -> Font {
4754        let mut app = APP.minimal().run_headless(false);
4755        let font = app
4756            .run_test(async {
4757                FONTS
4758                    .normal(&FontName::sans_serif(), &lang!(und))
4759                    .wait_rsp()
4760                    .await
4761                    .unwrap()
4762                    .sized(Px(20), vec![])
4763            })
4764            .unwrap();
4765        drop(app);
4766        font
4767    }
4768
4769    #[test]
4770    fn set_line_spacing() {
4771        let text = "0\n1\n2\n3\n4";
4772        test_line_spacing(text, Px(20), Px(0));
4773        test_line_spacing(text, Px(0), Px(20));
4774        test_line_spacing(text, Px(4), Px(6));
4775        test_line_spacing(text, Px(4), Px(4));
4776        test_line_spacing("a line\nanother\nand another", Px(20), Px(0));
4777        test_line_spacing("", Px(20), Px(0));
4778        test_line_spacing("a line", Px(20), Px(0));
4779    }
4780    fn test_line_spacing(text: &'static str, from: Px, to: Px) {
4781        let font = test_font();
4782        let mut config = TextShapingArgs {
4783            line_height: Px(40),
4784            line_spacing: from,
4785            ..Default::default()
4786        };
4787
4788        let text = SegmentedText::new(text, LayoutDirection::LTR);
4789        let mut test = font.shape_text(&text, &config);
4790
4791        config.line_spacing = to;
4792        let expected = font.shape_text(&text, &config);
4793
4794        assert_eq!(from, test.line_spacing());
4795        test.reshape_lines(&TextReshapingArgs {
4796            constraints: PxConstraints2d::new_fill_size(test.align_size()),
4797            inline_constraints: None,
4798            align: test.align(),
4799            overflow_align: test.overflow_align(),
4800            direction: test.direction(),
4801            line_height: test.line_height(),
4802            line_spacing: to,
4803            paragraph_spacing: Px(0),
4804            paragraph_indent: (Px(0), false),
4805            paragraph_break: ParagraphBreak::None,
4806        });
4807        assert_eq!(to, test.line_spacing());
4808
4809        for (i, (g0, g1)) in test.glyphs.iter().zip(expected.glyphs.iter()).enumerate() {
4810            assert_eq!(g0, g1, "testing {from} to {to}, glyph {i} is not equal");
4811        }
4812
4813        assert_eq!(test.size(), expected.size());
4814    }
4815
4816    #[test]
4817    fn set_line_height() {
4818        let text = "0\n1\n2\n3\n4";
4819        test_line_height(text, Px(20), Px(20));
4820        test_line_height(text, Px(20), Px(10));
4821        test_line_height(text, Px(10), Px(20));
4822        test_line_height("a line\nanother\nand another", Px(20), Px(10));
4823        test_line_height("", Px(20), Px(10));
4824        test_line_height("a line", Px(20), Px(10));
4825    }
4826    fn test_line_height(text: &'static str, from: Px, to: Px) {
4827        let font = test_font();
4828        let mut config = TextShapingArgs {
4829            line_height: from,
4830            line_spacing: Px(20),
4831            ..Default::default()
4832        };
4833
4834        let text = SegmentedText::new(text, LayoutDirection::LTR);
4835        let mut test = font.shape_text(&text, &config);
4836
4837        config.line_height = to;
4838        let expected = font.shape_text(&text, &config);
4839
4840        assert_eq!(from, test.line_height());
4841        test.reshape_lines(&TextReshapingArgs {
4842            constraints: PxConstraints2d::new_fill_size(test.align_size()),
4843            inline_constraints: None,
4844            align: test.align(),
4845            overflow_align: test.overflow_align(),
4846            direction: test.direction(),
4847            line_height: to,
4848            line_spacing: test.line_spacing(),
4849            paragraph_spacing: Px(0),
4850            paragraph_indent: (Px(0), false),
4851            paragraph_break: ParagraphBreak::None,
4852        });
4853        assert_eq!(to, test.line_height());
4854
4855        for (i, (g0, g1)) in test.glyphs.iter().zip(expected.glyphs.iter()).enumerate() {
4856            assert_eq!(g0, g1, "testing {from} to {to}, glyph {i} is not equal");
4857        }
4858
4859        assert_eq!(test.size(), expected.size());
4860    }
4861
4862    #[test]
4863    fn font_fallback_issue() {
4864        let mut app = APP.minimal().run_headless(false);
4865        app.run_test(async {
4866            let font = FONTS
4867                .list(
4868                    &[FontName::new("Consolas"), FontName::monospace()],
4869                    FontStyle::Normal,
4870                    FontWeight::NORMAL,
4871                    FontStretch::NORMAL,
4872                    &lang!(und),
4873                )
4874                .wait_rsp()
4875                .await
4876                .sized(Px(20), vec![]);
4877
4878            let config = TextShapingArgs::default();
4879
4880            let txt_seg = SegmentedText::new("النص ثنائي الاتجاه (بالإنجليزية:Bi", LayoutDirection::RTL);
4881            let txt_shape = font.shape_text(&txt_seg, &config);
4882
4883            let _ok = (txt_seg, txt_shape);
4884        })
4885        .unwrap()
4886    }
4887
4888    #[test]
4889    fn cluster_is_byte() {
4890        let font = test_font();
4891
4892        let data = font.shape_segment_no_cache("£a", &WordContextKey::new(&lang!("en-US"), LayoutDirection::LTR, &vec![]), &[]);
4893
4894        for ((i, _), g) in "£a".char_indices().zip(&data.glyphs) {
4895            assert_eq!(i as u32, g.cluster);
4896        }
4897    }
4898}