Skip to main content

unicode_normalize_aligned/
recompose.rs

1//! Canonical composition on top of a decomposition.
2
3use core::iter::FusedIterator;
4
5use crate::buffer::RunBuffer;
6use crate::decompose::Decompositions;
7use crate::props::{self, hangul, Props};
8
9#[derive(Clone, Copy, Debug)]
10enum State {
11    Composing,
12    Purging(usize),
13    Finished(usize),
14}
15
16/// Streaming NFC or NFKC. Yields `(char, isize)`: the output character and its
17/// alignment change count. See the crate docs for the tag contract.
18///
19/// Created by [`UnicodeNormalization::nfc`](crate::UnicodeNormalization::nfc)
20/// and [`UnicodeNormalization::nfkc`](crate::UnicodeNormalization::nfkc).
21#[derive(Clone, Debug)]
22pub struct Recompositions<I> {
23    inner: Decompositions<I>,
24    state: State,
25    /// Marks blocked from the current starter, in order.
26    buffer: RunBuffer<(char, isize)>,
27    composee: Option<(char, isize)>,
28    /// Whether the composee can start a composite pair. `None` means not
29    /// looked up yet. A composition result defers the lookup until the next
30    /// character is a pair second, so most results skip it entirely.
31    composee_first: Option<bool>,
32    last_ccc: Option<u8>,
33}
34
35impl<I: Iterator<Item = char>> Recompositions<I> {
36    pub(crate) fn new(inner: Decompositions<I>) -> Self {
37        Recompositions {
38            inner,
39            state: State::Composing,
40            buffer: RunBuffer::new(),
41            composee: None,
42            composee_first: None,
43            last_ccc: None,
44        }
45    }
46
47    #[inline]
48    fn set_composee(&mut self, c: char, tag: isize) {
49        self.composee = Some((c, tag));
50        self.composee_first = None;
51    }
52
53    /// Composes the current starter with `c` when the pair is a primary
54    /// composite. The composite carries `starter tag + c tag - 1`.
55    #[inline]
56    fn try_compose(&mut self, starter: (char, isize), c: char, tag: isize) -> bool {
57        if !Props::of(c).composes_second() {
58            return false;
59        }
60        let first = *self
61            .composee_first
62            .get_or_insert_with(|| Props::of(starter.0).composes_first());
63        if !first {
64            return false;
65        }
66        let composed = hangul::compose(starter.0, c).or_else(|| props::compose_pair(starter.0, c));
67        match composed {
68            Some(r) => {
69                self.set_composee(r, starter.1 + tag - 1);
70                true
71            }
72            None => false,
73        }
74    }
75
76    fn block(&mut self, c: char, tag: isize, ccc: u8) {
77        self.buffer.push((c, tag));
78        self.last_ccc = Some(ccc);
79    }
80
81    fn compose_step(&mut self) -> Option<(char, isize)> {
82        while let Some((ccc, c, tag)) = self.inner.next_full() {
83            let starter = match self.composee {
84                Some(k) => k,
85                None => {
86                    if ccc != 0 {
87                        return Some((c, tag));
88                    }
89                    self.set_composee(c, tag);
90                    continue;
91                }
92            };
93            match self.last_ccc {
94                None => {
95                    if self.try_compose(starter, c, tag) {
96                        continue;
97                    }
98                    if ccc == 0 {
99                        self.set_composee(c, tag);
100                        return Some(starter);
101                    }
102                    self.block(c, tag, ccc);
103                }
104                Some(last) => {
105                    if last >= ccc {
106                        if ccc == 0 {
107                            self.set_composee(c, tag);
108                            self.last_ccc = None;
109                            self.state = State::Purging(0);
110                            return Some(starter);
111                        }
112                        self.block(c, tag, ccc);
113                        continue;
114                    }
115                    if self.try_compose(starter, c, tag) {
116                        continue;
117                    }
118                    self.block(c, tag, ccc);
119                }
120            }
121        }
122        self.state = State::Finished(0);
123        self.composee.take()
124    }
125}
126
127impl<I: Iterator<Item = char>> Iterator for Recompositions<I> {
128    type Item = (char, isize);
129
130    #[inline]
131    fn next(&mut self) -> Option<(char, isize)> {
132        loop {
133            match self.state {
134                State::Composing => {
135                    if let Some(item) = self.compose_step() {
136                        return Some(item);
137                    }
138                }
139                State::Purging(i) => {
140                    if let Some(&item) = self.buffer.get(i) {
141                        self.state = State::Purging(i + 1);
142                        return Some(item);
143                    }
144                    self.buffer.clear();
145                    self.state = State::Composing;
146                }
147                State::Finished(i) => {
148                    if let Some(&item) = self.buffer.get(i) {
149                        self.state = State::Finished(i + 1);
150                        return Some(item);
151                    }
152                    self.buffer.clear();
153                    return None;
154                }
155            }
156        }
157    }
158
159    fn size_hint(&self) -> (usize, Option<usize>) {
160        // Composition can shrink the stream. A forwarded inner lower bound
161        // can exceed the remaining count, which the Iterator contract forbids.
162        (0, None)
163    }
164}
165
166impl<I: Iterator<Item = char>> FusedIterator for Recompositions<I> {}