Coverage Report

Created: 2026-09-01 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/unicode-segmentation/src/grapheme.rs
Line
Count
Source
1
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2
// file at the top-level directory of this distribution and at
3
// http://rust-lang.org/COPYRIGHT.
4
//
5
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8
// option. This file may not be copied, modified, or distributed
9
// except according to those terms.
10
11
use crate::tables::grapheme::GraphemeCat;
12
use core::cmp;
13
14
/// External iterator for grapheme clusters and byte offsets.
15
///
16
/// This struct is created by the [`grapheme_indices`] method on the [`UnicodeSegmentation`]
17
/// trait. See its documentation for more.
18
///
19
/// [`grapheme_indices`]: trait.UnicodeSegmentation.html#tymethod.grapheme_indices
20
/// [`UnicodeSegmentation`]: trait.UnicodeSegmentation.html
21
#[derive(Debug, Clone)]
22
pub struct GraphemeIndices<'a> {
23
    start_offset: usize,
24
    iter: Graphemes<'a>,
25
}
26
27
impl<'a> GraphemeIndices<'a> {
28
    #[inline]
29
    /// View the underlying data (the part yet to be iterated) as a slice of the original string.
30
    ///
31
    /// ```rust
32
    /// # use unicode_segmentation::UnicodeSegmentation;
33
    /// let mut iter = "abc".grapheme_indices(true);
34
    /// assert_eq!(iter.as_str(), "abc");
35
    /// iter.next();
36
    /// assert_eq!(iter.as_str(), "bc");
37
    /// iter.next();
38
    /// iter.next();
39
    /// assert_eq!(iter.as_str(), "");
40
    /// ```
41
0
    pub fn as_str(&self) -> &'a str {
42
0
        self.iter.as_str()
43
0
    }
44
}
45
46
impl<'a> Iterator for GraphemeIndices<'a> {
47
    type Item = (usize, &'a str);
48
49
    #[inline]
50
0
    fn next(&mut self) -> Option<(usize, &'a str)> {
51
0
        self.iter
52
0
            .next()
53
0
            .map(|s| (s.as_ptr() as usize - self.start_offset, s))
54
0
    }
55
56
    #[inline]
57
0
    fn size_hint(&self) -> (usize, Option<usize>) {
58
0
        self.iter.size_hint()
59
0
    }
60
}
61
62
impl<'a> DoubleEndedIterator for GraphemeIndices<'a> {
63
    #[inline]
64
0
    fn next_back(&mut self) -> Option<(usize, &'a str)> {
65
0
        self.iter
66
0
            .next_back()
67
0
            .map(|s| (s.as_ptr() as usize - self.start_offset, s))
68
0
    }
69
}
70
71
/// External iterator for a string's
72
/// [grapheme clusters](http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries).
73
///
74
/// This struct is created by the [`graphemes`] method on the [`UnicodeSegmentation`] trait. See its
75
/// documentation for more.
76
///
77
/// [`graphemes`]: trait.UnicodeSegmentation.html#tymethod.graphemes
78
/// [`UnicodeSegmentation`]: trait.UnicodeSegmentation.html
79
#[derive(Clone, Debug)]
80
pub struct Graphemes<'a> {
81
    string: &'a str,
82
    cursor: GraphemeCursor,
83
    cursor_back: GraphemeCursor,
84
}
85
86
impl<'a> Graphemes<'a> {
87
    #[inline]
88
    /// View the underlying data (the part yet to be iterated) as a slice of the original string.
89
    ///
90
    /// ```rust
91
    /// # use unicode_segmentation::UnicodeSegmentation;
92
    /// let mut iter = "abc".graphemes(true);
93
    /// assert_eq!(iter.as_str(), "abc");
94
    /// iter.next();
95
    /// assert_eq!(iter.as_str(), "bc");
96
    /// iter.next();
97
    /// iter.next();
98
    /// assert_eq!(iter.as_str(), "");
99
    /// ```
100
0
    pub fn as_str(&self) -> &'a str {
101
0
        &self.string[self.cursor.cur_cursor()..self.cursor_back.cur_cursor()]
102
0
    }
103
}
104
105
impl<'a> Iterator for Graphemes<'a> {
106
    type Item = &'a str;
107
108
    #[inline]
109
9.98k
    fn size_hint(&self) -> (usize, Option<usize>) {
110
9.98k
        let slen = self.cursor_back.cur_cursor() - self.cursor.cur_cursor();
111
9.98k
        (cmp::min(slen, 1), Some(slen))
112
9.98k
    }
<unicode_segmentation::grapheme::Graphemes as core::iter::traits::iterator::Iterator>::size_hint
Line
Count
Source
109
9.98k
    fn size_hint(&self) -> (usize, Option<usize>) {
110
9.98k
        let slen = self.cursor_back.cur_cursor() - self.cursor.cur_cursor();
111
9.98k
        (cmp::min(slen, 1), Some(slen))
112
9.98k
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::Graphemes as core::iter::traits::iterator::Iterator>::size_hint
113
114
    #[inline]
115
36.5M
    fn next(&mut self) -> Option<&'a str> {
116
36.5M
        let start = self.cursor.cur_cursor();
117
36.5M
        if start == self.cursor_back.cur_cursor() {
118
3.86k
            return None;
119
36.5M
        }
120
36.5M
        let next = self.cursor.next_boundary(self.string, 0).unwrap().unwrap();
121
36.5M
        Some(&self.string[start..next])
122
36.5M
    }
<unicode_segmentation::grapheme::Graphemes as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
115
36.5M
    fn next(&mut self) -> Option<&'a str> {
116
36.5M
        let start = self.cursor.cur_cursor();
117
36.5M
        if start == self.cursor_back.cur_cursor() {
118
3.86k
            return None;
119
36.5M
        }
120
36.5M
        let next = self.cursor.next_boundary(self.string, 0).unwrap().unwrap();
121
36.5M
        Some(&self.string[start..next])
122
36.5M
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::Graphemes as core::iter::traits::iterator::Iterator>::next
123
}
124
125
impl<'a> DoubleEndedIterator for Graphemes<'a> {
126
    #[inline]
127
0
    fn next_back(&mut self) -> Option<&'a str> {
128
0
        let end = self.cursor_back.cur_cursor();
129
0
        if end == self.cursor.cur_cursor() {
130
0
            return None;
131
0
        }
132
0
        let prev = self
133
0
            .cursor_back
134
0
            .prev_boundary(self.string, 0)
135
0
            .unwrap()
136
0
            .unwrap();
137
0
        Some(&self.string[prev..end])
138
0
    }
139
}
140
141
#[inline]
142
3.86k
pub fn new_graphemes(s: &str, is_extended: bool) -> Graphemes<'_> {
143
3.86k
    let len = s.len();
144
3.86k
    Graphemes {
145
3.86k
        string: s,
146
3.86k
        cursor: GraphemeCursor::new(0, len, is_extended),
147
3.86k
        cursor_back: GraphemeCursor::new(len, len, is_extended),
148
3.86k
    }
149
3.86k
}
unicode_segmentation::grapheme::new_graphemes
Line
Count
Source
142
3.86k
pub fn new_graphemes(s: &str, is_extended: bool) -> Graphemes<'_> {
143
3.86k
    let len = s.len();
144
3.86k
    Graphemes {
145
3.86k
        string: s,
146
3.86k
        cursor: GraphemeCursor::new(0, len, is_extended),
147
3.86k
        cursor_back: GraphemeCursor::new(len, len, is_extended),
148
3.86k
    }
149
3.86k
}
Unexecuted instantiation: unicode_segmentation::grapheme::new_graphemes
150
151
#[inline]
152
0
pub fn new_grapheme_indices(s: &str, is_extended: bool) -> GraphemeIndices<'_> {
153
0
    GraphemeIndices {
154
0
        start_offset: s.as_ptr() as usize,
155
0
        iter: new_graphemes(s, is_extended),
156
0
    }
157
0
}
158
159
/// maybe unify with PairResult?
160
/// An enum describing information about a potential boundary.
161
#[derive(PartialEq, Eq, Clone, Debug)]
162
enum GraphemeState {
163
    /// No information is known.
164
    Unknown,
165
    /// It is known to not be a boundary.
166
    NotBreak,
167
    /// It is known to be a boundary.
168
    Break,
169
    /// The codepoint after it has Indic_Conjunct_Break=Consonant,
170
    /// so there is a break before so a boundary if it is preceded by another
171
    /// InCB=Consonant follwoed by a sequence consisting of one or more InCB=Linker
172
    /// and zero or more InCB = Extend (in any order).
173
    InCbConsonant,
174
    /// The codepoint after is a Regional Indicator Symbol, so a boundary iff
175
    /// it is preceded by an even number of RIS codepoints. (GB12, GB13)
176
    Regional,
177
    /// The codepoint after is Extended_Pictographic,
178
    /// so whether it's a boundary depends on pre-context according to GB11.
179
    Emoji {
180
        /// Whether the ZWJ char has been seen already an only a "\p{Extended_Pictographic} Extend*"
181
        /// part of GB11 has to be checked
182
        seen_zwj: bool,
183
    },
184
}
185
186
/// Cursor-based segmenter for grapheme clusters.
187
///
188
/// This allows working with ropes and other datastructures where the string is not contiguous or
189
/// fully known at initialization time.
190
#[derive(Clone, Debug)]
191
pub struct GraphemeCursor {
192
    /// Current cursor position.
193
    offset: usize,
194
    /// Total length of the string.
195
    len: usize,
196
    /// A config flag indicating whether this cursor computes legacy or extended
197
    /// grapheme cluster boundaries (enables GB9a and GB9b if set).
198
    is_extended: bool,
199
    /// Information about the potential boundary at `offset`
200
    state: GraphemeState,
201
    /// Category of codepoint immediately preceding cursor, if known.
202
    cat_before: Option<GraphemeCat>,
203
    /// Category of codepoint immediately after cursor, if known.
204
    cat_after: Option<GraphemeCat>,
205
    /// If set, at least one more codepoint immediately preceding this offset
206
    /// is needed to resolve whether there's a boundary at `offset`.
207
    pre_context_offset: Option<usize>,
208
    /// The number of `InCB=Linker` codepoints preceding `offset`
209
    /// (potentially intermingled with `InCB=Extend`).
210
    incb_linker_count: Option<usize>,
211
    /// The number of RIS codepoints preceding `offset`. If `pre_context_offset`
212
    /// is set, then counts the number of RIS between that and `offset`, otherwise
213
    /// is an accurate count relative to the string.
214
    ris_count: Option<usize>,
215
    /// Set if a call to `prev_boundary` or `next_boundary` was suspended due
216
    /// to needing more input.
217
    resuming: bool,
218
    /// Cached grapheme category and associated scalar value range.
219
    grapheme_cat_cache: (u32, u32, GraphemeCat),
220
}
221
222
/// An error return indicating that not enough content was available in the
223
/// provided chunk to satisfy the query, and that more content must be provided.
224
#[derive(PartialEq, Eq, Debug)]
225
pub enum GraphemeIncomplete {
226
    /// More pre-context is needed. The caller should call `provide_context`
227
    /// with a chunk ending at the offset given, then retry the query. This
228
    /// will only be returned if the `chunk_start` parameter is nonzero.
229
    PreContext(usize),
230
231
    /// When requesting `prev_boundary`, the cursor is moving past the beginning
232
    /// of the current chunk, so the chunk before that is requested. This will
233
    /// only be returned if the `chunk_start` parameter is nonzero.
234
    PrevChunk,
235
236
    /// When requesting `next_boundary`, the cursor is moving past the end of the
237
    /// current chunk, so the chunk after that is requested. This will only be
238
    /// returned if the chunk ends before the `len` parameter provided on
239
    /// creation of the cursor.
240
    NextChunk, // requesting chunk following the one given
241
242
    /// An error returned when the chunk given does not contain the cursor position.
243
    InvalidOffset,
244
}
245
246
// An enum describing the result from lookup of a pair of categories.
247
#[derive(PartialEq, Eq)]
248
enum PairResult {
249
    /// definitely not a break
250
    NotBreak,
251
    /// definitely a break
252
    Break,
253
    /// a break iff not in extended mode
254
    Extended,
255
    /// a break unless in extended mode and preceded by
256
    /// a sequence of 0 or more InCB=Extend and one or more
257
    /// InCB = Linker (in any order),
258
    /// preceded by another InCB=Consonant
259
    InCbConsonant,
260
    /// a break if preceded by an even number of RIS
261
    Regional,
262
    /// a break if preceded by emoji base and (Extend)*
263
    Emoji,
264
}
265
266
#[inline]
267
36.6M
fn check_pair(before: GraphemeCat, after: GraphemeCat) -> PairResult {
268
    use self::PairResult::*;
269
    use crate::tables::grapheme::GraphemeCat::*;
270
36.6M
    match (before, after) {
271
8.06k
        (GC_CR, GC_LF) => NotBreak,                                 // GB3
272
9.65M
        (GC_Control | GC_CR | GC_LF, _) => Break,                   // GB4
273
305k
        (_, GC_Control | GC_CR | GC_LF) => Break,                   // GB5
274
1.19k
        (GC_L, GC_L | GC_V | GC_LV | GC_LVT) => NotBreak,           // GB6
275
500
        (GC_LV | GC_V, GC_V | GC_T) => NotBreak,                    // GB7
276
480
        (GC_LVT | GC_T, GC_T) => NotBreak,                          // GB8
277
49.6k
        (_, GC_Extend | GC_ZWJ) => NotBreak,                        // GB9
278
847
        (_, GC_SpacingMark) => Extended,                            // GB9a
279
862
        (GC_Prepend, _) => Extended,                                // GB9b
280
10.4k
        (_, GC_InCB_Consonant) => InCbConsonant,                    // GB9c
281
11.4k
        (GC_ZWJ, GC_Extended_Pictographic) => Emoji,                // GB11
282
1.95k
        (GC_Regional_Indicator, GC_Regional_Indicator) => Regional, // GB12, GB13
283
26.5M
        (_, _) => Break,                                            // GB999
284
    }
285
36.6M
}
unicode_segmentation::grapheme::check_pair
Line
Count
Source
267
36.6M
fn check_pair(before: GraphemeCat, after: GraphemeCat) -> PairResult {
268
    use self::PairResult::*;
269
    use crate::tables::grapheme::GraphemeCat::*;
270
36.6M
    match (before, after) {
271
8.06k
        (GC_CR, GC_LF) => NotBreak,                                 // GB3
272
9.65M
        (GC_Control | GC_CR | GC_LF, _) => Break,                   // GB4
273
305k
        (_, GC_Control | GC_CR | GC_LF) => Break,                   // GB5
274
1.19k
        (GC_L, GC_L | GC_V | GC_LV | GC_LVT) => NotBreak,           // GB6
275
500
        (GC_LV | GC_V, GC_V | GC_T) => NotBreak,                    // GB7
276
480
        (GC_LVT | GC_T, GC_T) => NotBreak,                          // GB8
277
49.6k
        (_, GC_Extend | GC_ZWJ) => NotBreak,                        // GB9
278
847
        (_, GC_SpacingMark) => Extended,                            // GB9a
279
862
        (GC_Prepend, _) => Extended,                                // GB9b
280
10.4k
        (_, GC_InCB_Consonant) => InCbConsonant,                    // GB9c
281
11.4k
        (GC_ZWJ, GC_Extended_Pictographic) => Emoji,                // GB11
282
1.95k
        (GC_Regional_Indicator, GC_Regional_Indicator) => Regional, // GB12, GB13
283
26.5M
        (_, _) => Break,                                            // GB999
284
    }
285
36.6M
}
Unexecuted instantiation: unicode_segmentation::grapheme::check_pair
286
287
/// Whether `ch`, whose grapheme category is `cat`, has `Indic_Conjunct_Break=Extend`.
288
///
289
/// `InCB=Extend` is defined as
290
/// `[\p{gcb=Extend} \p{gcb=ZWJ}] - \p{InCB=Linker} - \p{InCB=Consonant} - U+200C`,
291
/// and no `InCB=Consonant` is `gcb=Extend` or `gcb=ZWJ`,
292
/// so the grapheme category the caller already has, plus two equality tests, decides it.
293
///
294
/// That saves a binary search over a range table of its own for every codepoint the cursor walks over.
295
///
296
/// `scripts/unicode.py` checks the derivation against the UCD when it regenerates `src/tables.rs`,
297
/// so a future Unicode version cannot silently invalidate it.
298
#[inline]
299
12.3k
fn is_incb_extend(cat: GraphemeCat, ch: char) -> bool {
300
    // ZWNJ is `gcb=Extend` but `InCB=None`.
301
12.3k
    may_be_incb(cat) && ch != '\u{200c}' && !crate::tables::is_incb_linker(ch)
302
12.3k
}
unicode_segmentation::grapheme::is_incb_extend
Line
Count
Source
299
12.3k
fn is_incb_extend(cat: GraphemeCat, ch: char) -> bool {
300
    // ZWNJ is `gcb=Extend` but `InCB=None`.
301
12.3k
    may_be_incb(cat) && ch != '\u{200c}' && !crate::tables::is_incb_linker(ch)
302
12.3k
}
Unexecuted instantiation: unicode_segmentation::grapheme::is_incb_extend
303
304
/// Both `InCB=Linker` and `InCB=Extend` are subsets of `gcb=Extend` and `gcb=ZWJ`,
305
/// so any other category rules out both roles without inspecting the codepoint.
306
#[inline]
307
36.6M
fn may_be_incb(cat: GraphemeCat) -> bool {
308
36.6M
    matches!(cat, GraphemeCat::GC_Extend | GraphemeCat::GC_ZWJ)
309
36.6M
}
unicode_segmentation::grapheme::may_be_incb
Line
Count
Source
307
36.6M
fn may_be_incb(cat: GraphemeCat) -> bool {
308
36.6M
    matches!(cat, GraphemeCat::GC_Extend | GraphemeCat::GC_ZWJ)
309
36.6M
}
Unexecuted instantiation: unicode_segmentation::grapheme::may_be_incb
310
311
impl GraphemeCursor {
312
    /// Create a new cursor. The string and initial offset are given at creation
313
    /// time, but the contents of the string are not. The `is_extended` parameter
314
    /// controls whether extended grapheme clusters are selected.
315
    ///
316
    /// The `offset` parameter must be on a codepoint boundary.
317
    ///
318
    /// ```rust
319
    /// # use unicode_segmentation::GraphemeCursor;
320
    /// let s = "हिन्दी";
321
    /// let mut legacy = GraphemeCursor::new(0, s.len(), false);
322
    /// assert_eq!(legacy.next_boundary(s, 0), Ok(Some("ह".len())));
323
    /// let mut extended = GraphemeCursor::new(0, s.len(), true);
324
    /// assert_eq!(extended.next_boundary(s, 0), Ok(Some("हि".len())));
325
    /// ```
326
7.73k
    pub fn new(offset: usize, len: usize, is_extended: bool) -> GraphemeCursor {
327
7.73k
        let state = if offset == 0 || offset == len {
328
7.73k
            GraphemeState::Break
329
        } else {
330
0
            GraphemeState::Unknown
331
        };
332
7.73k
        GraphemeCursor {
333
7.73k
            offset,
334
7.73k
            len,
335
7.73k
            state,
336
7.73k
            is_extended,
337
7.73k
            cat_before: None,
338
7.73k
            cat_after: None,
339
7.73k
            pre_context_offset: None,
340
7.73k
            incb_linker_count: None,
341
7.73k
            ris_count: None,
342
7.73k
            resuming: false,
343
7.73k
            grapheme_cat_cache: (0, 0, GraphemeCat::GC_Control),
344
7.73k
        }
345
7.73k
    }
346
347
36.6M
    fn grapheme_category(&mut self, ch: char) -> GraphemeCat {
348
        use crate::tables::grapheme as gr;
349
        use crate::tables::grapheme::GraphemeCat::*;
350
351
36.6M
        if ch <= '\u{7e}' {
352
            // Special-case optimization for ascii, except U+007F.  This
353
            // improves performance even for many primarily non-ascii texts,
354
            // due to use of punctuation and white space characters from the
355
            // ascii range.
356
35.9M
            if ch >= '\u{20}' {
357
26.2M
                GC_Any
358
9.64M
            } else if ch == '\n' {
359
99.0k
                GC_LF
360
9.54M
            } else if ch == '\r' {
361
2.26M
                GC_CR
362
            } else {
363
7.27M
                GC_Control
364
            }
365
        } else {
366
            // If this char isn't within the cached range, update the cache to the
367
            // range that includes it.
368
771k
            if (ch as u32) < self.grapheme_cat_cache.0 || (ch as u32) > self.grapheme_cat_cache.1 {
369
116k
                self.grapheme_cat_cache = gr::grapheme_category(ch);
370
654k
            }
371
771k
            self.grapheme_cat_cache.2
372
        }
373
36.6M
    }
374
375
    // Not sure I'm gonna keep this, the advantage over new() seems thin.
376
377
    /// Set the cursor to a new location in the same string.
378
    ///
379
    /// ```rust
380
    /// # use unicode_segmentation::GraphemeCursor;
381
    /// let s = "abcd";
382
    /// let mut cursor = GraphemeCursor::new(0, s.len(), false);
383
    /// assert_eq!(cursor.cur_cursor(), 0);
384
    /// cursor.set_cursor(2);
385
    /// assert_eq!(cursor.cur_cursor(), 2);
386
    /// ```
387
0
    pub fn set_cursor(&mut self, offset: usize) {
388
0
        if offset != self.offset {
389
0
            self.offset = offset;
390
0
            self.state = if offset == 0 || offset == self.len {
391
0
                GraphemeState::Break
392
            } else {
393
0
                GraphemeState::Unknown
394
            };
395
            // reset state derived from text around cursor
396
0
            self.cat_before = None;
397
0
            self.cat_after = None;
398
0
            self.incb_linker_count = None;
399
0
            self.ris_count = None;
400
0
        }
401
0
    }
402
403
    #[inline]
404
    /// The current offset of the cursor. Equal to the last value provided to
405
    /// `new()` or `set_cursor()`, or returned from `next_boundary()` or
406
    /// `prev_boundary()`.
407
    ///
408
    /// ```rust
409
    /// # use unicode_segmentation::GraphemeCursor;
410
    /// // Two flags (🇷🇸🇮🇴), each flag is two RIS codepoints, each RIS is 4 bytes.
411
    /// let flags = "\u{1F1F7}\u{1F1F8}\u{1F1EE}\u{1F1F4}";
412
    /// let mut cursor = GraphemeCursor::new(4, flags.len(), false);
413
    /// assert_eq!(cursor.cur_cursor(), 4);
414
    /// assert_eq!(cursor.next_boundary(flags, 0), Ok(Some(8)));
415
    /// assert_eq!(cursor.cur_cursor(), 8);
416
    /// ```
417
73.1M
    pub fn cur_cursor(&self) -> usize {
418
73.1M
        self.offset
419
73.1M
    }
<unicode_segmentation::grapheme::GraphemeCursor>::cur_cursor
Line
Count
Source
417
73.1M
    pub fn cur_cursor(&self) -> usize {
418
73.1M
        self.offset
419
73.1M
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::cur_cursor
420
421
    /// Provide additional pre-context when it is needed to decide a boundary.
422
    /// The end of the chunk must coincide with the value given in the
423
    /// `GraphemeIncomplete::PreContext` request.
424
    ///
425
    /// ```rust
426
    /// # use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
427
    /// let flags = "\u{1F1F7}\u{1F1F8}\u{1F1EE}\u{1F1F4}";
428
    /// let mut cursor = GraphemeCursor::new(8, flags.len(), false);
429
    /// // Not enough pre-context to decide if there's a boundary between the two flags.
430
    /// assert_eq!(cursor.is_boundary(&flags[8..], 8), Err(GraphemeIncomplete::PreContext(8)));
431
    /// // Provide one more Regional Indicator Symbol of pre-context
432
    /// cursor.provide_context(&flags[4..8], 4);
433
    /// // Still not enough context to decide.
434
    /// assert_eq!(cursor.is_boundary(&flags[8..], 8), Err(GraphemeIncomplete::PreContext(4)));
435
    /// // Provide additional requested context.
436
    /// cursor.provide_context(&flags[0..4], 0);
437
    /// // That's enough to decide (it always is when context goes to the start of the string)
438
    /// assert_eq!(cursor.is_boundary(&flags[8..], 8), Ok(true));
439
    /// ```
440
0
    pub fn provide_context(&mut self, chunk: &str, chunk_start: usize) {
441
        use crate::tables::grapheme as gr;
442
0
        assert!(chunk_start.saturating_add(chunk.len()) == self.pre_context_offset.unwrap());
443
0
        self.pre_context_offset = None;
444
0
        if self.is_extended && chunk_start + chunk.len() == self.offset {
445
0
            let ch = chunk.chars().next_back().unwrap();
446
0
            if self.grapheme_category(ch) == gr::GC_Prepend {
447
0
                self.decide(false); // GB9b
448
0
                return;
449
0
            }
450
0
        }
451
0
        match self.state {
452
0
            GraphemeState::InCbConsonant => self.handle_incb_consonant(chunk, chunk_start),
453
0
            GraphemeState::Regional => self.handle_regional(chunk, chunk_start),
454
0
            GraphemeState::Emoji { seen_zwj } => self.handle_emoji(chunk, chunk_start, seen_zwj),
455
            _ => {
456
0
                if self.cat_before.is_none() && self.offset == chunk.len() + chunk_start {
457
0
                    let ch = chunk.chars().next_back().unwrap();
458
0
                    self.cat_before = Some(self.grapheme_category(ch));
459
0
                }
460
            }
461
        }
462
0
    }
463
464
    #[inline]
465
36.6M
    fn decide(&mut self, is_break: bool) {
466
36.6M
        self.state = if is_break {
467
36.5M
            GraphemeState::Break
468
        } else {
469
70.0k
            GraphemeState::NotBreak
470
        };
471
36.6M
    }
<unicode_segmentation::grapheme::GraphemeCursor>::decide
Line
Count
Source
465
36.6M
    fn decide(&mut self, is_break: bool) {
466
36.6M
        self.state = if is_break {
467
36.5M
            GraphemeState::Break
468
        } else {
469
70.0k
            GraphemeState::NotBreak
470
        };
471
36.6M
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::decide
472
473
    #[inline]
474
36.6M
    fn decision(&mut self, is_break: bool) -> Result<bool, GraphemeIncomplete> {
475
36.6M
        self.decide(is_break);
476
36.6M
        Ok(is_break)
477
36.6M
    }
<unicode_segmentation::grapheme::GraphemeCursor>::decision
Line
Count
Source
474
36.6M
    fn decision(&mut self, is_break: bool) -> Result<bool, GraphemeIncomplete> {
475
36.6M
        self.decide(is_break);
476
36.6M
        Ok(is_break)
477
36.6M
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::decision
478
479
    #[inline]
480
21.9k
    fn is_boundary_result(&self) -> Result<bool, GraphemeIncomplete> {
481
21.9k
        if self.state == GraphemeState::Break {
482
14.7k
            Ok(true)
483
7.14k
        } else if self.state == GraphemeState::NotBreak {
484
7.14k
            Ok(false)
485
0
        } else if let Some(pre_context_offset) = self.pre_context_offset {
486
0
            Err(GraphemeIncomplete::PreContext(pre_context_offset))
487
        } else {
488
0
            unreachable!("inconsistent state");
489
        }
490
21.9k
    }
<unicode_segmentation::grapheme::GraphemeCursor>::is_boundary_result
Line
Count
Source
480
21.9k
    fn is_boundary_result(&self) -> Result<bool, GraphemeIncomplete> {
481
21.9k
        if self.state == GraphemeState::Break {
482
14.7k
            Ok(true)
483
7.14k
        } else if self.state == GraphemeState::NotBreak {
484
7.14k
            Ok(false)
485
0
        } else if let Some(pre_context_offset) = self.pre_context_offset {
486
0
            Err(GraphemeIncomplete::PreContext(pre_context_offset))
487
        } else {
488
0
            unreachable!("inconsistent state");
489
        }
490
21.9k
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::is_boundary_result
491
492
    /// For handling rule GB9c:
493
    ///
494
    /// There's an `InCB=Consonant` after this, and we need to look back
495
    /// to verify whether there should be a break.
496
    ///
497
    /// Seek backward to find an `InCB=Linker` preceded by an `InCB=Consonsnt`
498
    /// (potentially separated by some number of `InCB=Linker` or `InCB=Extend`).
499
    /// If we find the consonant in question, then there's no break; if we find a consonant
500
    /// with no linker, or a non-linker non-extend non-consonant, or the start of text, there's a break;
501
    /// otherwise we need more context
502
    #[inline]
503
10.4k
    fn handle_incb_consonant(&mut self, chunk: &str, chunk_start: usize) {
504
        use crate::tables::{self, grapheme as gr};
505
506
        // GB9c only applies to extended grapheme clusters
507
10.4k
        if !self.is_extended {
508
0
            self.decide(true);
509
0
            return;
510
10.4k
        }
511
512
10.4k
        let mut incb_linker_count = self.incb_linker_count.unwrap_or(0);
513
514
35.9k
        for ch in chunk.chars().rev() {
515
35.9k
            if tables::is_incb_linker(ch) {
516
23.6k
                // We found an InCB linker
517
23.6k
                incb_linker_count += 1;
518
23.6k
                self.incb_linker_count = Some(incb_linker_count);
519
23.6k
            } else if is_incb_extend(self.grapheme_category(ch), ch) {
520
2.25k
                // We ignore InCB extends, continue
521
2.25k
            } else {
522
                // Prev character is neither linker nor extend, break suppressed iff it's InCB=Consonant
523
10.0k
                let result = !(self.incb_linker_count.unwrap_or(0) > 0
524
2.91k
                    && self.grapheme_category(ch) == gr::GC_InCB_Consonant);
525
10.0k
                self.decide(result);
526
10.0k
                return;
527
            }
528
        }
529
530
380
        if chunk_start == 0 {
531
380
            // Start of text and we still haven't found a consonant, so break
532
380
            self.decide(true);
533
380
        } else {
534
0
            // We need more context
535
0
            self.pre_context_offset = Some(chunk_start);
536
0
            self.state = GraphemeState::InCbConsonant;
537
0
        }
538
10.4k
    }
<unicode_segmentation::grapheme::GraphemeCursor>::handle_incb_consonant
Line
Count
Source
503
10.4k
    fn handle_incb_consonant(&mut self, chunk: &str, chunk_start: usize) {
504
        use crate::tables::{self, grapheme as gr};
505
506
        // GB9c only applies to extended grapheme clusters
507
10.4k
        if !self.is_extended {
508
0
            self.decide(true);
509
0
            return;
510
10.4k
        }
511
512
10.4k
        let mut incb_linker_count = self.incb_linker_count.unwrap_or(0);
513
514
35.9k
        for ch in chunk.chars().rev() {
515
35.9k
            if tables::is_incb_linker(ch) {
516
23.6k
                // We found an InCB linker
517
23.6k
                incb_linker_count += 1;
518
23.6k
                self.incb_linker_count = Some(incb_linker_count);
519
23.6k
            } else if is_incb_extend(self.grapheme_category(ch), ch) {
520
2.25k
                // We ignore InCB extends, continue
521
2.25k
            } else {
522
                // Prev character is neither linker nor extend, break suppressed iff it's InCB=Consonant
523
10.0k
                let result = !(self.incb_linker_count.unwrap_or(0) > 0
524
2.91k
                    && self.grapheme_category(ch) == gr::GC_InCB_Consonant);
525
10.0k
                self.decide(result);
526
10.0k
                return;
527
            }
528
        }
529
530
380
        if chunk_start == 0 {
531
380
            // Start of text and we still haven't found a consonant, so break
532
380
            self.decide(true);
533
380
        } else {
534
0
            // We need more context
535
0
            self.pre_context_offset = Some(chunk_start);
536
0
            self.state = GraphemeState::InCbConsonant;
537
0
        }
538
10.4k
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::handle_incb_consonant
539
540
    #[inline]
541
26
    fn handle_regional(&mut self, chunk: &str, chunk_start: usize) {
542
        use crate::tables::grapheme as gr;
543
26
        let mut ris_count = self.ris_count.unwrap_or(0);
544
26
        for ch in chunk.chars().rev() {
545
26
            if self.grapheme_category(ch) != gr::GC_Regional_Indicator {
546
0
                self.ris_count = Some(ris_count);
547
0
                self.decide(ris_count % 2 == 0);
548
0
                return;
549
26
            }
550
26
            ris_count += 1;
551
        }
552
26
        self.ris_count = Some(ris_count);
553
26
        if chunk_start == 0 {
554
26
            self.decide(ris_count % 2 == 0);
555
26
        } else {
556
0
            self.pre_context_offset = Some(chunk_start);
557
0
            self.state = GraphemeState::Regional;
558
0
        }
559
26
    }
<unicode_segmentation::grapheme::GraphemeCursor>::handle_regional
Line
Count
Source
541
26
    fn handle_regional(&mut self, chunk: &str, chunk_start: usize) {
542
        use crate::tables::grapheme as gr;
543
26
        let mut ris_count = self.ris_count.unwrap_or(0);
544
26
        for ch in chunk.chars().rev() {
545
26
            if self.grapheme_category(ch) != gr::GC_Regional_Indicator {
546
0
                self.ris_count = Some(ris_count);
547
0
                self.decide(ris_count % 2 == 0);
548
0
                return;
549
26
            }
550
26
            ris_count += 1;
551
        }
552
26
        self.ris_count = Some(ris_count);
553
26
        if chunk_start == 0 {
554
26
            self.decide(ris_count % 2 == 0);
555
26
        } else {
556
0
            self.pre_context_offset = Some(chunk_start);
557
0
            self.state = GraphemeState::Regional;
558
0
        }
559
26
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::handle_regional
560
561
    #[inline]
562
11.4k
    fn handle_emoji(&mut self, chunk: &str, chunk_start: usize, mut seen_zwj: bool) {
563
        // \p{Extended_Pictographic} Extend* ZWJ  ×  \p{Extended_Pictographic}
564
        use crate::tables::grapheme as gr;
565
11.4k
        let mut iter = chunk.chars().rev();
566
11.4k
        if !seen_zwj {
567
11.4k
            if let Some(ch) = iter.next() {
568
11.4k
                if self.grapheme_category(ch) != gr::GC_ZWJ {
569
0
                    self.decide(true);
570
0
                    return;
571
11.4k
                } else {
572
11.4k
                    seen_zwj = true;
573
11.4k
                }
574
0
            }
575
0
        }
576
13.4k
        for ch in iter {
577
13.3k
            match self.grapheme_category(ch) {
578
1.99k
                gr::GC_Extend => (),
579
                gr::GC_Extended_Pictographic => {
580
5.59k
                    self.decide(false);
581
5.59k
                    return;
582
                }
583
                _ => {
584
5.77k
                    self.decide(true);
585
5.77k
                    return;
586
                }
587
            }
588
        }
589
62
        if chunk_start == 0 {
590
62
            self.decide(true);
591
62
        } else {
592
0
            self.pre_context_offset = Some(chunk_start);
593
0
            self.state = GraphemeState::Emoji { seen_zwj };
594
0
        }
595
11.4k
    }
<unicode_segmentation::grapheme::GraphemeCursor>::handle_emoji
Line
Count
Source
562
11.4k
    fn handle_emoji(&mut self, chunk: &str, chunk_start: usize, mut seen_zwj: bool) {
563
        // \p{Extended_Pictographic} Extend* ZWJ  ×  \p{Extended_Pictographic}
564
        use crate::tables::grapheme as gr;
565
11.4k
        let mut iter = chunk.chars().rev();
566
11.4k
        if !seen_zwj {
567
11.4k
            if let Some(ch) = iter.next() {
568
11.4k
                if self.grapheme_category(ch) != gr::GC_ZWJ {
569
0
                    self.decide(true);
570
0
                    return;
571
11.4k
                } else {
572
11.4k
                    seen_zwj = true;
573
11.4k
                }
574
0
            }
575
0
        }
576
13.4k
        for ch in iter {
577
13.3k
            match self.grapheme_category(ch) {
578
1.99k
                gr::GC_Extend => (),
579
                gr::GC_Extended_Pictographic => {
580
5.59k
                    self.decide(false);
581
5.59k
                    return;
582
                }
583
                _ => {
584
5.77k
                    self.decide(true);
585
5.77k
                    return;
586
                }
587
            }
588
        }
589
62
        if chunk_start == 0 {
590
62
            self.decide(true);
591
62
        } else {
592
0
            self.pre_context_offset = Some(chunk_start);
593
0
            self.state = GraphemeState::Emoji { seen_zwj };
594
0
        }
595
11.4k
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::handle_emoji
596
597
    #[inline]
598
    /// Determine whether the current cursor location is a grapheme cluster boundary.
599
    /// Only a part of the string need be supplied. If `chunk_start` is nonzero or
600
    /// the length of `chunk` is not equal to `len` on creation, then this method
601
    /// may return `GraphemeIncomplete::PreContext`. The caller should then
602
    /// call `provide_context` with the requested chunk, then retry calling this
603
    /// method.
604
    ///
605
    /// For partial chunks, if the cursor is not at the beginning or end of the
606
    /// string, the chunk should contain at least the codepoint following the cursor.
607
    /// If the string is nonempty, the chunk must be nonempty.
608
    ///
609
    /// All calls should have consistent chunk contents (ie, if a chunk provides
610
    /// content for a given slice, all further chunks covering that slice must have
611
    /// the same content for it).
612
    ///
613
    /// ```rust
614
    /// # use unicode_segmentation::GraphemeCursor;
615
    /// let flags = "\u{1F1F7}\u{1F1F8}\u{1F1EE}\u{1F1F4}";
616
    /// let mut cursor = GraphemeCursor::new(8, flags.len(), false);
617
    /// assert_eq!(cursor.is_boundary(flags, 0), Ok(true));
618
    /// cursor.set_cursor(12);
619
    /// assert_eq!(cursor.is_boundary(flags, 0), Ok(false));
620
    /// ```
621
36.6M
    pub fn is_boundary(
622
36.6M
        &mut self,
623
36.6M
        chunk: &str,
624
36.6M
        chunk_start: usize,
625
36.6M
    ) -> Result<bool, GraphemeIncomplete> {
626
        use crate::tables::grapheme as gr;
627
36.6M
        if self.state == GraphemeState::Break {
628
3.86k
            return Ok(true);
629
36.6M
        }
630
36.6M
        if self.state == GraphemeState::NotBreak {
631
0
            return Ok(false);
632
36.6M
        }
633
36.6M
        if (self.offset < chunk_start || self.offset >= chunk_start.saturating_add(chunk.len()))
634
0
            && (self.offset > chunk_start.saturating_add(chunk.len()) || self.cat_after.is_none())
635
        {
636
0
            return Err(GraphemeIncomplete::InvalidOffset);
637
36.6M
        }
638
36.6M
        if let Some(pre_context_offset) = self.pre_context_offset {
639
0
            return Err(GraphemeIncomplete::PreContext(pre_context_offset));
640
36.6M
        }
641
36.6M
        let offset_in_chunk = self.offset.saturating_sub(chunk_start);
642
36.6M
        if self.cat_after.is_none() {
643
0
            let ch = chunk[offset_in_chunk..].chars().next().unwrap();
644
0
            self.cat_after = Some(self.grapheme_category(ch));
645
36.6M
        }
646
36.6M
        if self.offset == chunk_start {
647
0
            let mut need_pre_context = true;
648
0
            match self.cat_after.unwrap() {
649
0
                gr::GC_InCB_Consonant => self.state = GraphemeState::InCbConsonant,
650
                // Only look back for the RI count if it isn't known already.
651
0
                gr::GC_Regional_Indicator if self.ris_count.is_none() => {
652
0
                    self.state = GraphemeState::Regional
653
                }
654
                gr::GC_Extended_Pictographic => {
655
0
                    self.state = GraphemeState::Emoji { seen_zwj: false }
656
                }
657
0
                _ => need_pre_context = self.cat_before.is_none(),
658
            }
659
0
            if need_pre_context {
660
0
                self.pre_context_offset = Some(chunk_start);
661
0
                return Err(GraphemeIncomplete::PreContext(chunk_start));
662
0
            }
663
36.6M
        }
664
36.6M
        if self.cat_before.is_none() {
665
0
            let ch = chunk[..offset_in_chunk].chars().next_back().unwrap();
666
0
            self.cat_before = Some(self.grapheme_category(ch));
667
36.6M
        }
668
36.6M
        match check_pair(self.cat_before.unwrap(), self.cat_after.unwrap()) {
669
59.8k
            PairResult::NotBreak => self.decision(false),
670
36.5M
            PairResult::Break => self.decision(true),
671
            PairResult::Extended => {
672
1.70k
                let is_extended = self.is_extended;
673
1.70k
                self.decision(!is_extended)
674
            }
675
            PairResult::InCbConsonant => {
676
10.4k
                self.handle_incb_consonant(&chunk[..offset_in_chunk], chunk_start);
677
10.4k
                self.is_boundary_result()
678
            }
679
            PairResult::Regional => {
680
1.95k
                if let Some(ris_count) = self.ris_count {
681
1.93k
                    return self.decision((ris_count % 2) == 0);
682
26
                }
683
26
                self.handle_regional(&chunk[..offset_in_chunk], chunk_start);
684
26
                self.is_boundary_result()
685
            }
686
            PairResult::Emoji => {
687
11.4k
                self.handle_emoji(&chunk[..offset_in_chunk], chunk_start, false);
688
11.4k
                self.is_boundary_result()
689
            }
690
        }
691
36.6M
    }
<unicode_segmentation::grapheme::GraphemeCursor>::is_boundary
Line
Count
Source
621
36.6M
    pub fn is_boundary(
622
36.6M
        &mut self,
623
36.6M
        chunk: &str,
624
36.6M
        chunk_start: usize,
625
36.6M
    ) -> Result<bool, GraphemeIncomplete> {
626
        use crate::tables::grapheme as gr;
627
36.6M
        if self.state == GraphemeState::Break {
628
3.86k
            return Ok(true);
629
36.6M
        }
630
36.6M
        if self.state == GraphemeState::NotBreak {
631
0
            return Ok(false);
632
36.6M
        }
633
36.6M
        if (self.offset < chunk_start || self.offset >= chunk_start.saturating_add(chunk.len()))
634
0
            && (self.offset > chunk_start.saturating_add(chunk.len()) || self.cat_after.is_none())
635
        {
636
0
            return Err(GraphemeIncomplete::InvalidOffset);
637
36.6M
        }
638
36.6M
        if let Some(pre_context_offset) = self.pre_context_offset {
639
0
            return Err(GraphemeIncomplete::PreContext(pre_context_offset));
640
36.6M
        }
641
36.6M
        let offset_in_chunk = self.offset.saturating_sub(chunk_start);
642
36.6M
        if self.cat_after.is_none() {
643
0
            let ch = chunk[offset_in_chunk..].chars().next().unwrap();
644
0
            self.cat_after = Some(self.grapheme_category(ch));
645
36.6M
        }
646
36.6M
        if self.offset == chunk_start {
647
0
            let mut need_pre_context = true;
648
0
            match self.cat_after.unwrap() {
649
0
                gr::GC_InCB_Consonant => self.state = GraphemeState::InCbConsonant,
650
                // Only look back for the RI count if it isn't known already.
651
0
                gr::GC_Regional_Indicator if self.ris_count.is_none() => {
652
0
                    self.state = GraphemeState::Regional
653
                }
654
                gr::GC_Extended_Pictographic => {
655
0
                    self.state = GraphemeState::Emoji { seen_zwj: false }
656
                }
657
0
                _ => need_pre_context = self.cat_before.is_none(),
658
            }
659
0
            if need_pre_context {
660
0
                self.pre_context_offset = Some(chunk_start);
661
0
                return Err(GraphemeIncomplete::PreContext(chunk_start));
662
0
            }
663
36.6M
        }
664
36.6M
        if self.cat_before.is_none() {
665
0
            let ch = chunk[..offset_in_chunk].chars().next_back().unwrap();
666
0
            self.cat_before = Some(self.grapheme_category(ch));
667
36.6M
        }
668
36.6M
        match check_pair(self.cat_before.unwrap(), self.cat_after.unwrap()) {
669
59.8k
            PairResult::NotBreak => self.decision(false),
670
36.5M
            PairResult::Break => self.decision(true),
671
            PairResult::Extended => {
672
1.70k
                let is_extended = self.is_extended;
673
1.70k
                self.decision(!is_extended)
674
            }
675
            PairResult::InCbConsonant => {
676
10.4k
                self.handle_incb_consonant(&chunk[..offset_in_chunk], chunk_start);
677
10.4k
                self.is_boundary_result()
678
            }
679
            PairResult::Regional => {
680
1.95k
                if let Some(ris_count) = self.ris_count {
681
1.93k
                    return self.decision((ris_count % 2) == 0);
682
26
                }
683
26
                self.handle_regional(&chunk[..offset_in_chunk], chunk_start);
684
26
                self.is_boundary_result()
685
            }
686
            PairResult::Emoji => {
687
11.4k
                self.handle_emoji(&chunk[..offset_in_chunk], chunk_start, false);
688
11.4k
                self.is_boundary_result()
689
            }
690
        }
691
36.6M
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::is_boundary
692
693
    #[inline]
694
    /// Find the next boundary after the current cursor position. Only a part of
695
    /// the string need be supplied. If the chunk is incomplete, then this
696
    /// method might return `GraphemeIncomplete::PreContext` or
697
    /// `GraphemeIncomplete::NextChunk`. In the former case, the caller should
698
    /// call `provide_context` with the requested chunk, then retry. In the
699
    /// latter case, the caller should provide the chunk following the one
700
    /// given, then retry.
701
    ///
702
    /// See `is_boundary` for expectations on the provided chunk.
703
    ///
704
    /// ```rust
705
    /// # use unicode_segmentation::GraphemeCursor;
706
    /// let flags = "\u{1F1F7}\u{1F1F8}\u{1F1EE}\u{1F1F4}";
707
    /// let mut cursor = GraphemeCursor::new(4, flags.len(), false);
708
    /// assert_eq!(cursor.next_boundary(flags, 0), Ok(Some(8)));
709
    /// assert_eq!(cursor.next_boundary(flags, 0), Ok(Some(16)));
710
    /// assert_eq!(cursor.next_boundary(flags, 0), Ok(None));
711
    /// ```
712
    ///
713
    /// And an example that uses partial strings:
714
    ///
715
    /// ```rust
716
    /// # use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
717
    /// let s = "abcd";
718
    /// let mut cursor = GraphemeCursor::new(0, s.len(), false);
719
    /// assert_eq!(cursor.next_boundary(&s[..2], 0), Ok(Some(1)));
720
    /// assert_eq!(cursor.next_boundary(&s[..2], 0), Err(GraphemeIncomplete::NextChunk));
721
    /// assert_eq!(cursor.next_boundary(&s[2..4], 2), Ok(Some(2)));
722
    /// assert_eq!(cursor.next_boundary(&s[2..4], 2), Ok(Some(3)));
723
    /// assert_eq!(cursor.next_boundary(&s[2..4], 2), Ok(Some(4)));
724
    /// assert_eq!(cursor.next_boundary(&s[2..4], 2), Ok(None));
725
    /// ```
726
36.5M
    pub fn next_boundary(
727
36.5M
        &mut self,
728
36.5M
        chunk: &str,
729
36.5M
        chunk_start: usize,
730
36.5M
    ) -> Result<Option<usize>, GraphemeIncomplete> {
731
36.5M
        if self.offset == self.len {
732
0
            return Ok(None);
733
36.5M
        }
734
36.5M
        let mut iter = chunk[self.offset.saturating_sub(chunk_start)..].chars();
735
36.5M
        let mut ch = match iter.next() {
736
36.5M
            Some(ch) => ch,
737
0
            None => return Err(GraphemeIncomplete::NextChunk),
738
        };
739
        loop {
740
36.6M
            if self.resuming {
741
0
                if self.cat_after.is_none() {
742
0
                    self.cat_after = Some(self.grapheme_category(ch));
743
0
                }
744
            } else {
745
36.6M
                self.offset = self.offset.saturating_add(ch.len_utf8());
746
36.6M
                self.state = GraphemeState::Unknown;
747
36.6M
                self.cat_before = self.cat_after.take();
748
36.6M
                if self.cat_before.is_none() {
749
3.86k
                    self.cat_before = Some(self.grapheme_category(ch));
750
36.6M
                }
751
                // ZWNJ is the one `gcb=Extend` that is `InCB=None`.
752
36.6M
                if !may_be_incb(self.cat_before.unwrap()) || ch == '\u{200c}' {
753
36.5M
                    self.incb_linker_count = Some(0);
754
36.5M
                } else if crate::tables::is_incb_linker(ch) {
755
27.5k
                    self.incb_linker_count = Some(self.incb_linker_count.map_or(1, |c| c + 1));
<unicode_segmentation::grapheme::GraphemeCursor>::next_boundary::{closure#0}
Line
Count
Source
755
27.2k
                    self.incb_linker_count = Some(self.incb_linker_count.map_or(1, |c| c + 1));
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::next_boundary::{closure#0}
756
35.9k
                }
757
36.6M
                if self.cat_before.unwrap() == GraphemeCat::GC_Regional_Indicator {
758
3.28k
                    self.ris_count = self.ris_count.map(|c| c + 1);
<unicode_segmentation::grapheme::GraphemeCursor>::next_boundary::{closure#1}
Line
Count
Source
758
3.22k
                    self.ris_count = self.ris_count.map(|c| c + 1);
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::next_boundary::{closure#1}
759
36.6M
                } else {
760
36.6M
                    self.ris_count = Some(0);
761
36.6M
                }
762
36.6M
                if let Some(next_ch) = iter.next() {
763
36.6M
                    ch = next_ch;
764
36.6M
                    self.cat_after = Some(self.grapheme_category(ch));
765
36.6M
                } else if self.offset == self.len {
766
3.86k
                    self.decide(true);
767
3.86k
                } else {
768
0
                    self.resuming = true;
769
0
                    return Err(GraphemeIncomplete::NextChunk);
770
                }
771
            }
772
36.6M
            self.resuming = true;
773
36.6M
            if self.is_boundary(chunk, chunk_start)? {
774
36.5M
                self.resuming = false;
775
36.5M
                return Ok(Some(self.offset));
776
70.0k
            }
777
70.0k
            self.resuming = false;
778
        }
779
36.5M
    }
<unicode_segmentation::grapheme::GraphemeCursor>::next_boundary
Line
Count
Source
726
36.5M
    pub fn next_boundary(
727
36.5M
        &mut self,
728
36.5M
        chunk: &str,
729
36.5M
        chunk_start: usize,
730
36.5M
    ) -> Result<Option<usize>, GraphemeIncomplete> {
731
36.5M
        if self.offset == self.len {
732
0
            return Ok(None);
733
36.5M
        }
734
36.5M
        let mut iter = chunk[self.offset.saturating_sub(chunk_start)..].chars();
735
36.5M
        let mut ch = match iter.next() {
736
36.5M
            Some(ch) => ch,
737
0
            None => return Err(GraphemeIncomplete::NextChunk),
738
        };
739
        loop {
740
36.6M
            if self.resuming {
741
0
                if self.cat_after.is_none() {
742
0
                    self.cat_after = Some(self.grapheme_category(ch));
743
0
                }
744
            } else {
745
36.6M
                self.offset = self.offset.saturating_add(ch.len_utf8());
746
36.6M
                self.state = GraphemeState::Unknown;
747
36.6M
                self.cat_before = self.cat_after.take();
748
36.6M
                if self.cat_before.is_none() {
749
3.86k
                    self.cat_before = Some(self.grapheme_category(ch));
750
36.6M
                }
751
                // ZWNJ is the one `gcb=Extend` that is `InCB=None`.
752
36.6M
                if !may_be_incb(self.cat_before.unwrap()) || ch == '\u{200c}' {
753
36.5M
                    self.incb_linker_count = Some(0);
754
36.5M
                } else if crate::tables::is_incb_linker(ch) {
755
27.5k
                    self.incb_linker_count = Some(self.incb_linker_count.map_or(1, |c| c + 1));
756
35.9k
                }
757
36.6M
                if self.cat_before.unwrap() == GraphemeCat::GC_Regional_Indicator {
758
3.28k
                    self.ris_count = self.ris_count.map(|c| c + 1);
759
36.6M
                } else {
760
36.6M
                    self.ris_count = Some(0);
761
36.6M
                }
762
36.6M
                if let Some(next_ch) = iter.next() {
763
36.6M
                    ch = next_ch;
764
36.6M
                    self.cat_after = Some(self.grapheme_category(ch));
765
36.6M
                } else if self.offset == self.len {
766
3.86k
                    self.decide(true);
767
3.86k
                } else {
768
0
                    self.resuming = true;
769
0
                    return Err(GraphemeIncomplete::NextChunk);
770
                }
771
            }
772
36.6M
            self.resuming = true;
773
36.6M
            if self.is_boundary(chunk, chunk_start)? {
774
36.5M
                self.resuming = false;
775
36.5M
                return Ok(Some(self.offset));
776
70.0k
            }
777
70.0k
            self.resuming = false;
778
        }
779
36.5M
    }
Unexecuted instantiation: <unicode_segmentation::grapheme::GraphemeCursor>::next_boundary
780
781
    /// Find the previous boundary after the current cursor position. Only a part
782
    /// of the string need be supplied. If the chunk is incomplete, then this
783
    /// method might return `GraphemeIncomplete::PreContext` or
784
    /// `GraphemeIncomplete::PrevChunk`. In the former case, the caller should
785
    /// call `provide_context` with the requested chunk, then retry. In the
786
    /// latter case, the caller should provide the chunk preceding the one
787
    /// given, then retry.
788
    ///
789
    /// See `is_boundary` for expectations on the provided chunk.
790
    ///
791
    /// ```rust
792
    /// # use unicode_segmentation::GraphemeCursor;
793
    /// let flags = "\u{1F1F7}\u{1F1F8}\u{1F1EE}\u{1F1F4}";
794
    /// let mut cursor = GraphemeCursor::new(12, flags.len(), false);
795
    /// assert_eq!(cursor.prev_boundary(flags, 0), Ok(Some(8)));
796
    /// assert_eq!(cursor.prev_boundary(flags, 0), Ok(Some(0)));
797
    /// assert_eq!(cursor.prev_boundary(flags, 0), Ok(None));
798
    /// ```
799
    ///
800
    /// And an example that uses partial strings (note the exact return is not
801
    /// guaranteed, and may be `PrevChunk` or `PreContext` arbitrarily):
802
    ///
803
    /// ```rust
804
    /// # use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
805
    /// let s = "abcd";
806
    /// let mut cursor = GraphemeCursor::new(4, s.len(), false);
807
    /// assert_eq!(cursor.prev_boundary(&s[2..4], 2), Ok(Some(3)));
808
    /// assert_eq!(cursor.prev_boundary(&s[2..4], 2), Err(GraphemeIncomplete::PrevChunk));
809
    /// assert_eq!(cursor.prev_boundary(&s[0..2], 0), Ok(Some(2)));
810
    /// assert_eq!(cursor.prev_boundary(&s[0..2], 0), Ok(Some(1)));
811
    /// assert_eq!(cursor.prev_boundary(&s[0..2], 0), Ok(Some(0)));
812
    /// assert_eq!(cursor.prev_boundary(&s[0..2], 0), Ok(None));
813
    /// ```
814
0
    pub fn prev_boundary(
815
0
        &mut self,
816
0
        chunk: &str,
817
0
        chunk_start: usize,
818
0
    ) -> Result<Option<usize>, GraphemeIncomplete> {
819
0
        if self.offset == 0 {
820
0
            return Ok(None);
821
0
        }
822
0
        if self.offset == chunk_start {
823
0
            return Err(GraphemeIncomplete::PrevChunk);
824
0
        }
825
0
        let mut iter = chunk[..self.offset.saturating_sub(chunk_start)]
826
0
            .chars()
827
0
            .rev();
828
0
        let mut ch = iter.next().unwrap();
829
        loop {
830
0
            if self.offset == chunk_start {
831
0
                self.resuming = true;
832
0
                return Err(GraphemeIncomplete::PrevChunk);
833
0
            }
834
0
            if self.resuming {
835
0
                self.cat_before = Some(self.grapheme_category(ch));
836
0
            } else {
837
0
                self.offset -= ch.len_utf8();
838
0
                self.cat_after = self.cat_before.take();
839
0
                self.state = GraphemeState::Unknown;
840
0
                if let Some(incb_linker_count) = self.incb_linker_count {
841
                    self.incb_linker_count =
842
0
                        if incb_linker_count > 0 && crate::tables::is_incb_linker(ch) {
843
0
                            Some(incb_linker_count - 1)
844
0
                        } else if is_incb_extend(self.grapheme_category(ch), ch) {
845
0
                            Some(incb_linker_count)
846
                        } else {
847
0
                            None
848
                        };
849
0
                }
850
0
                if let Some(ris_count) = self.ris_count {
851
0
                    self.ris_count = if ris_count > 0 {
852
0
                        Some(ris_count - 1)
853
                    } else {
854
0
                        None
855
                    };
856
0
                }
857
0
                if let Some(prev_ch) = iter.next() {
858
0
                    ch = prev_ch;
859
0
                    self.cat_before = Some(self.grapheme_category(ch));
860
0
                } else if self.offset == 0 {
861
0
                    self.decide(true);
862
0
                } else {
863
0
                    self.resuming = true;
864
0
                    self.cat_after = Some(self.grapheme_category(ch));
865
0
                    return Err(GraphemeIncomplete::PrevChunk);
866
                }
867
            }
868
0
            self.resuming = true;
869
0
            if self.is_boundary(chunk, chunk_start)? {
870
0
                self.resuming = false;
871
0
                return Ok(Some(self.offset));
872
0
            }
873
0
            self.resuming = false;
874
        }
875
0
    }
876
}
877
878
#[test]
879
fn test_grapheme_cursor_ris_count_across_chunks() {
880
    use GraphemeIncomplete::*;
881
882
    let chunk0 = "a"; // 1 byte
883
    let chunk1 = "\u{1f1e6}"; // 4 bytes
884
    let chunk2 = "\u{1f1e6}"; // 4 bytes
885
    let full_len = chunk0.len() + chunk1.len() + chunk2.len(); // 9
886
    let chunk1_start = chunk0.len();
887
    let chunk2_start = chunk0.len() + chunk1.len();
888
889
    let mut c = GraphemeCursor::new(0, full_len, true);
890
    assert_eq!(c.next_boundary(chunk0, 0), Err(NextChunk));
891
    assert_eq!(c.next_boundary(chunk1, chunk1_start), Ok(Some(1)));
892
    assert_eq!(c.next_boundary(chunk1, chunk1_start), Err(NextChunk));
893
    assert_eq!(c.next_boundary(chunk2, chunk2_start), Ok(Some(9)));
894
    assert_eq!(c.next_boundary(chunk2, chunk2_start), Ok(None));
895
}
896
897
#[test]
898
fn test_grapheme_cursor_ris_precontext() {
899
    let s = "\u{1f1fa}\u{1f1f8}\u{1f1fa}\u{1f1f8}\u{1f1fa}\u{1f1f8}";
900
    let mut c = GraphemeCursor::new(8, s.len(), true);
901
    assert_eq!(
902
        c.is_boundary(&s[4..], 4),
903
        Err(GraphemeIncomplete::PreContext(4))
904
    );
905
    c.provide_context(&s[..4], 0);
906
    assert_eq!(c.is_boundary(&s[4..], 4), Ok(true));
907
}
908
909
#[test]
910
fn test_grapheme_cursor_chunk_start_require_precontext() {
911
    let s = "\r\n";
912
    let mut c = GraphemeCursor::new(1, s.len(), true);
913
    assert_eq!(
914
        c.is_boundary(&s[1..], 1),
915
        Err(GraphemeIncomplete::PreContext(1))
916
    );
917
    c.provide_context(&s[..1], 0);
918
    assert_eq!(c.is_boundary(&s[1..], 1), Ok(false));
919
}
920
921
#[test]
922
fn test_grapheme_cursor_prev_boundary() {
923
    let s = "abcd";
924
    let mut c = GraphemeCursor::new(3, s.len(), true);
925
    assert_eq!(
926
        c.prev_boundary(&s[2..], 2),
927
        Err(GraphemeIncomplete::PrevChunk)
928
    );
929
    assert_eq!(c.prev_boundary(&s[..2], 0), Ok(Some(2)));
930
}
931
932
#[test]
933
fn test_grapheme_cursor_prev_boundary_chunk_start() {
934
    let s = "abcd";
935
    let mut c = GraphemeCursor::new(2, s.len(), true);
936
    assert_eq!(
937
        c.prev_boundary(&s[2..], 2),
938
        Err(GraphemeIncomplete::PrevChunk)
939
    );
940
    assert_eq!(c.prev_boundary(&s[..2], 0), Ok(Some(1)));
941
}
942
943
#[test]
944
fn test_grapheme_cursor_boundary_with_zwj_on_chunk_start() {
945
    use GraphemeIncomplete::*;
946
947
    let chunk0 = "👩"; // 4 bytes
948
    let chunk1 = "\u{200d}🔬"; // 3 bytes + 4 bytes
949
950
    let full_len = chunk0.len() + chunk1.len();
951
952
    let mut cur = GraphemeCursor::new(0, full_len, true);
953
    assert_eq!(cur.next_boundary(chunk0, 0), Err(NextChunk));
954
    match cur.next_boundary(chunk1, chunk0.len()) {
955
        Ok(res) => assert_eq!(res, Some(11)),
956
        Err(PreContext(_)) => {
957
            cur.provide_context(chunk0, 0);
958
            assert_eq!(cur.next_boundary(chunk1, chunk0.len()), Ok(Some(11)));
959
        }
960
        _ => unreachable!(),
961
    }
962
}
963
964
#[test]
965
fn test_grapheme_cursor_emoji_no_zwj() {
966
    use GraphemeIncomplete::*;
967
    let chunk0 = "🍒"; // 4 bytes
968
    let chunk1 = "🥑"; // 4 bytes
969
    let full_len = chunk0.len() + chunk1.len();
970
971
    let mut c = GraphemeCursor::new(0, full_len, true);
972
    assert_eq!(c.next_boundary(chunk0, 0), Err(NextChunk));
973
    assert_eq!(
974
        c.next_boundary(chunk1, chunk0.len()),
975
        Err(PreContext(chunk0.len()))
976
    );
977
    c.provide_context(chunk0, 0);
978
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Ok(Some(4)));
979
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Ok(Some(8)));
980
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Ok(None));
981
}
982
983
#[test]
984
fn test_grapheme_cursor_emoji_chunk_boundary_before_zwj() {
985
    use GraphemeIncomplete::*;
986
    let chunk0 = "🍒"; // 4 bytes
987
    let chunk1 = "\u{200d}🥑"; // 3 + 4 bytes
988
    let full_len = chunk0.len() + chunk1.len(); // 11
989
990
    let mut c = GraphemeCursor::new(0, full_len, true);
991
    assert_eq!(c.next_boundary(chunk0, 0), Err(NextChunk));
992
    assert_eq!(
993
        c.next_boundary(chunk1, chunk0.len()),
994
        Err(PreContext(chunk0.len()))
995
    );
996
    c.provide_context(chunk0, 0);
997
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Ok(Some(11)));
998
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Ok(None));
999
}
1000
1001
#[test]
1002
fn test_grapheme_cursor_emoji_chunk_boundary_after_zwj() {
1003
    use GraphemeIncomplete::*;
1004
    let chunk0 = "🍒\u{200d}"; // 4 + 3 bytes
1005
    let chunk1 = "🥑"; // 4 bytes
1006
    let full_len = chunk0.len() + chunk1.len(); // 11
1007
1008
    let mut c = GraphemeCursor::new(0, full_len, true);
1009
    assert_eq!(c.next_boundary(chunk0, 0), Err(NextChunk));
1010
    assert_eq!(
1011
        c.next_boundary(chunk1, chunk0.len()),
1012
        Err(PreContext(chunk0.len()))
1013
    );
1014
    c.provide_context(chunk0, 0);
1015
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Ok(Some(11)));
1016
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Ok(None));
1017
}
1018
1019
#[test]
1020
fn test_grapheme_cursor_emoji_zwj_across_chunks() {
1021
    use GraphemeIncomplete::*;
1022
    let chunk0 = "🍒"; // 4 bytes
1023
    let chunk1 = "\u{200d}"; // 3 bytes
1024
    let chunk2 = "🥑"; // 4 bytes
1025
    let full_len = chunk0.len() + chunk1.len() + chunk2.len(); // 11
1026
    let chunk2_start = chunk0.len() + chunk1.len();
1027
1028
    let mut c = GraphemeCursor::new(0, full_len, true);
1029
    assert_eq!(c.next_boundary(chunk0, 0), Err(NextChunk));
1030
    assert_eq!(c.next_boundary(chunk1, chunk0.len()), Err(NextChunk));
1031
    assert_eq!(
1032
        c.next_boundary(chunk2, chunk2_start),
1033
        Err(PreContext(chunk2_start))
1034
    );
1035
    c.provide_context(chunk1, chunk0.len());
1036
    assert_eq!(
1037
        c.next_boundary(chunk2, chunk2_start),
1038
        Err(PreContext(chunk0.len()))
1039
    );
1040
    c.provide_context(chunk0, 0);
1041
    assert_eq!(c.next_boundary(chunk2, chunk2_start), Ok(Some(11)));
1042
    assert_eq!(c.next_boundary(chunk2, chunk2_start), Ok(None));
1043
}