1use write16::Write16;
11
12static TABLE: [u16; 96] = [
23 0x01, 0, 0, 0, 0, 0, 0, 0, 0x02, 0, 0x09, 0, 0, 0, 0, 0x0E, 0, 0, 0x41, 0x45, 0x16, 0x1D, 0, 0, 0x22, 0x2D, 0x29, 0, 0x2F, 0x3B, 0x47, 0, 0x4100, 0x4101, 0x4102, 0x4103, 0x4108, 0x410A, 0, 0x4327, 0x4500, 0x4501, 0x4502, 0x4508, 0x4900, 0x4901, 0x4902, 0x4908, 0, 0x4E03, 0x4F00, 0x4F01, 0x4F02, 0x4F03, 0x4F08, 0, 0, 0x5500, 0x5501, 0x5502, 0x5508, 0x5901, 0, 0, 0x6100, 0x6101, 0x6102, 0x6103, 0x6108, 0x610A, 0, 0x6327, 0x6500, 0x6501, 0x6502, 0x6508, 0x6900, 0x6901, 0x6902, 0x6908, 0, 0x6E03, 0x6F00, 0x6F01, 0x6F02, 0x6F03, 0x6F08, 0, 0, 0x7500, 0x7501, 0x7502, 0x7508, 0x7901, 0, 0x7908, ];
120
121static COMPATIBILITY_DECOMPOSITIONS: [u16; 20] = [
123 0x0020, 0x0308, 0x0061, 0x0020, 0x0304, 0x0020, 0x0301, 0x03BC, 0x0020, 0x0327, 0x006F, 0x0031,
124 0x2044, 0x0034, 0x0031, 0x2044, 0x0032, 0x0033, 0x2044, 0x0034,
125];
126
127const NFKC_BITS: u32 = const {
128 let mut accu = 0;
129 let mut i = 0;
130 while i < 0x20 {
131 if TABLE[i] != 0 {
132 accu |= 1 << (i as u32);
133 }
134 i += 1;
135 }
136 accu
137};
138
139const NFD_BITS: u64 = const {
140 let mut accu = 0;
141 let mut i = 0x20;
142 while i < TABLE.len() {
143 if TABLE[i] != 0 {
144 accu |= 1 << ((i - 0x20) as u32);
145 }
146 i += 1;
147 }
148 accu
149};
150
151const NFKD_BITS: u128 = const {
152 let mut accu = 0;
153 let mut i = 0;
154 while i < TABLE.len() {
155 if TABLE[i] != 0 {
156 accu |= 1 << ((i + 0x20) as u32);
157 }
158 i += 1;
159 }
160 accu
161};
162
163#[inline]
165fn compatibility_decomposition(val: u16) -> &'static [u16] {
166 debug_assert!(val <= 0xFF);
167 let len = val & 0b11;
168 let index = val >> 2;
169 COMPATIBILITY_DECOMPOSITIONS
170 .get(index as usize..index as usize + len as usize)
171 .unwrap_or_else(|| {
172 debug_assert!(false);
174 &[]
175 })
176}
177
178#[inline]
180pub fn normalize_nfd_to<W: Write16 + ?Sized>(text: &[u16], sink: &mut W) -> core::fmt::Result {
181 #[expect(clippy::indexing_slicing)]
183 let table = &TABLE[0x20..];
184 let mut text_left = text;
185 let mut iter = text_left.iter();
186 while let Some(u) = iter.next() {
187 let c = *u;
188 if c < 0xC0 {
189 continue;
190 }
191 if let Some(val) = table.get(c.wrapping_sub(0xC0) as usize) {
192 let v = *val;
193 if v != 0 {
194 let remaining = iter.as_slice();
195 #[expect(clippy::indexing_slicing)]
197 sink.write_slice(&text_left[..text_left.len() - remaining.len() - 1])?;
198 text_left = remaining;
199 sink.write_slice(&[v >> 8, (v & 0xFF) + 0x0300])?;
200 }
201 }
202 }
203 sink.write_slice(text_left)?;
204 Ok(())
205}
206
207#[inline]
209pub fn normalize_nfkd_to<W: Write16 + ?Sized>(text: &[u16], sink: &mut W) -> core::fmt::Result {
210 let mut text_left = text;
211 let mut iter = text_left.iter();
212 while let Some(u) = iter.next() {
213 let c = *u;
214 if c < 0xA0 {
215 continue;
216 }
217 if let Some(val) = TABLE.get(c.wrapping_sub(0xA0) as usize) {
218 let v = *val;
219 if v != 0 {
220 let remaining = iter.as_slice();
221 #[expect(clippy::indexing_slicing)]
223 sink.write_slice(&text_left[..text_left.len() - remaining.len() - 1])?;
224 text_left = remaining;
225 let hi = v >> 8;
226 if hi != 0 {
227 sink.write_slice(&[hi, (v & 0xFF) + 0x0300])?;
228 } else {
229 sink.write_slice(compatibility_decomposition(v))?;
230 }
231 }
232 }
233 }
234 sink.write_slice(text_left)?;
235 Ok(())
236}
237
238#[inline]
240pub fn normalize_nfkc_to<W: Write16 + ?Sized>(text: &[u16], sink: &mut W) -> core::fmt::Result {
241 #[expect(clippy::indexing_slicing)]
243 let table = &TABLE[..0x20];
244 let mut text_left = text;
245 let mut iter = text_left.iter();
246 while let Some(u) = iter.next() {
247 let c = *u;
248 if c < 0xA0 {
249 continue;
250 }
251 if let Some(val) = table.get(c.wrapping_sub(0xA0) as usize) {
252 let v = *val;
253 if v != 0 {
254 let remaining = iter.as_slice();
255 #[expect(clippy::indexing_slicing)]
257 sink.write_slice(&text_left[..text_left.len() - remaining.len() - 1])?;
258 text_left = remaining;
259 sink.write_slice(compatibility_decomposition(v))?;
260 }
261 }
262 }
263 sink.write_slice(text_left)?;
264 Ok(())
265}
266
267#[inline]
271pub fn split_normalized_nfd(text: &[u8]) -> (&[u8], &[u8]) {
272 let mut iter = text.iter();
273 while let Some(c) = iter.next() {
274 let b = *c;
275 if let Some(shifted) = 1u64.checked_shl(u32::from(b.wrapping_sub(0xC0))) {
276 if (NFD_BITS & shifted) != 0 {
277 let tail = iter.as_slice();
278 return text
279 .split_at_checked(text.len() - tail.len() - 1)
280 .unwrap_or_else(|| {
281 debug_assert!(false);
283 (&[], text)
284 });
285 }
286 }
287 }
288 (text, &[])
289}
290
291#[inline]
295pub fn split_normalized_nfkd(text: &[u8]) -> (&[u8], &[u8]) {
296 let mut iter = text.iter();
297 while let Some(c) = iter.next() {
298 let b = *c;
299 if let Some(shifted) = 1u128.checked_shl(u32::from(b.wrapping_sub(0x80))) {
300 if (NFKD_BITS & shifted) != 0 {
301 let tail = iter.as_slice();
302 return text
303 .split_at_checked(text.len() - tail.len() - 1)
304 .unwrap_or_else(|| {
305 debug_assert!(false);
307 (&[], text)
308 });
309 }
310 }
311 }
312 (text, &[])
313}
314
315#[inline]
319pub fn split_normalized_nfkc(text: &[u8]) -> (&[u8], &[u8]) {
320 let mut iter = text.iter();
321 while let Some(c) = iter.next() {
322 let b = *c;
323 if b < 0xA0 {
325 continue;
326 }
327 if let Some(shifted) = 1u32.checked_shl(u32::from(b.wrapping_sub(0xA0))) {
328 if (NFKC_BITS & shifted) != 0 {
329 let tail = iter.as_slice();
330 return text
331 .split_at_checked(text.len() - tail.len() - 1)
332 .unwrap_or_else(|| {
333 debug_assert!(false);
335 (&[], text)
336 });
337 }
338 }
339 }
340 (text, &[])
341}