zng_ext_font/
shaping.rs

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