kas_text/display/
glyph_pos.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Methods using positioned glyphs
7
8#![allow(clippy::collapsible_else_if)]
9#![allow(clippy::or_fun_call)]
10#![allow(clippy::never_loop)]
11#![allow(clippy::needless_range_loop)]
12
13use super::{Line, TextDisplay};
14use crate::conv::to_usize;
15use crate::fonts::{self, FaceId};
16use crate::{shaper, Glyph, Range, Vec2};
17
18/// Effect formatting marker
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct Effect {
22    /// Index in text at which formatting becomes active
23    ///
24    /// (Note that we use `u32` not `usize` since it can be assumed text length
25    /// will never exceed `u32::MAX`.)
26    pub start: u32,
27    /// User-specified value, e.g. index into a colour palette
28    pub e: u16,
29    /// Effect flags
30    pub flags: EffectFlags,
31}
32
33bitflags::bitflags! {
34    /// Text effects
35    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
36    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37    pub struct EffectFlags: u16 {
38        /// Glyph is underlined
39        const UNDERLINE = 1 << 0;
40        /// Glyph is crossed through by a center-line
41        const STRIKETHROUGH = 1 << 1;
42    }
43}
44
45/// Used to return the position of a glyph with associated metrics
46#[derive(Copy, Clone, Debug, Default, PartialEq)]
47pub struct MarkerPos {
48    /// (x, y) coordinate of glyph
49    pub pos: Vec2,
50    /// Ascent (subtract from y to get top)
51    pub ascent: f32,
52    /// Descent (subtract from y to get bottom)
53    pub descent: f32,
54    level: u8,
55}
56
57impl MarkerPos {
58    /// Returns the embedding level
59    ///
60    /// According to Unicode Technical Report #9, the embedding level is
61    /// guaranteed to be between 0 and 125 (inclusive), with a default level of
62    /// zero and where odd levels are right-to-left.
63    #[inline]
64    pub fn embedding_level(&self) -> u8 {
65        self.level
66    }
67
68    /// Returns true if the cursor is left-to-right
69    #[inline]
70    pub fn is_ltr(&self) -> bool {
71        self.level % 2 == 0
72    }
73
74    /// Returns true if the cursor is right-to-left
75    #[inline]
76    pub fn is_rtl(&self) -> bool {
77        self.level % 2 == 1
78    }
79}
80
81pub struct MarkerPosIter {
82    v: [MarkerPos; 2],
83    a: usize,
84    b: usize,
85}
86
87impl MarkerPosIter {
88    /// Directly access the slice of results
89    ///
90    /// The result excludes elements removed by iteration.
91    pub fn as_slice(&self) -> &[MarkerPos] {
92        &self.v[self.a..self.b]
93    }
94}
95
96impl Iterator for MarkerPosIter {
97    type Item = MarkerPos;
98
99    #[inline]
100    fn next(&mut self) -> Option<Self::Item> {
101        if self.a < self.b {
102            let i = self.a;
103            self.a = i + 1;
104            Some(self.v[i])
105        } else {
106            None
107        }
108    }
109
110    #[inline]
111    fn size_hint(&self) -> (usize, Option<usize>) {
112        let len = self.b - self.a;
113        (len, Some(len))
114    }
115}
116
117impl DoubleEndedIterator for MarkerPosIter {
118    #[inline]
119    fn next_back(&mut self) -> Option<Self::Item> {
120        if self.a < self.b {
121            let i = self.b - 1;
122            self.b = i;
123            Some(self.v[i])
124        } else {
125            None
126        }
127    }
128}
129
130impl ExactSizeIterator for MarkerPosIter {}
131
132/// A sequence of positioned glyphs with effects
133///
134/// Yielded by [`TextDisplay::runs`].
135pub struct GlyphRun<'a> {
136    run: &'a shaper::GlyphRun,
137    range: Range,
138    offset: Vec2,
139    effects: &'a [Effect],
140}
141
142impl<'a> GlyphRun<'a> {
143    /// Get the font face used for this run
144    #[inline]
145    pub fn face_id(&self) -> FaceId {
146        self.run.face_id
147    }
148
149    /// Get the font size used for this run
150    ///
151    /// Units are dots-per-Em (see [crate::fonts]).
152    #[inline]
153    pub fn dpem(&self) -> f32 {
154        self.run.dpem
155    }
156
157    /// Get an iterator over glyphs for this run
158    ///
159    /// This method ignores effects; if you want those call
160    /// [`Self::glyphs_with_effects`] instead.
161    pub fn glyphs(&self) -> impl Iterator<Item = Glyph> + '_ {
162        self.run.glyphs[self.range.to_std()]
163            .iter()
164            .map(|glyph| Glyph {
165                index: glyph.index,
166                id: glyph.id,
167                position: glyph.position + self.offset,
168            })
169    }
170
171    /// Yield glyphs and effects for this run
172    ///
173    /// The callback `f` receives `glyph, e` where `e` is the [`Effect::e`]
174    /// value (defaults to 0).
175    ///
176    /// The callback `g` receives positioning for each underline/strike-through
177    /// segment: `x1, x2, y_top, h` where `h` is the thickness (height). Note
178    /// that it is possible to have `h < 1.0` and `y_top, y_top + h` to round to
179    /// the same number; the renderer is responsible for ensuring such lines
180    /// are actually visible. The last parameter is `e` as for `f`.
181    ///
182    /// Note: this is more computationally expensive than [`GlyphRun::glyphs`],
183    /// so you may prefer to call that. Optionally one may choose to cache the
184    /// result, though this is not really necessary.
185    pub fn glyphs_with_effects<F, G>(&self, mut f: F, mut g: G)
186    where
187        F: FnMut(Glyph, u16),
188        G: FnMut(f32, f32, f32, f32, u16),
189    {
190        let sf = fonts::library()
191            .get_face(self.run.face_id)
192            .scale_by_dpu(self.run.dpu);
193
194        let ltr = self.run.level.is_ltr();
195
196        let mut underline = None;
197        let mut strikethrough = None;
198
199        let mut effect_cur = usize::MAX;
200        let mut effect_next = 0;
201
202        // We iterate in left-to-right order regardless of text direction.
203        // Start by finding the effect applicable to the left-most glyph.
204        let left_index = self.run.glyphs[self.range.start()].index;
205        while self
206            .effects
207            .get(effect_next)
208            .map(|e| e.start <= left_index)
209            .unwrap_or(false)
210        {
211            effect_cur = effect_next;
212            effect_next += 1;
213        }
214
215        let mut next_start = self
216            .effects
217            .get(effect_next)
218            .map(|e| e.start)
219            .unwrap_or(u32::MAX);
220
221        let mut fmt = self
222            .effects
223            .get(effect_cur)
224            .cloned()
225            .unwrap_or(Effect::default());
226
227        // In case an effect applies to the left-most glyph, it starts from that
228        // glyph's x coordinate.
229        if !fmt.flags.is_empty() {
230            let glyph = &self.run.glyphs[self.range.start()];
231            let position = glyph.position + self.offset;
232            if fmt.flags.contains(EffectFlags::UNDERLINE) {
233                if let Some(metrics) = sf.underline_metrics() {
234                    let y_top = position.1 - metrics.position;
235                    let h = metrics.thickness;
236                    let x1 = position.0;
237                    underline = Some((x1, y_top, h, fmt.e));
238                }
239            }
240            if fmt.flags.contains(EffectFlags::STRIKETHROUGH) {
241                if let Some(metrics) = sf.strikethrough_metrics() {
242                    let y_top = position.1 - metrics.position;
243                    let h = metrics.thickness;
244                    let x1 = position.0;
245                    strikethrough = Some((x1, y_top, h, fmt.e));
246                }
247            }
248        }
249
250        // Iterate over glyphs in left-to-right order.
251        for mut glyph in self.run.glyphs[self.range.to_std()].iter().cloned() {
252            glyph.position += self.offset;
253
254            // Does the effect change?
255            if (ltr && next_start <= glyph.index) || (!ltr && fmt.start > glyph.index) {
256                if ltr {
257                    // Find the next active effect
258                    loop {
259                        effect_cur = effect_next;
260                        effect_next += 1;
261                        if self
262                            .effects
263                            .get(effect_next)
264                            .map(|e| e.start > glyph.index)
265                            .unwrap_or(true)
266                        {
267                            break;
268                        }
269                    }
270                    next_start = self
271                        .effects
272                        .get(effect_next)
273                        .map(|e| e.start)
274                        .unwrap_or(u32::MAX);
275                } else {
276                    // Find the previous active effect
277                    loop {
278                        effect_cur = effect_cur.wrapping_sub(1);
279                        if self.effects.get(effect_cur).map(|e| e.start).unwrap_or(0) <= glyph.index
280                        {
281                            break;
282                        }
283                    }
284                }
285                fmt = self
286                    .effects
287                    .get(effect_cur)
288                    .cloned()
289                    .unwrap_or(Effect::default());
290
291                if underline.is_some() != fmt.flags.contains(EffectFlags::UNDERLINE) {
292                    if let Some((x1, y_top, h, e)) = underline {
293                        let x2 = glyph.position.0;
294                        g(x1, x2, y_top, h, e);
295                        underline = None;
296                    } else if let Some(metrics) = sf.underline_metrics() {
297                        let y_top = glyph.position.1 - metrics.position;
298                        let h = metrics.thickness;
299                        let x1 = glyph.position.0;
300                        underline = Some((x1, y_top, h, fmt.e));
301                    }
302                }
303                if strikethrough.is_some() != fmt.flags.contains(EffectFlags::STRIKETHROUGH) {
304                    if let Some((x1, y_top, h, e)) = strikethrough {
305                        let x2 = glyph.position.0;
306                        g(x1, x2, y_top, h, e);
307                        strikethrough = None;
308                    } else if let Some(metrics) = sf.strikethrough_metrics() {
309                        let y_top = glyph.position.1 - metrics.position;
310                        let h = metrics.thickness;
311                        let x1 = glyph.position.0;
312                        strikethrough = Some((x1, y_top, h, fmt.e));
313                    }
314                }
315            }
316
317            f(glyph, fmt.e);
318        }
319
320        // Effects end at the following glyph's start (or end of this run part)
321        if let Some((x1, y_top, h, e)) = underline {
322            let x2 = if self.range.end() < self.run.glyphs.len() {
323                self.run.glyphs[self.range.end()].position.0
324            } else {
325                self.run.caret
326            } + self.offset.0;
327            g(x1, x2, y_top, h, e);
328        }
329        if let Some((x1, y_top, h, e)) = strikethrough {
330            let x2 = if self.range.end() < self.run.glyphs.len() {
331                self.run.glyphs[self.range.end()].position.0
332            } else {
333                self.run.caret
334            } + self.offset.0;
335            g(x1, x2, y_top, h, e);
336        }
337    }
338}
339
340impl TextDisplay {
341    /// Find the starting position (top-left) of the glyph at the given index
342    ///
343    /// [Requires status][Self#status-of-preparation]:
344    /// text is fully prepared for display.
345    ///
346    /// The index should be no greater than the text length. It is not required
347    /// to be on a code-point boundary. Returns an iterator over matching
348    /// positions. Length of results is guaranteed to be one of 0, 1 or 2:
349    ///
350    /// -   0 if the index is not included in any run of text (probably only
351    ///     possible within a multi-byte line break or beyond the text length)
352    /// -   1 is the normal case
353    /// -   2 if the index is at the end of one run of text *and* at the start
354    ///     of another; these positions may be the same or may not be (over
355    ///     line breaks and with bidirectional text). If only a single position
356    ///     is desired, usually the latter is preferred (via `next_back()`).
357    ///
358    /// The result is not guaranteed to be within [`Self::bounding_box`].
359    /// Depending on the use-case, the caller may need to clamp the resulting
360    /// position.
361    pub fn text_glyph_pos(&self, index: usize) -> MarkerPosIter {
362        let mut v: [MarkerPos; 2] = Default::default();
363        let (a, mut b) = (0, 0);
364        let mut push_result = |pos, ascent, descent, level| {
365            v[b] = MarkerPos {
366                pos,
367                ascent,
368                descent,
369                level,
370            };
371            b += 1;
372        };
373
374        // We don't care too much about performance: use a naive search strategy
375        'a: for run_part in &self.wrapped_runs {
376            if index > to_usize(run_part.text_end) {
377                continue;
378            }
379
380            let glyph_run = &self.runs[to_usize(run_part.glyph_run)];
381            let sf = fonts::library()
382                .get_face(glyph_run.face_id)
383                .scale_by_dpu(glyph_run.dpu);
384
385            // If index is at the end of a run, we potentially get two matches.
386            if index == to_usize(run_part.text_end) {
387                let i = if glyph_run.level.is_ltr() {
388                    run_part.glyph_range.end()
389                } else {
390                    run_part.glyph_range.start()
391                };
392                let pos = if i < glyph_run.glyphs.len() {
393                    glyph_run.glyphs[i].position
394                } else {
395                    // NOTE: for RTL we only hit this case if glyphs.len() == 0
396                    Vec2(glyph_run.caret, 0.0)
397                };
398
399                let pos = run_part.offset + pos;
400                push_result(pos, sf.ascent(), sf.descent(), glyph_run.level.number());
401                continue;
402            }
403
404            // else: index < to_usize(run_part.text_end)
405            let pos = 'b: loop {
406                if glyph_run.level.is_ltr() {
407                    for glyph in glyph_run.glyphs[run_part.glyph_range.to_std()].iter().rev() {
408                        if to_usize(glyph.index) <= index {
409                            break 'b glyph.position;
410                        }
411                    }
412                } else {
413                    for glyph in glyph_run.glyphs[run_part.glyph_range.to_std()].iter() {
414                        if to_usize(glyph.index) <= index {
415                            let mut pos = glyph.position;
416                            pos.0 += sf.h_advance(glyph.id);
417                            break 'b pos;
418                        }
419                    }
420                }
421                break 'a;
422            };
423
424            let pos = run_part.offset + pos;
425            push_result(pos, sf.ascent(), sf.descent(), glyph_run.level.number());
426            break;
427        }
428
429        MarkerPosIter { v, a, b }
430    }
431
432    /// Get the number of glyphs
433    ///
434    /// [Requires status][Self#status-of-preparation]: lines have been wrapped.
435    ///
436    /// This method is a simple memory-read.
437    #[inline]
438    #[cfg(feature = "num_glyphs")]
439    pub fn num_glyphs(&self) -> usize {
440        to_usize(self.num_glyphs)
441    }
442
443    /// Iterate over runs of positioned glyphs
444    ///
445    /// All glyphs are translated by the given `offset` (this is practically
446    /// free).
447    ///
448    /// An [`Effect`] sequence supports underline, strikethrough and custom
449    /// indexing (e.g. for a color palette). Pass `&[]` if effects are not
450    /// required. (The default effect is always [`Effect::default()`].)
451    ///
452    /// Runs are yielded in undefined order. The total number of
453    /// glyphs yielded will equal [`TextDisplay::num_glyphs`].
454    ///
455    /// [Requires status][Self#status-of-preparation]:
456    /// text is fully prepared for display.
457    pub fn runs<'a>(
458        &'a self,
459        offset: Vec2,
460        effects: &'a [Effect],
461    ) -> impl Iterator<Item = GlyphRun<'a>> + 'a {
462        self.wrapped_runs
463            .iter()
464            .filter(|part| !part.glyph_range.is_empty())
465            .map(move |part| GlyphRun {
466                run: &self.runs[to_usize(part.glyph_run)],
467                range: part.glyph_range,
468                offset: offset + part.offset,
469                effects,
470            })
471    }
472
473    /// Yield a sequence of rectangles to highlight a given text range
474    ///
475    /// [Requires status][Self#status-of-preparation]:
476    /// text is fully prepared for display.
477    ///
478    /// Calls `f(top_left, bottom_right)` for each highlighting rectangle.
479    pub fn highlight_range(&self, range: std::ops::Range<usize>, f: &mut dyn FnMut(Vec2, Vec2)) {
480        for line in &self.lines {
481            let line_range: std::ops::Range<usize> = line.text_range.into();
482            if line_range.end <= range.start {
483                continue;
484            } else if range.end <= line_range.start {
485                break;
486            } else if range.start <= line_range.start && line_range.end <= range.end {
487                let tl = Vec2(self.l_bound, line.top);
488                let br = Vec2(self.r_bound, line.bottom);
489                f(tl, br);
490            } else {
491                self.highlight_line(line.clone(), range.clone(), f);
492            }
493        }
494    }
495
496    /// Produce highlighting rectangles within a range of runs
497    ///
498    /// [Requires status][Self#status-of-preparation]:
499    /// text is fully prepared for display.
500    ///
501    /// Warning: runs are in logical order which does not correspond to display
502    /// order. As a result, the order of results (on a line) is not known.
503    fn highlight_line(
504        &self,
505        line: Line,
506        range: std::ops::Range<usize>,
507        f: &mut dyn FnMut(Vec2, Vec2),
508    ) {
509        let fonts = fonts::library();
510
511        let mut a;
512
513        let mut i = line.run_range.start();
514        let line_range_end = line.run_range.end();
515        'b: loop {
516            if i >= line_range_end {
517                return;
518            }
519            let run_part = &self.wrapped_runs[i];
520            if range.start >= to_usize(run_part.text_end) {
521                i += 1;
522                continue;
523            }
524
525            let glyph_run = &self.runs[to_usize(run_part.glyph_run)];
526
527            if glyph_run.level.is_ltr() {
528                for glyph in glyph_run.glyphs[run_part.glyph_range.to_std()].iter().rev() {
529                    if to_usize(glyph.index) <= range.start {
530                        a = glyph.position.0;
531                        break 'b;
532                    }
533                }
534                a = 0.0;
535            } else {
536                let sf = fonts
537                    .get_face(glyph_run.face_id)
538                    .scale_by_dpu(glyph_run.dpu);
539                for glyph in glyph_run.glyphs[run_part.glyph_range.to_std()].iter() {
540                    if to_usize(glyph.index) <= range.start {
541                        a = glyph.position.0;
542                        a += sf.h_advance(glyph.id);
543                        break 'b;
544                    }
545                }
546                a = glyph_run.caret;
547            }
548            break 'b;
549        }
550
551        let mut first = true;
552        'a: while i < line_range_end {
553            let run_part = &self.wrapped_runs[i];
554            let offset = run_part.offset.0;
555            let glyph_run = &self.runs[to_usize(run_part.glyph_run)];
556
557            if !first {
558                a = if glyph_run.level.is_ltr() {
559                    if run_part.glyph_range.start() < glyph_run.glyphs.len() {
560                        glyph_run.glyphs[run_part.glyph_range.start()].position.0
561                    } else {
562                        0.0
563                    }
564                } else {
565                    if run_part.glyph_range.end() < glyph_run.glyphs.len() {
566                        glyph_run.glyphs[run_part.glyph_range.end()].position.0
567                    } else {
568                        glyph_run.caret
569                    }
570                };
571            }
572            first = false;
573
574            if range.end >= to_usize(run_part.text_end) {
575                let b;
576                if glyph_run.level.is_ltr() {
577                    if run_part.glyph_range.end() < glyph_run.glyphs.len() {
578                        b = glyph_run.glyphs[run_part.glyph_range.end()].position.0;
579                    } else {
580                        b = glyph_run.caret;
581                    }
582                } else {
583                    let p = if run_part.glyph_range.start() < glyph_run.glyphs.len() {
584                        glyph_run.glyphs[run_part.glyph_range.start()].position.0
585                    } else {
586                        // NOTE: for RTL we only hit this case if glyphs.len() == 0
587                        glyph_run.caret
588                    };
589                    b = a;
590                    a = p;
591                };
592
593                f(Vec2(a + offset, line.top), Vec2(b + offset, line.bottom));
594                i += 1;
595                continue;
596            }
597
598            let sf = fonts
599                .get_face(glyph_run.face_id)
600                .scale_by_dpu(glyph_run.dpu);
601
602            let b;
603            'c: loop {
604                if glyph_run.level.is_ltr() {
605                    for glyph in glyph_run.glyphs[run_part.glyph_range.to_std()].iter().rev() {
606                        if to_usize(glyph.index) <= range.end {
607                            b = glyph.position.0;
608                            break 'c;
609                        }
610                    }
611                } else {
612                    for glyph in glyph_run.glyphs[run_part.glyph_range.to_std()].iter() {
613                        if to_usize(glyph.index) <= range.end {
614                            b = a;
615                            a = glyph.position.0 + sf.h_advance(glyph.id);
616                            break 'c;
617                        }
618                    }
619                }
620                break 'a;
621            }
622
623            f(Vec2(a + offset, line.top), Vec2(b + offset, line.bottom));
624            break;
625        }
626    }
627}