1use alloc::vec::Vec;
41
42use crate::segment_data::{RUN_PROPS, RUN_START};
43
44const G_CR: u32 = 1;
46const G_LF: u32 = 2;
47const G_CONTROL: u32 = 3;
48const G_EXTEND: u32 = 4;
49const G_ZWJ: u32 = 5;
50const G_RI: u32 = 6;
51const G_PREPEND: u32 = 7;
52const G_SPACINGMARK: u32 = 8;
53const G_L: u32 = 9;
54const G_V: u32 = 10;
55const G_T: u32 = 11;
56const G_LV: u32 = 12;
57const G_LVT: u32 = 13;
58
59const W_CR: u32 = 1;
61const W_LF: u32 = 2;
62const W_NEWLINE: u32 = 3;
63const W_EXTEND: u32 = 4;
64const W_ZWJ: u32 = 5;
65const W_RI: u32 = 6;
66const W_FORMAT: u32 = 7;
67const W_KATAKANA: u32 = 8;
68const W_HL: u32 = 9;
69const W_ALETTER: u32 = 10;
70const W_SQ: u32 = 11;
71const W_DQ: u32 = 12;
72const W_MIDNUMLET: u32 = 13;
73const W_MIDLETTER: u32 = 14;
74const W_MIDNUM: u32 = 15;
75const W_NUMERIC: u32 = 16;
76const W_EXTENDNUMLET: u32 = 17;
77const W_WSEG: u32 = 18;
78
79const L_BK: u32 = 1;
81const L_CR: u32 = 2;
82const L_LF: u32 = 3;
83const L_CM: u32 = 4;
84const L_NL: u32 = 5;
85const L_WJ: u32 = 7;
86const L_ZW: u32 = 8;
87const L_GL: u32 = 9;
88const L_SP: u32 = 10;
89const L_ZWJ: u32 = 11;
90const L_B2: u32 = 12;
91const L_BA: u32 = 13;
92const L_BB: u32 = 14;
93const L_HY: u32 = 15;
94const L_CB: u32 = 16;
95const L_CL: u32 = 17;
96const L_CP: u32 = 18;
97const L_EX: u32 = 19;
98const L_IN: u32 = 20;
99const L_NS: u32 = 21;
100const L_OP: u32 = 22;
101const L_QU: u32 = 23;
102const L_IS: u32 = 24;
103const L_NU: u32 = 25;
104const L_PO: u32 = 26;
105const L_PR: u32 = 27;
106const L_SY: u32 = 28;
107const L_AL: u32 = 30;
108const L_EB: u32 = 32;
109const L_EM: u32 = 33;
110const L_H2: u32 = 34;
111const L_H3: u32 = 35;
112const L_HL: u32 = 36;
113const L_ID: u32 = 37;
114const L_JL: u32 = 38;
115const L_JV: u32 = 39;
116const L_JT: u32 = 40;
117const L_RI: u32 = 41;
118
119const P_EXTPICT: u32 = 1 << 15;
120const P_WIDE: u32 = 1 << 16;
121const P_ALNUM: u32 = 1 << 17;
122const P_SPACE: u32 = 1 << 18;
123const P_UNASSIGNED: u32 = 1 << 19;
124
125pub const WK_WORD: u32 = 0;
127pub const WK_SPACE: u32 = 1;
129pub const WK_PUNCT: u32 = 2;
131
132pub fn char_props(c: char) -> u32 {
134 let cp = c as u32;
135 let i = RUN_START.partition_point(|&s| s <= cp);
136 RUN_PROPS[i.saturating_sub(1)]
137}
138
139fn gcb(p: u32) -> u32 {
140 p & 15
141}
142fn wb(p: u32) -> u32 {
143 p >> 4 & 31
144}
145fn lbc(p: u32) -> u32 {
146 p >> 9 & 63
147}
148
149fn floor(s: &str, mut i: usize) -> usize {
151 i = i.min(s.len());
152 while !s.is_char_boundary(i) {
153 i -= 1;
154 }
155 i
156}
157
158fn para_start(s: &str, i: usize) -> usize {
160 s.as_bytes()[..i].iter().rposition(|&b| b == b'\n').map_or(0, |k| k + 1)
161}
162
163fn joins(pp: u32, p: u32, ri: u32, emoji: u8) -> bool {
168 match (gcb(pp), gcb(p)) {
169 (G_CR, G_LF) => true, (G_CONTROL | G_CR | G_LF, _) | (_, G_CONTROL | G_CR | G_LF) => false, (G_L, G_L | G_V | G_LV | G_LVT) => true, (G_LV | G_V, G_V | G_T) => true, (G_LVT | G_T, G_T) => true, (_, G_EXTEND | G_ZWJ) => true, (_, G_SPACINGMARK) => true, (G_PREPEND, _) => true, _ if p & P_EXTPICT != 0 && emoji == 2 => true, (G_RI, G_RI) => ri % 2 == 1, _ => false, }
181}
182
183fn grapheme_walk(s: &str, from: usize, at: &mut dyn FnMut(usize) -> bool) {
186 let mut prev: Option<u32> = None;
187 let mut ri = 0u32; let mut emoji = 0u8; for (k, c) in s[from..].char_indices() {
190 let i = from + k;
191 let p = char_props(c);
192 let g = gcb(p);
193 if let Some(pp) = prev {
194 if !joins(pp, p, ri, emoji) && at(i) {
195 return;
196 }
197 }
198 ri = if g == G_RI { ri + 1 } else { 0 };
200 emoji = if p & P_EXTPICT != 0 {
201 1
202 } else if emoji != 0 && g == G_EXTEND {
203 1
204 } else if emoji == 1 && g == G_ZWJ {
205 2
206 } else {
207 0
208 };
209 prev = Some(p);
210 }
211 if from < s.len() {
212 at(s.len());
213 }
214}
215
216fn cluster_start(s: &str, i: usize) -> usize {
221 let mut k = floor(s, i);
222 while k > 0 && k < s.len() {
223 let (Some(a), Some(c)) = (s[..k].chars().next_back(), s[k..].chars().next()) else { break };
224 let pp = char_props(a);
225 if !joins(pp, char_props(c), 1, if gcb(pp) == G_ZWJ { 2 } else { 0 }) {
226 break;
227 }
228 k -= a.len_utf8();
229 }
230 k
231}
232
233pub fn grapheme_next(s: &str, i: usize) -> usize {
235 let i = floor(s, i);
236 if i >= s.len() {
237 return s.len();
238 }
239 cluster_after(s, cluster_start(s, i), i)
240}
241
242pub fn cluster_end(s: &str, i: usize) -> usize {
245 let i = floor(s, i);
246 let Some(c) = s.get(i..).and_then(|r| r.chars().next()) else { return s.len() };
247 let n = i + c.len_utf8();
248 let pc = char_props(c);
251 match s.as_bytes().get(n) {
252 None => n,
253 Some(&b) if b < 0x80 && gcb(pc) != G_PREPEND && !(gcb(pc) == G_CR && b == b'\n') => n,
254 _ => cluster_after(s, i, i),
255 }
256}
257
258fn cluster_after(s: &str, from: usize, i: usize) -> usize {
260 let mut end = s.len();
261 grapheme_walk(s, from, &mut |b| {
262 end = b;
263 b > i
264 });
265 end
266}
267
268pub fn grapheme_prev(s: &str, i: usize) -> usize {
270 let i = floor(s, i);
271 if i == 0 {
272 return 0;
273 }
274 let from = cluster_start(s, i - 1);
275 let mut last = from;
276 grapheme_walk(s, from, &mut |b| {
277 if b < i {
278 last = b;
279 }
280 b >= i
281 });
282 last
283}
284
285pub fn is_grapheme_boundary(s: &str, i: usize) -> bool {
287 if i == 0 || i >= s.len() {
288 return i <= s.len();
289 }
290 if !s.is_char_boundary(i) {
291 return false;
292 }
293 let from = cluster_start(s, i);
294 let mut hit = from == i;
295 if !hit {
296 grapheme_walk(s, from, &mut |b| {
297 hit = b == i;
298 b >= i
299 });
300 }
301 hit
302}
303
304pub fn graphemes(s: &str) -> Vec<usize> {
306 let mut b = alloc::vec![0];
307 grapheme_walk(s, 0, &mut |x| {
308 b.push(x);
309 false
310 });
311 b
312}
313
314pub fn pictographic_cluster(s: &str, a: usize, b: usize) -> bool {
316 let t = &s[floor(s, a)..floor(s, b)];
317 t.chars().count() > 1
318 && t.chars().any(|c| {
319 let p = char_props(c);
320 p & P_EXTPICT != 0 || gcb(p) == G_RI || gcb(p) == G_ZWJ || c == '\u{20E3}' || c == '\u{FE0F}'
321 })
322}
323
324fn ignorable(w: u32) -> bool {
327 matches!(w, W_EXTEND | W_FORMAT | W_ZWJ)
328}
329
330fn ahletter(w: u32) -> bool {
331 w == W_ALETTER || w == W_HL
332}
333
334fn midnumletq(w: u32) -> bool {
335 w == W_MIDNUMLET || w == W_SQ
336}
337
338pub fn word_bounds(s: &str) -> Vec<usize> {
340 let ch: Vec<(usize, u32)> = s.char_indices().map(|(i, c)| (i, char_props(c))).collect();
341 let n = ch.len();
342 let w = |k: usize| wb(ch[k].1);
343 let before = |k: usize| -> Option<usize> { (0..k).rev().find(|&j| !ignorable(w(j))) };
345 let after = |k: usize| -> Option<usize> { (k + 1..n).find(|&j| !ignorable(w(j))) };
346 let mut out = alloc::vec![0];
347 for k in 1..n {
348 let a = w(k - 1);
349 let b = w(k);
350 let keep = if a == W_CR && b == W_LF {
351 true } else if matches!(a, W_NEWLINE | W_CR | W_LF) || matches!(b, W_NEWLINE | W_CR | W_LF) {
353 false } else if a == W_ZWJ && ch[k].1 & P_EXTPICT != 0 {
355 true } else if a == W_WSEG && b == W_WSEG {
357 true } else if ignorable(b) {
359 true } else {
361 let p1 = before(k);
363 let x = p1.map_or(a, w);
364 let x0 = p1.and_then(before).map(w); let y = b;
366 let y2 = after(k).map(w); let ri_odd = || {
368 let mut c = 0;
369 let mut j = p1;
370 while let Some(jj) = j {
371 if w(jj) != W_RI {
372 break;
373 }
374 c += 1;
375 j = before(jj);
376 }
377 c % 2 == 1
378 };
379 (ahletter(x) && ahletter(y)) || (ahletter(x) && (y == W_MIDLETTER || midnumletq(y)) && y2.is_some_and(ahletter)) || (x0.is_some_and(ahletter) && (x == W_MIDLETTER || midnumletq(x)) && ahletter(y)) || (x == W_HL && y == W_SQ) || (x == W_HL && y == W_DQ && y2 == Some(W_HL)) || (x0 == Some(W_HL) && x == W_DQ && y == W_HL) || (x == W_NUMERIC && y == W_NUMERIC) || (ahletter(x) && y == W_NUMERIC) || (x == W_NUMERIC && ahletter(y)) || (x0 == Some(W_NUMERIC) && (x == W_MIDNUM || midnumletq(x)) && y == W_NUMERIC) || (x == W_NUMERIC && (y == W_MIDNUM || midnumletq(y)) && y2 == Some(W_NUMERIC)) || (x == W_KATAKANA && y == W_KATAKANA) || ((ahletter(x) || matches!(x, W_NUMERIC | W_KATAKANA | W_EXTENDNUMLET)) && y == W_EXTENDNUMLET) || (x == W_EXTENDNUMLET && (ahletter(y) || matches!(y, W_NUMERIC | W_KATAKANA))) || (x == W_RI && y == W_RI && ri_odd()) };
395 if !keep {
396 out.push(ch[k].0);
397 }
398 }
399 if s.is_empty() {
400 return out;
401 }
402 out.push(s.len());
403 out
404}
405
406pub fn word_kind(s: &str, a: usize, b: usize) -> u32 {
408 let t = &s[floor(s, a)..floor(s, b)];
409 let mut space = !t.is_empty();
410 for c in t.chars() {
411 let p = char_props(c);
412 if p & P_ALNUM != 0 || wb(p) == W_EXTENDNUMLET || matches!(lbc(p), L_ID | L_H2 | L_H3 | L_JL | L_JV | L_JT) {
414 return WK_WORD;
415 }
416 if p & P_SPACE == 0 && !ignorable(wb(p)) {
417 space = false;
418 }
419 }
420 if space { WK_SPACE } else { WK_PUNCT }
421}
422
423fn word_hard(s: &str, k: usize) -> bool {
428 let (Some(a), Some(b)) = (s[..k].chars().next_back(), s[k..].chars().next()) else { return true };
429 let (x, y) = (wb(char_props(a)), wb(char_props(b)));
430 if ignorable(x) || ignorable(y) {
431 return false;
432 }
433 if matches!(x, W_NEWLINE | W_CR | W_LF) || matches!(y, W_NEWLINE | W_CR | W_LF) {
434 return !(x == W_CR && y == W_LF);
435 }
436 x == 0 || y == 0 || (x == W_WSEG) != (y == W_WSEG)
437}
438
439const WORD_REACH: usize = 256;
441
442fn para_words(s: &str, i: usize) -> (Vec<usize>, usize) {
445 let mut a = i;
446 let mut n = 0;
447 while a > 0 && !word_hard(s, a) && n < WORD_REACH {
448 a = floor(s, a - 1);
449 n += 1;
450 }
451 if a > 0 && !word_hard(s, a) {
452 a = para_start(s, i);
453 }
454 let mut e = i + s[i..].chars().next().map_or(0, |c| c.len_utf8());
455 n = 0;
456 while e < s.len() && !word_hard(s, e) && n < WORD_REACH {
457 e += s[e..].chars().next().map_or(1, |c| c.len_utf8());
458 n += 1;
459 }
460 if e < s.len() && !word_hard(s, e) {
461 e = s.as_bytes()[i..].iter().position(|&b| b == b'\n').map_or(s.len(), |k| i + k + 1);
462 }
463 (word_bounds(&s[a..e]).into_iter().map(|x| x + a).collect(), a)
464}
465
466pub fn word_at(s: &str, i: usize) -> (usize, usize) {
468 let i = floor(s, i);
469 if s.is_empty() {
470 return (0, 0);
471 }
472 let i = if i >= s.len() { floor(s, s.len() - 1) } else { i };
473 let (b, _) = para_words(s, i);
474 let k = b.partition_point(|&x| x <= i);
475 let start = b[k.saturating_sub(1)];
476 let end = b.get(k).copied().unwrap_or(s.len());
477 (start, end)
478}
479
480pub fn word_next(s: &str, i: usize) -> usize {
483 let mut p = floor(s, i);
484 if p >= s.len() {
485 return s.len();
486 }
487 let (_, e) = word_at(s, p);
488 p = e;
489 while p < s.len() {
490 let (a, e) = word_at(s, p);
491 if word_kind(s, a, e) != WK_SPACE || s[a..e].contains('\n') {
492 break;
493 }
494 p = e;
495 }
496 p
497}
498
499pub fn word_prev(s: &str, i: usize) -> usize {
501 let mut p = floor(s, i);
502 while p > 0 {
503 let (a, e) = word_at(s, p - 1);
504 if word_kind(s, a, e) != WK_SPACE || s[a..e].contains('\n') {
505 return a;
506 }
507 p = a;
508 }
509 0
510}
511
512pub fn line_breaks(s: &str) -> Vec<(usize, bool)> {
517 let ch: Vec<(usize, u32)> = s.char_indices().map(|(i, c)| (i, char_props(c))).collect();
518 let n = ch.len();
519 let mut out = Vec::new();
520 if n == 0 {
521 return out;
522 }
523 let mut cls: Vec<u32> = Vec::with_capacity(n);
525 let mut attached: Vec<bool> = Vec::with_capacity(n);
526 for k in 0..n {
527 let c = lbc(ch[k].1);
528 if matches!(c, L_CM | L_ZWJ) {
529 if let Some(&base) = cls.last() {
530 if !matches!(base, L_BK | L_CR | L_LF | L_NL | L_SP | L_ZW) {
531 cls.push(base);
532 attached.push(true);
533 continue;
534 }
535 }
536 cls.push(L_AL);
537 attached.push(false);
538 continue;
539 }
540 cls.push(c);
541 attached.push(false);
542 }
543 let wide = |k: usize| ch[k].1 & P_WIDE != 0;
544 let mut ri = 0u32; let mut sp_before: Option<u32> = None; for k in 1..n {
547 let a = cls[k - 1];
548 let b = cls[k];
549 let raw_a = lbc(ch[k - 1].1);
550 if a == L_SP {
552 if k >= 2 && cls[k - 2] != L_SP {
553 sp_before = Some(cls[k - 2]);
554 } else if k < 2 {
555 sp_before = None;
556 }
557 } else {
558 sp_before = None;
559 }
560 if cls[k - 1] == L_RI && !attached[k - 1] {
561 ri += 1;
562 } else if !attached[k - 1] {
563 ri = 0;
564 }
565 let mandatory = match a {
567 L_BK | L_NL | L_LF => true,
568 L_CR => b != L_LF,
569 _ => false,
570 };
571 if mandatory {
572 out.push((ch[k].0, true));
573 continue;
574 }
575 let brk = if a == L_CR && b == L_LF {
576 false } else if matches!(b, L_BK | L_CR | L_LF | L_NL) {
578 false } else if b == L_SP || b == L_ZW {
580 false } else if a == L_ZW || (a == L_SP && sp_before == Some(L_ZW)) {
582 true } else if raw_a == L_ZWJ {
584 false } else if attached[k] {
586 false } else if a == L_WJ || b == L_WJ {
588 false } else if a == L_GL {
590 false } else if b == L_GL && !matches!(a, L_SP | L_BA | L_HY) {
592 false } else if matches!(b, L_CL | L_CP | L_EX | L_IS | L_SY) {
594 false } else if a == L_OP || (a == L_SP && sp_before == Some(L_OP)) {
596 false } else if b == L_OP && (a == L_QU || (a == L_SP && sp_before == Some(L_QU))) {
598 false } else if b == L_NS && (matches!(a, L_CL | L_CP) || (a == L_SP && matches!(sp_before, Some(L_CL | L_CP)))) {
600 false } else if b == L_B2 && (a == L_B2 || (a == L_SP && sp_before == Some(L_B2))) {
602 false } else if a == L_SP {
604 true } else if a == L_QU || b == L_QU {
606 false } else if a == L_CB || b == L_CB {
608 true } else if matches!(b, L_BA | L_HY | L_NS) || a == L_BB {
610 false } else if matches!(a, L_HY | L_BA) && k >= 2 && cls[k - 2] == L_HL {
612 false } else if a == L_SY && b == L_HL {
614 false } else if b == L_IN {
616 false } else if (matches!(a, L_AL | L_HL) && b == L_NU) || (a == L_NU && matches!(b, L_AL | L_HL)) {
618 false } else if (a == L_PR && matches!(b, L_ID | L_EB | L_EM)) || (matches!(a, L_ID | L_EB | L_EM) && b == L_PO) {
620 false } else if (matches!(a, L_PR | L_PO) && matches!(b, L_AL | L_HL)) || (matches!(a, L_AL | L_HL) && matches!(b, L_PR | L_PO)) {
622 false } else if matches!(
624 (a, b),
625 (L_CL | L_CP | L_NU, L_PO | L_PR) | (L_PO | L_PR, L_OP | L_NU) | (L_HY | L_IS | L_NU | L_SY, L_NU)
626 ) {
627 false } else if (a == L_JL && matches!(b, L_JL | L_JV | L_H2 | L_H3))
629 || (matches!(a, L_JV | L_H2) && matches!(b, L_JV | L_JT))
630 || (matches!(a, L_JT | L_H3) && b == L_JT)
631 {
632 false } else if (matches!(a, L_JL | L_JV | L_JT | L_H2 | L_H3) && b == L_PO) || (a == L_PR && matches!(b, L_JL | L_JV | L_JT | L_H2 | L_H3)) {
634 false } else if matches!(a, L_AL | L_HL) && matches!(b, L_AL | L_HL) {
636 false } else if a == L_IS && matches!(b, L_AL | L_HL) {
638 false } else if (matches!(a, L_AL | L_HL | L_NU) && b == L_OP && !wide(k)) || (a == L_CP && !wide(k - 1) && matches!(b, L_AL | L_HL | L_NU)) {
640 false } else if a == L_RI && b == L_RI && ri % 2 == 1 {
642 false } else if (a == L_EB || (ch[k - 1].1 & P_EXTPICT != 0 && ch[k - 1].1 & P_UNASSIGNED != 0)) && b == L_EM {
644 false } else {
646 true };
648 if brk {
649 out.push((ch[k].0, false));
650 }
651 }
652 out.push((s.len(), true));
653 out
654}
655
656pub fn wrap_end(s: &str, pos: usize, pe: usize, fits: &mut dyn FnMut(usize, usize) -> bool) -> usize {
661 let pe = floor(s, pe);
662 if pos >= pe {
663 return pe;
664 }
665 let seg = &s[pos..pe];
666 let ops = line_breaks(seg);
667 let hard = ops.iter().find(|o| o.1 && pos + o.0 < pe).map(|o| pos + o.0);
669 let limit = hard.unwrap_or(pe);
670 let visible = |e: usize| {
671 let mut v = e;
672 while v > pos {
673 let c = s[..v].chars().next_back().unwrap_or('a');
674 if char_props(c) & P_SPACE == 0 {
675 break;
676 }
677 v -= c.len_utf8();
678 }
679 v
680 };
681 if fits(pos, visible(limit)) {
682 return limit;
683 }
684 let mut best = pos;
685 for &(o, _) in &ops {
686 let e = pos + o;
687 if e > limit || e == pos {
688 continue;
689 }
690 if !fits(pos, visible(e)) {
691 break;
692 }
693 best = e;
694 }
695 if best > pos {
696 return best;
697 }
698 let mut best = grapheme_next(s, pos).min(limit);
700 while best < limit {
701 let n = grapheme_next(s, best).min(limit);
702 if !fits(pos, n) {
703 break;
704 }
705 best = n;
706 }
707 best
708}
709
710pub fn wrap_end_hyph(
719 s: &str,
720 pos: usize,
721 pe: usize,
722 fits: &mut dyn FnMut(usize, usize, bool) -> bool,
723 mut hyph: Option<&mut dyn FnMut(&str, &mut Vec<usize>)>,
724) -> (usize, bool) {
725 let pe = floor(s, pe);
726 if pos >= pe {
727 return (pe, false);
728 }
729 let seg = &s[pos..pe];
730 let ops = line_breaks(seg);
731 let hard = ops.iter().find(|o| o.1 && pos + o.0 < pe).map(|o| pos + o.0);
732 let limit = hard.unwrap_or(pe);
733 let visible = |e: usize| {
734 let mut v = e;
735 while v > pos {
736 let c = s[..v].chars().next_back().unwrap_or('a');
737 if char_props(c) & P_SPACE == 0 {
738 break;
739 }
740 v -= c.len_utf8();
741 }
742 v
743 };
744 let shy = |e: usize| s[..e].ends_with('\u{AD}');
745 if fits(pos, visible(limit), false) {
746 return (limit, false);
747 }
748 let mut best = pos;
751 let mut best_h = false;
752 let mut next = limit;
753 for &(o, _) in &ops {
754 let e = pos + o;
755 if e > limit || e == pos {
756 continue;
757 }
758 let h = shy(e);
759 if fits(pos, visible(e), h) {
760 best = e;
761 best_h = h;
762 } else if !h {
763 next = e;
764 break;
765 }
766 }
767 if next <= best {
768 next = ops.iter().map(|o| pos + o.0).find(|&e| e > best && e <= limit).unwrap_or(limit);
769 }
770 if let Some(h) = hyph.as_mut() {
772 let mut words: Vec<(usize, usize)> = Vec::new();
773 let mut q = best;
774 while q < next {
775 let (a, b) = word_at(s, q);
776 if b <= q {
777 break;
778 }
779 let w = &s[a..b];
780 if word_kind(s, a, b) == WK_WORD && !w.contains('\u{AD}') && !w.chars().any(|c| c.is_numeric()) {
781 words.push((a, b));
782 }
783 q = b;
784 }
785 let mut offs = Vec::new();
786 for &(a, b) in words.iter().rev() {
787 offs.clear();
788 h(&s[a..b], &mut offs);
789 for &o in offs.iter().rev() {
790 let e = a + o;
791 if e > pos && e > best && e < b && e <= next && s.is_char_boundary(e) && fits(pos, e, true) {
792 return (e, true);
793 }
794 }
795 }
796 }
797 if best > pos {
798 return (best, best_h);
799 }
800 let mut end = grapheme_next(s, pos).min(limit);
802 while end < limit {
803 let n = grapheme_next(s, end).min(limit);
804 if !fits(pos, n, false) {
805 break;
806 }
807 end = n;
808 }
809 (end, false)
810}
811
812pub fn unicode_version() -> &'static str {
814 crate::segment_data::UNICODE_VERSION
815}