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