1
//! Implementation of font shaping for Thai and Lao scripts, following the specification at:
2
//! <https://github.com/n8willis/opentype-shaping-documents/blob/master/opentype-shaping-thai-lao.md>.
3

            
4
use crate::error::ShapingError;
5
use crate::gsub::{self, Feature, FeatureMask, RawGlyph};
6
use crate::layout::{FeatureTableSubstitution, GDEFTable, LayoutCache, LayoutTable, GSUB};
7
use crate::unicode::mcc::sort_by_modified_combining_class;
8

            
9
pub(super) fn reorder_marks(cs: &mut Vec<char>) {
10
    // U+0E4D THAI NIKHAHIT and U+0ECD LAO NIGGAHITA marks that originate from an AM vowel
11
    // must be reordered before any tone markers. NIKHAHIT and NIGGAHITA marks that do not
12
    // originate from an AM vowel should not be reordered.
13
    //
14
    // Reordering may not just be limited to tone markers, but all abovebase marks:
15
    // https://github.com/n8willis/opentype-shaping-documents/issues/125.
16
    for i in 0..cs.len() {
17
        if let Some((c1, c2)) = split_am_vowel(cs[i]) {
18
            cs[i] = c1;
19
            cs.insert(i + 1, c2);
20

            
21
            let mut j = i;
22
            while j > 0 && is_abovebase_mark(cs[j - 1]) {
23
                j -= 1;
24
            }
25
            cs[j..=i].rotate_right(1);
26
        }
27
    }
28

            
29
    // U+0E3A PHINTHU is reordered so it occurs after any U+0E38 SARA U or U+0E39 SARA UU marks.
30
    // This is done by mapping the Thai combining class `CCC103` to `CCC3`, see the `unicode::mcc`
31
    // module.
32
    sort_by_modified_combining_class(cs);
33
}
34

            
35
fn split_am_vowel(c: char) -> Option<(char, char)> {
36
    match c {
37
        // Thai
38
        '\u{0E33}' => Some(('\u{0E4D}', '\u{0E32}')),
39
        // Lao
40
        '\u{0EB3}' => Some(('\u{0ECD}', '\u{0EB2}')),
41
        _ => None,
42
    }
43
}
44

            
45
fn is_abovebase_mark(c: char) -> bool {
46
    match c {
47
        // Thai
48
        '\u{0E31}' => true,
49
        '\u{0E34}'..='\u{0E37}' => true,
50
        '\u{0E47}'..='\u{0E4E}' => true,
51
        // Lao
52
        '\u{0EB1}' => true,
53
        '\u{0EB4}'..='\u{0EB7}' => true,
54
        '\u{0EBB}' => true,
55
        '\u{0EC8}'..='\u{0ECD}' => true,
56
        _ => false,
57
    }
58
}
59

            
60
pub fn gsub_apply_thai_lao(
61
    gsub_cache: &LayoutCache<GSUB>,
62
    gsub_table: &LayoutTable<GSUB>,
63
    gdef_table: Option<&GDEFTable>,
64
    script_tag: u32,
65
    lang_tag: Option<u32>,
66
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
67
    extra_features: FeatureMask,
68
    glyphs: &mut Vec<RawGlyph<()>>,
69
    max_glyphs: usize,
70
) -> Result<(), ShapingError> {
71
    let features = Feature::LOCL
72
        | Feature::CCMP
73
        | Feature::RLIG
74
        | Feature::CLIG
75
        | Feature::LIGA
76
        | Feature::CALT;
77

            
78
    let index = gsub::get_lookups_cache_index(
79
        gsub_cache,
80
        script_tag,
81
        lang_tag,
82
        feature_variations,
83
        features | extra_features,
84
    )?;
85
    let lookups = &gsub_cache.cached_lookups.lock().unwrap()[index];
86

            
87
    for &(lookup_index, feature_tag) in lookups {
88
        gsub::gsub_apply_lookup(
89
            gsub_cache,
90
            gsub_table,
91
            gdef_table,
92
            lookup_index,
93
            feature_tag,
94
            None,
95
            glyphs,
96
            max_glyphs,
97
            0,
98
            glyphs.len(),
99
            |_| true,
100
        )?;
101
    }
102

            
103
    Ok(())
104
}
105

            
106
#[cfg(test)]
107
mod tests {
108
    use super::*;
109

            
110
    mod reorder_marks {
111
        use super::*;
112

            
113
        #[test]
114
        fn test_am1() {
115
            let mut cs = vec!['\u{0E33}'];
116
            let cs_exp = vec!['\u{0E4D}', '\u{0E32}'];
117
            reorder_marks(&mut cs);
118
            assert_eq!(cs_exp, cs);
119
        }
120

            
121
        #[test]
122
        fn test_am2() {
123
            let mut cs = vec!['\u{0E49}', '\u{0E33}'];
124
            let cs_exp = vec!['\u{0E4D}', '\u{0E49}', '\u{0E32}'];
125
            reorder_marks(&mut cs);
126
            assert_eq!(cs_exp, cs);
127
        }
128

            
129
        #[test]
130
        fn test_am3() {
131
            let mut cs = vec!['\u{0E49}', '\u{0E4D}', '\u{0E32}'];
132
            let cs_exp = cs.clone();
133
            reorder_marks(&mut cs);
134
            assert_eq!(cs_exp, cs);
135
        }
136

            
137
        #[test]
138
        fn test_am4() {
139
            let mut cs = vec!['\u{0E19}', '\u{0E49}', '\u{0E19}', '\u{0E49}', '\u{0E33}'];
140
            let cs_exp = vec![
141
                '\u{0E19}', '\u{0E49}', '\u{0E19}', '\u{0E4D}', '\u{0E49}', '\u{0E32}',
142
            ];
143
            reorder_marks(&mut cs);
144
            assert_eq!(cs_exp, cs);
145
        }
146

            
147
        #[test]
148
        fn test_am5() {
149
            let mut cs = vec![
150
                '\u{0E19}', '\u{0E49}', '\u{0E19}', '\u{0E49}', '\u{0E4D}', '\u{0E32}',
151
            ];
152
            let cs_exp = cs.clone();
153
            reorder_marks(&mut cs);
154
            assert_eq!(cs_exp, cs);
155
        }
156

            
157
        #[test]
158
        fn test_phinthu1() {
159
            let mut cs = vec!['\u{0E19}', '\u{0E38}', '\u{0E3A}'];
160
            let cs_exp = cs.clone();
161
            reorder_marks(&mut cs);
162
            assert_eq!(cs_exp, cs);
163
        }
164

            
165
        #[test]
166
        fn test_phinthu2() {
167
            let mut cs = vec!['\u{0E19}', '\u{0E3A}', '\u{0E38}'];
168
            let cs_exp = vec!['\u{0E19}', '\u{0E38}', '\u{0E3A}'];
169
            reorder_marks(&mut cs);
170
            assert_eq!(cs_exp, cs);
171
        }
172
    }
173
}