zng_ext_font/
shaping.rs

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