rat_text/
glyph.rs

1use crate::{Grapheme, TextPosition};
2use std::borrow::Cow;
3use std::fmt::Debug;
4use std::ops::Range;
5
6/// Data for rendering/mapping graphemes to screen coordinates.
7#[derive(Debug)]
8#[deprecated(since = "1.1.0", note = "discontinued api")]
9pub struct Glyph<'a> {
10    /// Display glyph.
11    glyph: Cow<'a, str>,
12    /// byte-range of the glyph in the given slice.
13    text_bytes: Range<usize>,
14    /// screen-position corrected by text_offset.
15    /// first visible column is at 0.
16    screen_pos: (u16, u16),
17    /// Display length for the glyph.
18    screen_width: u16,
19    /// text-position
20    pos: TextPosition,
21}
22
23#[allow(deprecated)]
24impl<'a> Glyph<'a> {
25    pub fn new(
26        glyph: Cow<'a, str>,
27        text_bytes: Range<usize>,
28        screen_pos: (u16, u16),
29        screen_width: u16,
30        pos: TextPosition,
31    ) -> Self {
32        Self {
33            glyph,
34            text_bytes,
35            screen_pos,
36            screen_width,
37            pos,
38        }
39    }
40
41    /// Get the glyph.
42    pub fn glyph(&'a self) -> &'a str {
43        self.glyph.as_ref()
44    }
45
46    /// Get the byte-range as absolute range into the complete text.
47    pub fn text_bytes(&self) -> Range<usize> {
48        self.text_bytes.clone()
49    }
50
51    /// Get the position of the glyph
52    pub fn pos(&self) -> TextPosition {
53        self.pos
54    }
55
56    /// Get the screen position of the glyph.
57    pub fn screen_pos(&self) -> (u16, u16) {
58        self.screen_pos
59    }
60
61    /// Display width of the glyph.
62    pub fn screen_width(&self) -> u16 {
63        self.screen_width
64    }
65}
66
67/// Iterates over the glyphs of a row-range.
68///
69/// Keeps track of the text-position and the display-position on screen.
70/// Does a conversion from graphemes to glyph-text and glyph-width.
71///
72/// This is used for rendering text, and for mapping text-positions
73/// to screen-positions and vice versa.
74#[derive(Debug)]
75pub(crate) struct GlyphIter<Iter> {
76    iter: Iter,
77
78    pos: TextPosition,
79
80    screen_offset: u16,
81    screen_width: u16,
82    screen_pos: (u16, u16),
83
84    tabs: u16,
85    show_ctrl: bool,
86    line_break: bool,
87}
88
89impl<'a, Iter> GlyphIter<Iter>
90where
91    Iter: Iterator<Item = Grapheme<'a>>,
92{
93    /// New iterator.
94    pub(crate) fn new(pos: TextPosition, iter: Iter) -> Self {
95        Self {
96            iter,
97            pos,
98            screen_offset: 0,
99            screen_width: u16::MAX,
100            screen_pos: Default::default(),
101            tabs: 8,
102            show_ctrl: false,
103            line_break: true,
104        }
105    }
106
107    /// Screen offset.
108    pub(crate) fn set_screen_offset(&mut self, offset: u16) {
109        self.screen_offset = offset;
110    }
111
112    /// Screen width.
113    pub(crate) fn set_screen_width(&mut self, width: u16) {
114        self.screen_width = width;
115    }
116
117    /// Tab width
118    pub(crate) fn set_tabs(&mut self, tabs: u16) {
119        self.tabs = tabs;
120    }
121
122    /// Handle line-breaks. If false everything is treated as one line.
123    pub(crate) fn set_line_break(&mut self, line_break: bool) {
124        self.line_break = line_break;
125    }
126
127    /// Show ASCII control codes.
128    pub(crate) fn set_show_ctrl(&mut self, show_ctrl: bool) {
129        self.show_ctrl = show_ctrl;
130    }
131}
132
133#[allow(deprecated)]
134impl<'a, Iter> Iterator for GlyphIter<Iter>
135where
136    Iter: Iterator<Item = Grapheme<'a>>,
137{
138    type Item = Glyph<'a>;
139
140    fn next(&mut self) -> Option<Self::Item> {
141        for grapheme in self.iter.by_ref() {
142            let (grapheme, grapheme_bytes) = grapheme.into_parts();
143
144            let glyph;
145            let len: u16;
146            let mut lbrk = false;
147
148            // todo: maybe add some ligature support.
149
150            match grapheme.as_ref() {
151                "\n" | "\r\n" if self.line_break => {
152                    lbrk = true;
153                    len = if self.show_ctrl { 1 } else { 0 };
154                    glyph = Cow::Borrowed(if self.show_ctrl { "\u{2424}" } else { "" });
155                }
156                "\n" | "\r\n" if !self.line_break => {
157                    lbrk = false;
158                    len = 1;
159                    glyph = Cow::Borrowed("\u{2424}");
160                }
161                "\t" => {
162                    len = self.tabs - (self.screen_pos.0 % self.tabs);
163                    glyph = Cow::Borrowed(if self.show_ctrl { "\u{2409}" } else { " " });
164                }
165                c if ("\x00".."\x20").contains(&c) => {
166                    static CCHAR: [&str; 32] = [
167                        "\u{2400}", "\u{2401}", "\u{2402}", "\u{2403}", "\u{2404}", "\u{2405}",
168                        "\u{2406}", "\u{2407}", "\u{2408}", "\u{2409}", "\u{240A}", "\u{240B}",
169                        "\u{240C}", "\u{240D}", "\u{240E}", "\u{240F}", "\u{2410}", "\u{2411}",
170                        "\u{2412}", "\u{2413}", "\u{2414}", "\u{2415}", "\u{2416}", "\u{2417}",
171                        "\u{2418}", "\u{2419}", "\u{241A}", "\u{241B}", "\u{241C}", "\u{241D}",
172                        "\u{241E}", "\u{241F}",
173                    ];
174                    let c0 = c.bytes().next().expect("byte");
175                    len = 1;
176                    glyph = Cow::Borrowed(if self.show_ctrl {
177                        CCHAR[c0 as usize]
178                    } else {
179                        "\u{FFFD}"
180                    });
181                }
182                c => {
183                    len = unicode_display_width::width(c) as u16;
184                    glyph = grapheme;
185                }
186            }
187
188            let pos = self.pos;
189            let screen_pos = self.screen_pos;
190
191            if lbrk {
192                self.screen_pos.0 = 0;
193                self.screen_pos.1 += 1;
194                self.pos.x = 0;
195                self.pos.y += 1;
196            } else {
197                self.screen_pos.0 += len;
198                self.pos.x += 1;
199            }
200
201            // clip left
202            if screen_pos.0 < self.screen_offset {
203                if screen_pos.0 + len > self.screen_offset {
204                    // don't show partial glyphs, but show the space they need.
205                    // avoids flickering when scrolling left/right.
206                    return Some(Glyph {
207                        glyph: Cow::Borrowed("\u{2203}"),
208                        text_bytes: grapheme_bytes,
209                        screen_width: screen_pos.0 + len - self.screen_offset,
210                        pos,
211                        screen_pos: (0, screen_pos.1),
212                    });
213                } else {
214                    // out left
215                }
216            } else if screen_pos.0 + len > self.screen_offset + self.screen_width {
217                if screen_pos.0 < self.screen_offset + self.screen_width {
218                    // don't show partial glyphs, but show the space they need.
219                    // avoids flickering when scrolling left/right.
220                    return Some(Glyph {
221                        glyph: Cow::Borrowed("\u{2203}"),
222                        text_bytes: grapheme_bytes,
223                        screen_width: screen_pos.0 + len - (self.screen_offset + self.screen_width),
224                        pos,
225                        screen_pos: (screen_pos.0 - self.screen_offset, screen_pos.1),
226                    });
227                } else {
228                    // out right
229                    if !self.line_break {
230                        break;
231                    }
232                }
233            } else {
234                return Some(Glyph {
235                    glyph,
236                    text_bytes: grapheme_bytes,
237                    screen_width: len,
238                    pos,
239                    screen_pos: (screen_pos.0 - self.screen_offset, screen_pos.1),
240                });
241            }
242        }
243
244        None
245    }
246}
247
248#[cfg(test)]
249mod test_glyph {
250    use crate::glyph::GlyphIter;
251    use crate::grapheme::RopeGraphemes;
252    use crate::TextPosition;
253    use ropey::Rope;
254
255    #[test]
256    fn test_glyph1() {
257        let s = Rope::from(
258            r#"0123456789
259abcdefghij
260jklöjklöjk
261uiopü+uiop"#,
262        );
263        let r = RopeGraphemes::new(0, s.byte_slice(..));
264        let mut glyphs = GlyphIter::new(TextPosition::new(0, 0), r);
265
266        let n = glyphs.next().unwrap();
267        assert_eq!(n.glyph(), "0");
268        assert_eq!(n.text_bytes(), 0..1);
269        assert_eq!(n.screen_pos(), (0, 0));
270        assert_eq!(n.pos(), TextPosition::new(0, 0));
271        assert_eq!(n.screen_width(), 1);
272
273        let n = glyphs.next().unwrap();
274        assert_eq!(n.glyph(), "1");
275        assert_eq!(n.text_bytes(), 1..2);
276        assert_eq!(n.screen_pos(), (1, 0));
277        assert_eq!(n.pos(), TextPosition::new(1, 0));
278        assert_eq!(n.screen_width(), 1);
279
280        let n = glyphs.next().unwrap();
281        assert_eq!(n.glyph(), "2");
282        assert_eq!(n.text_bytes(), 2..3);
283        assert_eq!(n.screen_pos(), (2, 0));
284        assert_eq!(n.pos(), TextPosition::new(2, 0));
285        assert_eq!(n.screen_width(), 1);
286
287        let n = glyphs.nth(7).unwrap();
288        assert_eq!(n.glyph(), "");
289        assert_eq!(n.text_bytes(), 10..11);
290        assert_eq!(n.screen_pos(), (10, 0));
291        assert_eq!(n.pos(), TextPosition::new(10, 0));
292        assert_eq!(n.screen_width(), 0);
293
294        let n = glyphs.next().unwrap();
295        assert_eq!(n.glyph(), "a");
296        assert_eq!(n.text_bytes(), 11..12);
297        assert_eq!(n.screen_pos(), (0, 1));
298        assert_eq!(n.pos(), TextPosition::new(0, 1));
299        assert_eq!(n.screen_width(), 1);
300    }
301
302    #[test]
303    fn test_glyph2() {
304        // screen offset
305        let s = Rope::from(
306            r#"0123456789
307abcdefghij
308jklöjklöjk
309uiopü+uiop"#,
310        );
311        let r = RopeGraphemes::new(0, s.byte_slice(..));
312        let mut glyphs = GlyphIter::new(TextPosition::new(0, 0), r);
313        glyphs.set_screen_offset(2);
314        glyphs.set_screen_width(100);
315
316        let n = glyphs.next().unwrap();
317        assert_eq!(n.glyph(), "2");
318        assert_eq!(n.text_bytes(), 2..3);
319        assert_eq!(n.screen_pos(), (0, 0));
320        assert_eq!(n.pos(), TextPosition::new(2, 0));
321        assert_eq!(n.screen_width(), 1);
322
323        let n = glyphs.next().unwrap();
324        assert_eq!(n.glyph(), "3");
325        assert_eq!(n.text_bytes(), 3..4);
326        assert_eq!(n.screen_pos(), (1, 0));
327        assert_eq!(n.pos(), TextPosition::new(3, 0));
328        assert_eq!(n.screen_width(), 1);
329
330        let n = glyphs.nth(6).unwrap();
331        assert_eq!(n.glyph(), "");
332        assert_eq!(n.text_bytes(), 10..11);
333        assert_eq!(n.screen_pos(), (8, 0));
334        assert_eq!(n.pos(), TextPosition::new(10, 0));
335        assert_eq!(n.screen_width(), 0);
336
337        let n = glyphs.next().unwrap();
338        assert_eq!(n.glyph(), "c");
339        assert_eq!(n.text_bytes(), 13..14);
340        assert_eq!(n.screen_pos(), (0, 1));
341        assert_eq!(n.pos(), TextPosition::new(2, 1));
342        assert_eq!(n.screen_width(), 1);
343    }
344
345    #[test]
346    fn test_glyph3() {
347        // screen offset + width
348        let s = Rope::from(
349            r#"0123456789
350abcdefghij
351jklöjklöjk
352uiopü+uiop"#,
353        );
354        let r = RopeGraphemes::new(0, s.byte_slice(..));
355        let mut glyphs = GlyphIter::new(TextPosition::new(0, 0), r);
356        glyphs.set_screen_offset(2);
357        glyphs.set_screen_width(6);
358
359        let n = glyphs.next().unwrap();
360        assert_eq!(n.glyph(), "2");
361        assert_eq!(n.text_bytes(), 2..3);
362        assert_eq!(n.screen_pos(), (0, 0));
363        assert_eq!(n.pos(), TextPosition::new(2, 0));
364        assert_eq!(n.screen_width(), 1);
365
366        let n = glyphs.next().unwrap();
367        assert_eq!(n.glyph(), "3");
368        assert_eq!(n.text_bytes(), 3..4);
369        assert_eq!(n.screen_pos(), (1, 0));
370        assert_eq!(n.pos(), TextPosition::new(3, 0));
371        assert_eq!(n.screen_width(), 1);
372
373        let n = glyphs.nth(2).unwrap();
374        assert_eq!(n.glyph(), "6");
375
376        let n = glyphs.next().unwrap();
377        assert_eq!(n.glyph(), "7");
378        assert_eq!(n.text_bytes(), 7..8);
379        assert_eq!(n.screen_pos(), (5, 0));
380        assert_eq!(n.pos(), TextPosition::new(7, 0));
381        assert_eq!(n.screen_width(), 1);
382
383        let n = glyphs.next().unwrap();
384        assert_eq!(n.glyph(), "c");
385        assert_eq!(n.text_bytes(), 13..14);
386        assert_eq!(n.screen_pos(), (0, 1));
387        assert_eq!(n.pos(), TextPosition::new(2, 1));
388        assert_eq!(n.screen_width(), 1);
389    }
390
391    #[test]
392    fn test_glyph4() {
393        // tabs
394        let s = Rope::from(
395            "012\t3456789
396abcdefghij
397jklöjklöjk
398uiopü+uiop",
399        );
400        let r = RopeGraphemes::new(0, s.byte_slice(..));
401        let mut glyphs = GlyphIter::new(TextPosition::new(0, 0), r);
402        glyphs.set_screen_offset(2);
403        glyphs.set_screen_width(100);
404
405        let n = glyphs.next().unwrap();
406        assert_eq!(n.glyph(), "2");
407        assert_eq!(n.text_bytes(), 2..3);
408        assert_eq!(n.screen_pos(), (0, 0));
409        assert_eq!(n.pos(), TextPosition::new(2, 0));
410        assert_eq!(n.screen_width(), 1);
411
412        let n = glyphs.next().unwrap();
413        assert_eq!(n.glyph(), " ");
414        assert_eq!(n.text_bytes(), 3..4);
415        assert_eq!(n.screen_pos(), (1, 0));
416        assert_eq!(n.pos(), TextPosition::new(3, 0));
417        assert_eq!(n.screen_width(), 5);
418
419        let n = glyphs.nth(7).unwrap();
420        assert_eq!(n.glyph(), "");
421        assert_eq!(n.text_bytes(), 11..12);
422        assert_eq!(n.screen_pos(), (13, 0));
423        assert_eq!(n.pos(), TextPosition::new(11, 0));
424        assert_eq!(n.screen_width(), 0);
425
426        let n = glyphs.next().unwrap();
427        assert_eq!(n.glyph(), "c");
428        assert_eq!(n.text_bytes(), 14..15);
429        assert_eq!(n.screen_pos(), (0, 1));
430        assert_eq!(n.pos(), TextPosition::new(2, 1));
431        assert_eq!(n.screen_width(), 1);
432    }
433
434    #[test]
435    fn test_glyph5() {
436        // clipping wide
437        let s = Rope::from(
438            "0\t12345678\t9
439abcdefghij
440jklöjklöjk
441uiopü+uiop",
442        );
443        let r = RopeGraphemes::new(0, s.byte_slice(..));
444        let mut glyphs = GlyphIter::new(TextPosition::new(0, 0), r);
445        glyphs.set_screen_offset(2);
446        glyphs.set_screen_width(20);
447
448        let n = glyphs.next().unwrap();
449        assert_eq!(n.glyph(), "∃");
450        assert_eq!(n.text_bytes(), 1..2);
451        assert_eq!(n.screen_pos(), (0, 0));
452        assert_eq!(n.pos(), TextPosition::new(1, 0));
453        assert_eq!(n.screen_width(), 6);
454
455        let n = glyphs.next().unwrap();
456        assert_eq!(n.glyph(), "1");
457        assert_eq!(n.text_bytes(), 2..3);
458        assert_eq!(n.screen_pos(), (6, 0));
459        assert_eq!(n.pos(), TextPosition::new(2, 0));
460        assert_eq!(n.screen_width(), 1);
461
462        let n = glyphs.nth(6).unwrap();
463        assert_eq!(n.glyph(), "8");
464
465        let n = glyphs.next().unwrap();
466        assert_eq!(n.glyph(), "∃");
467        assert_eq!(n.text_bytes(), 10..11);
468        assert_eq!(n.screen_pos(), (14, 0));
469        assert_eq!(n.pos(), TextPosition::new(10, 0));
470        assert_eq!(n.screen_width(), 2);
471
472        let n = glyphs.next().unwrap();
473        assert_eq!(n.glyph(), "c");
474        assert_eq!(n.text_bytes(), 15..16);
475        assert_eq!(n.screen_pos(), (0, 1));
476        assert_eq!(n.pos(), TextPosition::new(2, 1));
477        assert_eq!(n.screen_width(), 1);
478    }
479}