1
//! Implementation of font shaping for the Tibetan script.
2
//!
3
//! Tibetan doesn't need syllable identification or reordering — subjoined consonants have
4
//! separate Unicode codepoints (U+0F90–U+0FBC). Preprocessing decomposes deprecated compound
5
//! vowels and sorts marks by modified combining class.
6

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

            
12
/// Decompose deprecated Tibetan compound vowels, then sort marks by modified combining class.
13
pub fn preprocess_tibetan(cs: &mut Vec<char>) {
14
    decompose_tibetan(cs);
15
    sort_by_modified_combining_class(cs);
16
}
17

            
18
/// Decompose deprecated Tibetan compound vowels into their constituent parts.
19
///
20
/// Reference: Unicode Standard, Chapter 13.4 (Tibetan), Table 13-3.
21
fn decompose_tibetan(cs: &mut Vec<char>) {
22
    let mut i = 0;
23
    while i < cs.len() {
24
        match cs[i] {
25
            // U+0F73 TIBETAN VOWEL SIGN II → U+0F71 + U+0F72
26
            '\u{0F73}' => {
27
                cs[i] = '\u{0F71}';
28
                i += 1;
29
                cs.insert(i, '\u{0F72}');
30
            }
31
            // U+0F75 TIBETAN VOWEL SIGN UU → U+0F71 + U+0F74
32
            '\u{0F75}' => {
33
                cs[i] = '\u{0F71}';
34
                i += 1;
35
                cs.insert(i, '\u{0F74}');
36
            }
37
            // U+0F77 TIBETAN VOWEL SIGN VOCALIC RR → U+0FB2 + U+0F71 + U+0F80
38
            '\u{0F77}' => {
39
                cs[i] = '\u{0FB2}';
40
                i += 1;
41
                cs.insert(i, '\u{0F71}');
42
                i += 1;
43
                cs.insert(i, '\u{0F80}');
44
            }
45
            // U+0F79 TIBETAN VOWEL SIGN VOCALIC LL → U+0FB3 + U+0F71 + U+0F80
46
            '\u{0F79}' => {
47
                cs[i] = '\u{0FB3}';
48
                i += 1;
49
                cs.insert(i, '\u{0F71}');
50
                i += 1;
51
                cs.insert(i, '\u{0F80}');
52
            }
53
            // U+0F81 TIBETAN VOWEL SIGN REVERSED II → U+0F71 + U+0F80
54
            '\u{0F81}' => {
55
                cs[i] = '\u{0F71}';
56
                i += 1;
57
                cs.insert(i, '\u{0F80}');
58
            }
59
            _ => {}
60
        }
61
        i += 1;
62
    }
63
}
64

            
65
fn apply_features(
66
    gsub_cache: &LayoutCache<GSUB>,
67
    gsub_table: &LayoutTable<GSUB>,
68
    gdef_table: Option<&GDEFTable>,
69
    script_tag: u32,
70
    lang_tag: Option<u32>,
71
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
72
    features: FeatureMask,
73
    glyphs: &mut Vec<RawGlyph<()>>,
74
    max_glyphs: usize,
75
) -> Result<(), ShapingError> {
76
    let index = gsub::get_lookups_cache_index(
77
        gsub_cache,
78
        script_tag,
79
        lang_tag,
80
        feature_variations,
81
        features,
82
    )?;
83
    let lookups = &gsub_cache.cached_lookups.lock().unwrap()[index];
84
    for &(lookup_index, feature_tag) in lookups {
85
        gsub::gsub_apply_lookup(
86
            gsub_cache,
87
            gsub_table,
88
            gdef_table,
89
            lookup_index,
90
            feature_tag,
91
            None,
92
            glyphs,
93
            max_glyphs,
94
            0,
95
            glyphs.len(),
96
            |_| true,
97
        )?;
98
    }
99
    Ok(())
100
}
101

            
102
pub fn gsub_apply_tibetan(
103
    gsub_cache: &LayoutCache<GSUB>,
104
    gsub_table: &LayoutTable<GSUB>,
105
    gdef_table: Option<&GDEFTable>,
106
    script_tag: u32,
107
    lang_tag: Option<u32>,
108
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
109
    extra_features: FeatureMask,
110
    glyphs: &mut Vec<RawGlyph<()>>,
111
    max_glyphs: usize,
112
) -> Result<(), ShapingError> {
113
    // Stage 1: Language forms
114
    let stage1 = Feature::LOCL | Feature::CCMP;
115
    apply_features(
116
        gsub_cache,
117
        gsub_table,
118
        gdef_table,
119
        script_tag,
120
        lang_tag,
121
        feature_variations,
122
        stage1,
123
        glyphs,
124
        max_glyphs,
125
    )?;
126

            
127
    // Stage 2: Conjuncts and typographical forms
128
    let stage2 = Feature::ABVS | Feature::BLWS | Feature::CALT | Feature::LIGA | extra_features;
129
    apply_features(
130
        gsub_cache,
131
        gsub_table,
132
        gdef_table,
133
        script_tag,
134
        lang_tag,
135
        feature_variations,
136
        stage2,
137
        glyphs,
138
        max_glyphs,
139
    )?;
140

            
141
    Ok(())
142
}
143

            
144
#[cfg(test)]
145
mod tests {
146
    use super::*;
147

            
148
    #[test]
149
    fn test_decompose_0f73() {
150
        let mut cs = vec!['\u{0F40}', '\u{0F73}'];
151
        decompose_tibetan(&mut cs);
152
        assert_eq!(cs, vec!['\u{0F40}', '\u{0F71}', '\u{0F72}']);
153
    }
154

            
155
    #[test]
156
    fn test_decompose_0f75() {
157
        let mut cs = vec!['\u{0F40}', '\u{0F75}'];
158
        decompose_tibetan(&mut cs);
159
        assert_eq!(cs, vec!['\u{0F40}', '\u{0F71}', '\u{0F74}']);
160
    }
161

            
162
    #[test]
163
    fn test_decompose_0f77() {
164
        let mut cs = vec!['\u{0F40}', '\u{0F77}'];
165
        decompose_tibetan(&mut cs);
166
        assert_eq!(cs, vec!['\u{0F40}', '\u{0FB2}', '\u{0F71}', '\u{0F80}']);
167
    }
168

            
169
    #[test]
170
    fn test_decompose_0f79() {
171
        let mut cs = vec!['\u{0F40}', '\u{0F79}'];
172
        decompose_tibetan(&mut cs);
173
        assert_eq!(cs, vec!['\u{0F40}', '\u{0FB3}', '\u{0F71}', '\u{0F80}']);
174
    }
175

            
176
    #[test]
177
    fn test_decompose_0f81() {
178
        let mut cs = vec!['\u{0F40}', '\u{0F81}'];
179
        decompose_tibetan(&mut cs);
180
        assert_eq!(cs, vec!['\u{0F40}', '\u{0F71}', '\u{0F80}']);
181
    }
182

            
183
    #[test]
184
    fn test_no_decompose() {
185
        let mut cs = vec!['\u{0F40}', '\u{0F72}'];
186
        let expected = cs.clone();
187
        decompose_tibetan(&mut cs);
188
        assert_eq!(cs, expected);
189
    }
190

            
191
    #[test]
192
    fn test_multiple_decompositions() {
193
        let mut cs = vec!['\u{0F40}', '\u{0F73}', '\u{0F51}', '\u{0F75}'];
194
        decompose_tibetan(&mut cs);
195
        assert_eq!(
196
            cs,
197
            vec!['\u{0F40}', '\u{0F71}', '\u{0F72}', '\u{0F51}', '\u{0F71}', '\u{0F74}']
198
        );
199
    }
200
}