1
use tinyvec::tiny_vec;
2

            
3
use crate::error::{ParseError, ShapingError};
4
use crate::gsub::{self, Feature, FeatureMask, GlyphData, GlyphOrigin, RawGlyph, RawGlyphFlags};
5
use crate::layout::{FeatureTableSubstitution, GDEFTable, LayoutCache, LayoutTable, GSUB};
6
use crate::scripts::syllable::*;
7
use crate::tag;
8
use crate::unicode::mcc::sort_by_modified_combining_class;
9
use crate::DOTTED_CIRCLE;
10

            
11
fn shaping_class(c: char) -> Option<ShapingClass> {
12
    khmer_character(c).0
13
}
14

            
15
fn mark_placement(c: char) -> Option<MarkPlacementSubclass> {
16
    khmer_character(c).1
17
}
18

            
19
fn ra(c: char) -> bool {
20
    c == '\u{179A}'
21
}
22

            
23
fn consonant(c: char) -> bool {
24
    match shaping_class(c) {
25
        Some(ShapingClass::Consonant) => !ra(c),
26
        _ => false,
27
    }
28
}
29

            
30
fn vowel(c: char) -> bool {
31
    shaping_class(c) == Some(ShapingClass::VowelIndependent)
32
}
33

            
34
fn nukta(c: char) -> bool {
35
    match shaping_class(c) {
36
        Some(ShapingClass::Nukta) => true,
37
        Some(ShapingClass::ConsonantPostRepha) => true,
38
        _ => false,
39
    }
40
}
41

            
42
fn zwj(c: char) -> bool {
43
    shaping_class(c) == Some(ShapingClass::Joiner)
44
}
45

            
46
fn zwnj(c: char) -> bool {
47
    shaping_class(c) == Some(ShapingClass::NonJoiner)
48
}
49

            
50
fn matra(c: char) -> bool {
51
    match shaping_class(c) {
52
        Some(ShapingClass::VowelDependent) => true,
53
        Some(ShapingClass::PureKiller) => true,
54
        Some(ShapingClass::ConsonantKiller) => true,
55
        _ => false,
56
    }
57
}
58

            
59
fn syllable_modifier(c: char) -> bool {
60
    match shaping_class(c) {
61
        Some(ShapingClass::SyllableModifier) => true,
62
        Some(ShapingClass::Bindu) => true,
63
        Some(ShapingClass::Visarga) => true,
64
        _ => false,
65
    }
66
}
67

            
68
fn placeholder(c: char) -> bool {
69
    match shaping_class(c) {
70
        Some(ShapingClass::Placeholder) => true,
71
        Some(ShapingClass::ConsonantPlaceholder) => true,
72
        _ => false,
73
    }
74
}
75

            
76
fn dotted_circle(c: char) -> bool {
77
    shaping_class(c) == Some(ShapingClass::DottedCircle)
78
}
79

            
80
fn register_shifter(c: char) -> bool {
81
    shaping_class(c) == Some(ShapingClass::RegisterShifter)
82
}
83

            
84
fn coeng(c: char) -> bool {
85
    shaping_class(c) == Some(ShapingClass::InvisibleStacker)
86
}
87

            
88
fn _symbol(c: char) -> bool {
89
    match shaping_class(c) {
90
        Some(ShapingClass::Symbol) => true,
91
        Some(ShapingClass::Avagraha) => true,
92
        _ => false,
93
    }
94
}
95

            
96
fn match_c<T: SyllableChar>(cs: &[T]) -> Option<usize> {
97
    match_either(
98
        match_one(consonant),
99
        match_either(match_one(ra), match_one(vowel)),
100
    )(cs)
101
}
102

            
103
fn match_n<T: SyllableChar>(cs: &[T]) -> Option<usize> {
104
    match_optional_seq(
105
        match_seq(match_optional(match_one(zwnj)), match_one(register_shifter)),
106
        match_optional(match_seq(
107
            match_one(nukta),
108
            match_optional(match_one(nukta)),
109
        )),
110
    )(cs)
111
}
112

            
113
fn match_z<T: SyllableChar>(cs: &[T]) -> Option<usize> {
114
    match_either(match_one(zwj), match_one(zwnj))(cs)
115
}
116

            
117
fn match_cn<T: SyllableChar>(cs: &[T]) -> Option<usize> {
118
    match_seq(match_c, match_optional(match_n))(cs)
119
}
120

            
121
fn match_matra_group<T: SyllableChar>(cs: &[T]) -> Option<usize> {
122
    match_optional_seq(
123
        match_z,
124
        match_seq(match_one(matra), match_optional(match_n)),
125
    )(cs)
126
}
127

            
128
fn match_syllable_tail<T: SyllableChar>(cs: &[T]) -> Option<usize> {
129
    match_optional(match_seq(
130
        match_one(syllable_modifier),
131
        match_optional(match_one(syllable_modifier)),
132
    ))(cs)
133
}
134

            
135
fn match_partial_cluster<T: SyllableChar>(cs: &[T]) -> Option<usize> {
136
    match_optional_seq(
137
        match_n,
138
        match_repeat_upto(
139
            4,
140
            match_seq(match_one(coeng), match_cn),
141
            match_repeat_upto(
142
                4,
143
                match_matra_group,
144
                match_optional_seq(match_seq(match_one(coeng), match_cn), match_syllable_tail),
145
            ),
146
        ),
147
    )(cs)
148
}
149

            
150
fn match_valid_syllable<T: SyllableChar>(cs: &[T]) -> Option<usize> {
151
    match_seq(
152
        match_either(
153
            match_c,
154
            match_either(match_one(placeholder), match_one(dotted_circle)),
155
        ),
156
        match_partial_cluster,
157
    )(cs)
158
}
159

            
160
fn match_syllable<T: SyllableChar>(cs: &[T]) -> Option<(usize, Syllable)> {
161
    match match_valid_syllable(cs) {
162
        Some(len) => Some((len, Syllable::Valid)),
163
        None => match match_partial_cluster(cs) {
164
            // The entire partial cluster is optional, which can lead to zero-
165
            // length matches. Categorise these as invalid syllables instead.
166
            Some(len) if len > 0 => Some((len, Syllable::Broken)),
167
            _ => None,
168
        },
169
    }
170
}
171

            
172
#[derive(Copy, Clone, Debug)]
173
enum Syllable {
174
    Valid,
175
    Broken,
176
}
177

            
178
#[allow(dead_code)]
179
#[derive(Copy, Clone, Debug, PartialEq)]
180
enum ShapingClass {
181
    Avagraha,
182
    Bindu,
183
    Cantillation,
184
    Consonant,
185
    ConsonantDead,
186
    ConsonantKiller,
187
    ConsonantMedial,
188
    ConsonantPlaceholder,
189
    ConsonantPostRepha,
190
    ConsonantPreRepha,
191
    ConsonantWithStacker,
192
    DottedCircle,
193
    GeminationMark,
194
    InvisibleStacker,
195
    Joiner,
196
    ModifyingLetter,
197
    NonJoiner,
198
    Nukta,
199
    Number,
200
    Placeholder,
201
    PureKiller,
202
    RegisterShifter,
203
    SyllableModifier,
204
    Symbol,
205
    Virama,
206
    Visarga,
207
    VowelDependent,
208
    VowelIndependent,
209
}
210

            
211
#[allow(dead_code)]
212
#[derive(Copy, Clone, Debug, PartialEq)]
213
enum MarkPlacementSubclass {
214
    Bottom,
215
    Left,
216
    LeftAndRight,
217
    Overstruck,
218
    Right,
219
    Top,
220
    TopAndBottom,
221
    TopAndLeft,
222
    TopAndLeftAndRight,
223
    TopAndRight,
224
}
225

            
226
#[derive(Copy, Clone, Debug, PartialEq)]
227
enum BasicFeature {
228
    Locl,
229
    Ccmp,
230
    Pref,
231
    Blwf,
232
    Abvf,
233
    Pstf,
234
    Cfar,
235
}
236

            
237
impl BasicFeature {
238
    const ALL: &'static [BasicFeature] = &[
239
        BasicFeature::Locl,
240
        BasicFeature::Ccmp,
241
        BasicFeature::Pref,
242
        BasicFeature::Blwf,
243
        BasicFeature::Abvf,
244
        BasicFeature::Pstf,
245
        BasicFeature::Cfar,
246
    ];
247

            
248
    fn feature(self) -> Feature {
249
        match self {
250
            BasicFeature::Locl => Feature::LOCL,
251
            BasicFeature::Ccmp => Feature::CCMP,
252
            BasicFeature::Pref => Feature::PREF,
253
            BasicFeature::Blwf => Feature::BLWF,
254
            BasicFeature::Abvf => Feature::ABVF,
255
            BasicFeature::Pstf => Feature::PSTF,
256
            BasicFeature::Cfar => Feature::CFAR,
257
        }
258
    }
259

            
260
    fn is_global(self) -> bool {
261
        match self {
262
            BasicFeature::Locl => true,
263
            BasicFeature::Ccmp => true,
264
            BasicFeature::Pref => false,
265
            BasicFeature::Blwf => false,
266
            BasicFeature::Abvf => false,
267
            BasicFeature::Pstf => true,
268
            BasicFeature::Cfar => false,
269
        }
270
    }
271
}
272

            
273
pub(super) fn preprocess_khmer(cs: &mut Vec<char>) {
274
    decompose_matra(cs);
275
    sort_by_modified_combining_class(cs);
276
}
277

            
278
fn decompose_matra(cs: &mut Vec<char>) {
279
    let mut i = 0;
280
    while i < cs.len() {
281
        match cs[i] {
282
            '\u{17BE}' | '\u{17BF}' | '\u{17C0}' | '\u{17C4}' | '\u{17C5}' => {
283
                cs.insert(i, '\u{17C1}');
284
                i += 2;
285
            }
286
            _ => i += 1,
287
        }
288
    }
289
}
290

            
291
#[derive(Copy, Clone, Debug)]
292
struct KhmerData {
293
    mask: FeatureMask,
294
}
295

            
296
impl GlyphData for KhmerData {
297
    fn merge(d1: KhmerData, _d2: KhmerData) -> KhmerData {
298
        d1
299
    }
300
}
301

            
302
type RawGlyphKhmer = RawGlyph<KhmerData>;
303

            
304
impl RawGlyphKhmer {
305
    fn is(&self, f: impl FnOnce(char) -> bool) -> bool {
306
        match self.glyph_origin {
307
            GlyphOrigin::Char(c) => f(c),
308
            GlyphOrigin::Direct => false,
309
        }
310
    }
311

            
312
    fn has_mask(&self, mask: FeatureMask) -> bool {
313
        self.extra_data.mask.contains(mask)
314
    }
315

            
316
    fn add_mask(&mut self, mask: FeatureMask) {
317
        self.extra_data.mask.insert(mask)
318
    }
319
}
320

            
321
pub fn gsub_apply_khmer(
322
    dotted_circle_index: u16,
323
    gsub_cache: &LayoutCache<GSUB>,
324
    gsub_table: &LayoutTable<GSUB>,
325
    gdef_table: Option<&GDEFTable>,
326
    script_tag: u32,
327
    lang_tag: Option<u32>,
328
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
329
    extra_features: FeatureMask,
330
    glyphs: &mut Vec<RawGlyph<()>>,
331
) -> Result<(), ShapingError> {
332
    let mut syllables = to_khmer_syllables(dotted_circle_index, glyphs);
333

            
334
    for (syllable, syllable_type) in syllables.iter_mut() {
335
        shape_syllable(
336
            gsub_cache,
337
            gsub_table,
338
            gdef_table,
339
            script_tag,
340
            lang_tag,
341
            feature_variations,
342
            extra_features,
343
            syllable,
344
            *syllable_type,
345
        )?;
346
    }
347

            
348
    *glyphs = syllables
349
        .into_iter()
350
        .flat_map(|s| s.0)
351
        .map(from_raw_glyph_khmer)
352
        .collect();
353

            
354
    Ok(())
355
}
356

            
357
fn to_khmer_syllables(
358
    dotted_circle_index: u16,
359
    mut glyphs: &[RawGlyph<()>],
360
) -> Vec<(Vec<RawGlyphKhmer>, Syllable)> {
361
    let mut syllables: Vec<(Vec<RawGlyphKhmer>, Syllable)> = Vec::new();
362

            
363
    while !glyphs.is_empty() {
364
        match match_syllable(glyphs) {
365
            Some((len, syllable_type)) => {
366
                let mut syllable;
367
                match syllable_type {
368
                    Syllable::Valid => {
369
                        syllable = glyphs[..len].iter().map(to_raw_glyph_khmer).collect();
370
                    }
371
                    Syllable::Broken => {
372
                        // Prepend a dotted circle to a broken syllable, then treat it as valid.
373
                        syllable = Vec::with_capacity(len + 1);
374
                        insert_dotted_circle(dotted_circle_index, &mut syllable);
375
                        syllable.extend(glyphs[..len].iter().map(to_raw_glyph_khmer));
376
                    }
377
                }
378
                syllables.push((syllable, Syllable::Valid));
379
                glyphs = &glyphs[len..];
380
            }
381
            None => {
382
                let invalid_glyph = to_raw_glyph_khmer(&glyphs[0]);
383
                match syllables.last_mut() {
384
                    // Append invalid glyph to last syllable if syllable is invalid.
385
                    Some((invalid_syllable, Syllable::Broken)) => {
386
                        invalid_syllable.push(invalid_glyph)
387
                    }
388
                    _ => syllables.push((vec![invalid_glyph], Syllable::Broken)),
389
                }
390
                glyphs = &glyphs[1..];
391
            }
392
        }
393
    }
394

            
395
    syllables
396
}
397

            
398
fn insert_dotted_circle(dotted_circle_index: u16, glyphs: &mut Vec<RawGlyphKhmer>) {
399
    if dotted_circle_index == 0 {
400
        return;
401
    }
402
    let dotted_circle = RawGlyphKhmer {
403
        unicodes: tiny_vec![[char; 1] => DOTTED_CIRCLE],
404
        glyph_index: dotted_circle_index,
405
        liga_component_pos: 0,
406
        glyph_origin: GlyphOrigin::Char(DOTTED_CIRCLE),
407
        flags: RawGlyphFlags::empty(),
408
        variation: None,
409
        extra_data: KhmerData {
410
            mask: FeatureMask::empty(),
411
        },
412
    };
413
    glyphs.insert(0, dotted_circle);
414
}
415

            
416
fn shape_syllable(
417
    gsub_cache: &LayoutCache<GSUB>,
418
    gsub_table: &LayoutTable<GSUB>,
419
    gdef_table: Option<&GDEFTable>,
420
    script_tag: u32,
421
    lang_tag: Option<u32>,
422
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
423
    extra_features: FeatureMask,
424
    syllable: &mut Vec<RawGlyphKhmer>,
425
    syllable_type: Syllable,
426
) -> Result<(), ShapingError> {
427
    let max_glyphs = syllable.len().saturating_mul(gsub::MAX_GLYPHS_FACTOR);
428

            
429
    match syllable_type {
430
        Syllable::Valid => {
431
            reorder_and_mask_syllable(syllable)?;
432
            apply_basic_features(
433
                gsub_cache,
434
                gsub_table,
435
                gdef_table,
436
                script_tag,
437
                lang_tag,
438
                feature_variations,
439
                syllable,
440
                max_glyphs,
441
            )?;
442
            apply_remaining_features(
443
                gsub_cache,
444
                gsub_table,
445
                gdef_table,
446
                script_tag,
447
                lang_tag,
448
                feature_variations,
449
                extra_features,
450
                syllable,
451
                max_glyphs,
452
            )?;
453
        }
454
        Syllable::Broken => {}
455
    }
456

            
457
    Ok(())
458
}
459

            
460
fn reorder_and_mask_syllable(glyphs: &mut [RawGlyphKhmer]) -> Result<(), ShapingError> {
461
    let mut base_i = match glyphs.iter().position(|g| g.is(base_candidate)) {
462
        Some(i) => i,
463
        None => return Ok(()),
464
    };
465

            
466
    if let Some(ra_i) = glyphs[(base_i + 1)..].iter().position(|g| g.is(ra)) {
467
        let ra_i = ra_i + base_i + 1;
468
        // CFAR is applied to glyphs occurring after a (Sign Coeng, Ro) sequence.
469
        glyphs[(ra_i + 1)..]
470
            .iter_mut()
471
            .for_each(|g| g.add_mask(BasicFeature::Cfar.feature().mask()));
472
        // A (Sign Coeng, Ro) sequence is reordered to before the base consonant.
473
        // (The syllable matcher should ensure that a Sign Coeng precedes a Ro.)
474
        glyphs[base_i..=ra_i].rotate_right(2);
475
        base_i += 2;
476
        glyphs[base_i - 1].add_mask(BasicFeature::Pref.feature().mask());
477
        glyphs[base_i - 2].add_mask(BasicFeature::Pref.feature().mask());
478
    }
479

            
480
    let post_base_masks =
481
        BasicFeature::Blwf.feature() | BasicFeature::Abvf.feature() | BasicFeature::Pstf.feature();
482
    glyphs[(base_i + 1)..]
483
        .iter_mut()
484
        .for_each(|g| g.add_mask(post_base_masks));
485

            
486
    fn left_matra(c: char) -> bool {
487
        matra(c) && mark_placement(c) == Some(MarkPlacementSubclass::Left)
488
    }
489

            
490
    // Reorder a left matra to the start of the syllable. Consistent with Uniscribe.
491
    // HarfBuzz's reordering depends on the left matra's initial position relative to
492
    // the initial (Sign Coeng, Ro) position. Example:
493
    //     U+1780, U+17C1, U+17D2, U+179A (occurs before Coeng, Ro; reordered after Coeng, Ro)
494
    //     U+1780, U+17D2, U+179A, U+17C1 (occurs after Coeng, Ro; reordered before Coeng, Ro)
495
    // See: https://github.com/harfbuzz/harfbuzz/commit/1a96cc825dc9.
496
    if let Some(left_matra_i) = glyphs.iter().position(|g| g.is(left_matra)) {
497
        glyphs[..=left_matra_i].rotate_right(1);
498
        // base_i += 1; (Not required for now.)
499
    }
500

            
501
    Ok(())
502
}
503

            
504
fn apply_basic_features(
505
    gsub_cache: &LayoutCache<GSUB>,
506
    gsub_table: &LayoutTable<GSUB>,
507
    gdef_table: Option<&GDEFTable>,
508
    script_tag: u32,
509
    lang_tag: Option<u32>,
510
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
511
    glyphs: &mut Vec<RawGlyphKhmer>,
512
    max_glyphs: usize,
513
) -> Result<(), ParseError> {
514
    // Apply features in _lookup_ order. HarfBuzz believes that Uniscribe does this. In our
515
    // corpus tests, this fixes issues with certain fonts (Battambang and Khmer UI) without
516
    // causing regressions in others.
517
    let features = BasicFeature::ALL
518
        .iter()
519
        .fold(FeatureMask::empty(), |acc, f| acc | f.feature());
520
    let index = gsub::get_lookups_cache_index(
521
        gsub_cache,
522
        script_tag,
523
        lang_tag,
524
        feature_variations,
525
        features,
526
    )?;
527
    let lookups = &gsub_cache.cached_lookups.lock().unwrap()[index];
528

            
529
    for &(lookup_index, feature_tag) in lookups {
530
        let feature = match feature_tag {
531
            tag::LOCL => BasicFeature::Locl,
532
            tag::CCMP => BasicFeature::Ccmp,
533
            tag::PREF => BasicFeature::Pref,
534
            tag::BLWF => BasicFeature::Blwf,
535
            tag::ABVF => BasicFeature::Abvf,
536
            tag::PSTF => BasicFeature::Pstf,
537
            tag::CFAR => BasicFeature::Cfar,
538
            _ => panic!("unexpected feature tag"), // Should never happen.
539
        };
540
        gsub::gsub_apply_lookup(
541
            gsub_cache,
542
            gsub_table,
543
            gdef_table,
544
            lookup_index,
545
            feature_tag,
546
            None,
547
            glyphs,
548
            max_glyphs,
549
            0,
550
            glyphs.len(),
551
            |g| feature.is_global() || g.has_mask(feature.feature().mask()),
552
        )?;
553
    }
554

            
555
    Ok(())
556
}
557

            
558
fn apply_remaining_features(
559
    gsub_cache: &LayoutCache<GSUB>,
560
    gsub_table: &LayoutTable<GSUB>,
561
    gdef_table: Option<&GDEFTable>,
562
    script_tag: u32,
563
    lang_tag: Option<u32>,
564
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
565
    extra_features: FeatureMask,
566
    glyphs: &mut Vec<RawGlyphKhmer>,
567
    max_glyphs: usize,
568
) -> Result<(), ParseError> {
569
    let features = Feature::ABVS
570
        | Feature::BLWS
571
        | Feature::CALT
572
        | Feature::CLIG
573
        | Feature::LIGA
574
        | Feature::PRES
575
        | Feature::PSTS
576
        | extra_features;
577

            
578
    let index = gsub::get_lookups_cache_index(
579
        gsub_cache,
580
        script_tag,
581
        lang_tag,
582
        feature_variations,
583
        features,
584
    )?;
585
    let lookups = &gsub_cache.cached_lookups.lock().unwrap()[index];
586

            
587
    for &(lookup_index, feature_tag) in lookups {
588
        gsub::gsub_apply_lookup(
589
            gsub_cache,
590
            gsub_table,
591
            gdef_table,
592
            lookup_index,
593
            feature_tag,
594
            None,
595
            glyphs,
596
            max_glyphs,
597
            0,
598
            glyphs.len(),
599
            |_| true,
600
        )?;
601
    }
602

            
603
    Ok(())
604
}
605

            
606
fn base_candidate(c: char) -> bool {
607
    consonant(c) || ra(c) || vowel(c) || placeholder(c) || dotted_circle(c)
608
}
609

            
610
fn to_raw_glyph_khmer(g: &RawGlyph<()>) -> RawGlyphKhmer {
611
    RawGlyphKhmer {
612
        unicodes: g.unicodes.clone(),
613
        glyph_index: g.glyph_index,
614
        liga_component_pos: g.liga_component_pos,
615
        glyph_origin: g.glyph_origin,
616
        flags: g.flags,
617
        variation: g.variation,
618
        extra_data: KhmerData {
619
            mask: FeatureMask::empty(),
620
        },
621
    }
622
}
623

            
624
fn from_raw_glyph_khmer(g: RawGlyphKhmer) -> RawGlyph<()> {
625
    RawGlyph {
626
        unicodes: g.unicodes,
627
        glyph_index: g.glyph_index,
628
        liga_component_pos: g.liga_component_pos,
629
        glyph_origin: g.glyph_origin,
630
        flags: g.flags,
631
        variation: g.variation,
632
        extra_data: (),
633
    }
634
}
635

            
636
fn khmer_character(c: char) -> (Option<ShapingClass>, Option<MarkPlacementSubclass>) {
637
    use MarkPlacementSubclass::*;
638
    use ShapingClass::*;
639

            
640
    match c as u32 {
641
        0x1780 => (Some(Consonant), None),                          // Ka
642
        0x1781 => (Some(Consonant), None),                          // Kha
643
        0x1782 => (Some(Consonant), None),                          // Ko
644
        0x1783 => (Some(Consonant), None),                          // Kho
645
        0x1784 => (Some(Consonant), None),                          // Ngo
646
        0x1785 => (Some(Consonant), None),                          // Ca
647
        0x1786 => (Some(Consonant), None),                          // Cha
648
        0x1787 => (Some(Consonant), None),                          // Co
649
        0x1788 => (Some(Consonant), None),                          // Cho
650
        0x1789 => (Some(Consonant), None),                          // Nyo
651
        0x178A => (Some(Consonant), None),                          // Da
652
        0x178B => (Some(Consonant), None),                          // Ttha
653
        0x178C => (Some(Consonant), None),                          // Do
654
        0x178D => (Some(Consonant), None),                          // Ttho
655
        0x178E => (Some(Consonant), None),                          // Nno
656
        0x178F => (Some(Consonant), None),                          // Ta
657
        0x1790 => (Some(Consonant), None),                          // Tha
658
        0x1791 => (Some(Consonant), None),                          // To
659
        0x1792 => (Some(Consonant), None),                          // Tho
660
        0x1793 => (Some(Consonant), None),                          // No
661
        0x1794 => (Some(Consonant), None),                          // Ba
662
        0x1795 => (Some(Consonant), None),                          // Pha
663
        0x1796 => (Some(Consonant), None),                          // Po
664
        0x1797 => (Some(Consonant), None),                          // Pho
665
        0x1798 => (Some(Consonant), None),                          // Mo
666
        0x1799 => (Some(Consonant), None),                          // Yo
667
        0x179A => (Some(Consonant), None),                          // Ro
668
        0x179B => (Some(Consonant), None),                          // Lo
669
        0x179C => (Some(Consonant), None),                          // Vo
670
        0x179D => (Some(Consonant), None),                          // Sha
671
        0x179E => (Some(Consonant), None),                          // Sso
672
        0x179F => (Some(Consonant), None),                          // Sa
673
        0x17A0 => (Some(Consonant), None),                          // Ha
674
        0x17A1 => (Some(Consonant), None),                          // La
675
        0x17A2 => (Some(Consonant), None),                          // Qa
676
        0x17A3 => (Some(VowelIndependent), None),                   // Qaq
677
        0x17A4 => (Some(VowelIndependent), None),                   // Qaa
678
        0x17A5 => (Some(VowelIndependent), None),                   // Qi
679
        0x17A6 => (Some(VowelIndependent), None),                   // Qii
680
        0x17A7 => (Some(VowelIndependent), None),                   // Qu
681
        0x17A8 => (Some(VowelIndependent), None),                   // Quk
682
        0x17A9 => (Some(VowelIndependent), None),                   // Quu
683
        0x17AA => (Some(VowelIndependent), None),                   // Quuv
684
        0x17AB => (Some(VowelIndependent), None),                   // Ry
685
        0x17AC => (Some(VowelIndependent), None),                   // Ryy
686
        0x17AD => (Some(VowelIndependent), None),                   // Ly
687
        0x17AE => (Some(VowelIndependent), None),                   // Lyy
688
        0x17AF => (Some(VowelIndependent), None),                   // Qe
689
        0x17B0 => (Some(VowelIndependent), None),                   // Qai
690
        0x17B1 => (Some(VowelIndependent), None),                   // Qoo Type One
691
        0x17B2 => (Some(VowelIndependent), None),                   // Qoo Type Two
692
        0x17B3 => (Some(VowelIndependent), None),                   // Qau
693
        0x17B4 => (None, None),                                     // Inherent Aq
694
        0x17B5 => (None, None),                                     // Inherent Aa
695
        0x17B6 => (Some(VowelDependent), Some(Right)),              // Sign Aa
696
        0x17B7 => (Some(VowelDependent), Some(Top)),                // Sign I
697
        0x17B8 => (Some(VowelDependent), Some(Top)),                // Sign Ii
698
        0x17B9 => (Some(VowelDependent), Some(Top)),                // Sign Y
699
        0x17BA => (Some(VowelDependent), Some(Top)),                // Sign Yy
700
        0x17BB => (Some(VowelDependent), Some(Bottom)),             // Sign U
701
        0x17BC => (Some(VowelDependent), Some(Bottom)),             // Sign Uu
702
        0x17BD => (Some(VowelDependent), Some(Bottom)),             // Sign Ua
703
        0x17BE => (Some(VowelDependent), Some(TopAndLeft)),         // Sign Oe
704
        0x17BF => (Some(VowelDependent), Some(TopAndLeftAndRight)), // Sign Ya
705
        0x17C0 => (Some(VowelDependent), Some(LeftAndRight)),       // Sign Ie
706
        0x17C1 => (Some(VowelDependent), Some(Left)),               // Sign E
707
        0x17C2 => (Some(VowelDependent), Some(Left)),               // Sign Ae
708
        0x17C3 => (Some(VowelDependent), Some(Left)),               // Sign Ai
709
        0x17C4 => (Some(VowelDependent), Some(LeftAndRight)),       // Sign Oo
710
        0x17C5 => (Some(VowelDependent), Some(LeftAndRight)),       // Sign Au
711
        0x17C6 => (Some(Nukta), Some(Top)),                         // Nikahit
712
        0x17C7 => (Some(Visarga), Some(Right)),                     // Reahmuk
713
        0x17C8 => (Some(VowelDependent), Some(Right)),              // Yuukaleapintu
714
        0x17C9 => (Some(RegisterShifter), Some(Top)),               // Muusikatoan
715
        0x17CA => (Some(RegisterShifter), Some(Top)),               // Triisap
716
        0x17CB => (Some(SyllableModifier), Some(Top)),              // Bantoc
717
        0x17CC => (Some(ConsonantPostRepha), Some(Top)),            // Robat
718
        0x17CD => (Some(ConsonantKiller), Some(Top)),               // Toandakhiat
719
        0x17CE => (Some(SyllableModifier), Some(Top)),              // Kakabat
720
        0x17CF => (Some(SyllableModifier), Some(Top)),              // Ahsda
721
        0x17D0 => (Some(SyllableModifier), Some(Top)),              // Samyok Sannya
722
        0x17D1 => (Some(PureKiller), Some(Top)),                    // Viriam
723
        0x17D2 => (Some(InvisibleStacker), None),                   // Sign Coeng
724
        0x17D3 => (Some(SyllableModifier), Some(Top)),              // Bathamasat
725
        0x17D4 => (None, None),                                     // Khan
726
        0x17D5 => (None, None),                                     // Bariyoosan
727
        0x17D6 => (None, None),                                     // Camnuc Pii Kuuh
728
        0x17D7 => (None, None),                                     // Lek Too
729
        0x17D8 => (None, None),                                     // Beyyal
730
        0x17D9 => (None, None),                                     // Phnaek Muan
731
        0x17DA => (None, None),                                     // Koomuut
732
        0x17DB => (Some(Symbol), None),                             // Riel
733
        0x17DC => (Some(Avagraha), None),                           // Avakrahasanya
734
        0x17DD => (Some(SyllableModifier), Some(Top)),              // Atthacan
735
        0x17E0 => (Some(Number), None),                             // Digit Zero
736
        0x17E1 => (Some(Number), None),                             // Digit One
737
        0x17E2 => (Some(Number), None),                             // Digit Two
738
        0x17E3 => (Some(Number), None),                             // Digit Three
739
        0x17E4 => (Some(Number), None),                             // Digit Four
740
        0x17E5 => (Some(Number), None),                             // Digit Five
741
        0x17E6 => (Some(Number), None),                             // Digit Six
742
        0x17E7 => (Some(Number), None),                             // Digit Seven
743
        0x17E8 => (Some(Number), None),                             // Digit Eight
744
        0x17E9 => (Some(Number), None),                             // Digit Nine
745
        0x17F0 => (None, None),                                     // Lek Attak Son
746
        0x17F1 => (None, None),                                     // Lek Attak Muoy
747
        0x17F2 => (None, None),                                     // Lek Attak Pii
748
        0x17F3 => (None, None),                                     // Lek Attak Bei
749
        0x17F4 => (None, None),                                     // Lek Attak Buon
750
        0x17F5 => (None, None),                                     // Lek Attak Pram
751
        0x17F6 => (None, None),                                     // Lek Attak Pram-Muoy
752
        0x17F7 => (None, None),                                     // Lek Attak Pram-Pii
753
        0x17F8 => (None, None),                                     // Lek Attak Pram-Bei
754
        0x17F9 => (None, None),                                     // Lek Attak Pram-Buon
755

            
756
        // Khmer symbols character table.
757
        0x19E0 => (None, None), // Pathamasat
758
        0x19E1 => (None, None), // Muoy Koet
759
        0x19E2 => (None, None), // Pii Koet
760
        0x19E3 => (None, None), // Bei Koet
761
        0x19E4 => (None, None), // Buon Koet
762
        0x19E5 => (None, None), // Pram Koet
763
        0x19E6 => (None, None), // Pram-Muoy Koet
764
        0x19E7 => (None, None), // Pram-Pii Koet
765
        0x19E8 => (None, None), // Pram-Bei Koet
766
        0x19E9 => (None, None), // Pram-Buon Koet
767
        0x19EA => (None, None), // Dap Koet
768
        0x19EB => (None, None), // Dap-Muoy Koet
769
        0x19EC => (None, None), // Dap-Pii Koet
770
        0x19ED => (None, None), // Dap-Bei Koet
771
        0x19EE => (None, None), // Dap-Buon Koet
772
        0x19EF => (None, None), // Dap-Pram Koet
773
        0x19F0 => (None, None), // Tuteyasat
774
        0x19F1 => (None, None), // Muoy ROC
775
        0x19F2 => (None, None), // Pii Roc
776
        0x19F3 => (None, None), // Bei Roc
777
        0x19F4 => (None, None), // Buon Roc
778
        0x19F5 => (None, None), // Pram Roc
779
        0x19F6 => (None, None), // Pram-Muoy Roc
780
        0x19F7 => (None, None), // Pram-Pii Roc
781
        0x19F8 => (None, None), // Pram-Bei Roc
782
        0x19F9 => (None, None), // Pram-Buon Roc
783
        0x19FA => (None, None), // Dap Roc
784
        0x19FB => (None, None), // Dap-Muoy Roc
785
        0x19FC => (None, None), // Dap-Pii Roc
786
        0x19FD => (None, None), // Dap-Bei Roc
787
        0x19FE => (None, None), // Dap-Buon Roc
788
        0x19FF => (None, None), // Dap-Pram Roc
789

            
790
        // Miscellaneous character table.
791
        0x00A0 => (Some(Placeholder), None),  // No-break space
792
        0x200C => (Some(NonJoiner), None),    // Zero-width non-joiner
793
        0x200D => (Some(Joiner), None),       // Zero-width joiner
794
        0x2010 => (Some(Placeholder), None),  // Hyphen
795
        0x2011 => (Some(Placeholder), None),  // No-break hyphen
796
        0x2012 => (Some(Placeholder), None),  // Figure dash
797
        0x2013 => (Some(Placeholder), None),  // En dash
798
        0x2014 => (Some(Placeholder), None),  // Em dash
799
        0x25CC => (Some(DottedCircle), None), // Dotted circle
800

            
801
        _ => (None, None),
802
    }
803
}
804

            
805
#[cfg(test)]
806
mod tests {
807
    use super::*;
808

            
809
    mod decompose_matra {
810
        use super::*;
811

            
812
        #[test]
813
        fn test_decomposition1() {
814
            let mut cs = vec!['\u{17C0}'];
815
            decompose_matra(&mut cs);
816

            
817
            assert_eq!(vec!['\u{17C1}', '\u{17C0}'], cs);
818
        }
819

            
820
        #[test]
821
        fn test_decomposition2() {
822
            let mut cs = vec!['\u{17C0}', '\u{17C0}'];
823
            decompose_matra(&mut cs);
824

            
825
            assert_eq!(vec!['\u{17C1}', '\u{17C0}', '\u{17C1}', '\u{17C0}'], cs);
826
        }
827
    }
828
}