zng_ext_font/
shaping.rs

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