1
//! Implementation of font shaping for Mongolian script
2
//!
3
//! Code herein follows the specification at:
4
//! <https://github.com/n8willis/opentype-shaping-documents/blob/master/opentype-shaping-mongolian.md>
5

            
6
use unicode_joining_type::{get_joining_type, JoiningType};
7

            
8
use crate::error::{ParseError, ShapingError};
9
use crate::gsub::{self, Feature, FeatureMask, GlyphData, GlyphOrigin, RawGlyph};
10
use crate::layout::{FeatureTableSubstitution, GDEFTable, LayoutCache, LayoutTable, GSUB};
11
use crate::tag;
12

            
13
#[derive(Clone)]
14
struct MongolianData {
15
    joining_type: JoiningType,
16
    feature_tag: u32,
17
}
18

            
19
impl GlyphData for MongolianData {
20
    fn merge(data1: MongolianData, _data2: MongolianData) -> MongolianData {
21
        data1
22
    }
23
}
24

            
25
type MongolianGlyph = RawGlyph<MongolianData>;
26

            
27
impl MongolianGlyph {
28
    fn is_transparent(&self) -> bool {
29
        self.extra_data.joining_type == JoiningType::Transparent || self.multi_subst_dup()
30
    }
31

            
32
    fn is_left_joining(&self) -> bool {
33
        self.extra_data.joining_type == JoiningType::LeftJoining
34
            || self.extra_data.joining_type == JoiningType::DualJoining
35
            || self.extra_data.joining_type == JoiningType::JoinCausing
36
    }
37

            
38
    fn is_right_joining(&self) -> bool {
39
        self.extra_data.joining_type == JoiningType::RightJoining
40
            || self.extra_data.joining_type == JoiningType::DualJoining
41
            || self.extra_data.joining_type == JoiningType::JoinCausing
42
    }
43

            
44
    fn feature_tag(&self) -> u32 {
45
        self.extra_data.feature_tag
46
    }
47

            
48
    fn set_feature_tag(&mut self, feature_tag: u32) {
49
        self.extra_data.feature_tag = feature_tag
50
    }
51
}
52

            
53
impl From<&RawGlyph<()>> for MongolianGlyph {
54
    fn from(raw_glyph: &RawGlyph<()>) -> MongolianGlyph {
55
        let joining_type = match raw_glyph.glyph_origin {
56
            GlyphOrigin::Char(c) => get_joining_type(c),
57
            GlyphOrigin::Direct => JoiningType::NonJoining,
58
        };
59

            
60
        MongolianGlyph {
61
            unicodes: raw_glyph.unicodes.clone(),
62
            glyph_index: raw_glyph.glyph_index,
63
            liga_component_pos: raw_glyph.liga_component_pos,
64
            glyph_origin: raw_glyph.glyph_origin,
65
            flags: raw_glyph.flags,
66
            variation: raw_glyph.variation,
67
            extra_data: MongolianData {
68
                joining_type,
69
                feature_tag: tag::ISOL,
70
            },
71
        }
72
    }
73
}
74

            
75
impl From<&MongolianGlyph> for RawGlyph<()> {
76
    fn from(mongolian_glyph: &MongolianGlyph) -> RawGlyph<()> {
77
        RawGlyph {
78
            unicodes: mongolian_glyph.unicodes.clone(),
79
            glyph_index: mongolian_glyph.glyph_index,
80
            liga_component_pos: mongolian_glyph.liga_component_pos,
81
            glyph_origin: mongolian_glyph.glyph_origin,
82
            flags: mongolian_glyph.flags,
83
            variation: mongolian_glyph.variation,
84
            extra_data: (),
85
        }
86
    }
87
}
88

            
89
pub fn gsub_apply_mongolian(
90
    gsub_cache: &LayoutCache<GSUB>,
91
    gsub_table: &LayoutTable<GSUB>,
92
    gdef_table: Option<&GDEFTable>,
93
    script_tag: u32,
94
    lang_tag: Option<u32>,
95
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
96
    extra_features: FeatureMask,
97
    raw_glyphs: &mut Vec<RawGlyph<()>>,
98
    max_glyphs: usize,
99
) -> Result<(), ShapingError> {
100
    match gsub_table.find_script(script_tag)? {
101
        Some(s) => {
102
            if s.find_langsys_or_default(lang_tag)?.is_none() {
103
                return Ok(());
104
            }
105
        }
106
        None => return Ok(()),
107
    }
108

            
109
    let mongolian_glyphs = &mut raw_glyphs.iter().map(MongolianGlyph::from).collect();
110

            
111
    // 1. Compound character composition and decomposition
112

            
113
    apply_lookups(
114
        Feature::CCMP.mask(),
115
        gsub_cache,
116
        gsub_table,
117
        gdef_table,
118
        script_tag,
119
        lang_tag,
120
        feature_variations,
121
        mongolian_glyphs,
122
        max_glyphs,
123
        |_, _| true,
124
    )?;
125

            
126
    // 2. Computing letter joining states
127

            
128
    {
129
        let mut previous_i = mongolian_glyphs
130
            .iter()
131
            .position(|g| !g.is_transparent())
132
            .unwrap_or(0);
133

            
134
        for i in (previous_i + 1)..mongolian_glyphs.len() {
135
            if mongolian_glyphs[i].is_transparent() {
136
                continue;
137
            }
138

            
139
            if mongolian_glyphs[previous_i].is_left_joining()
140
                && mongolian_glyphs[i].is_right_joining()
141
            {
142
                mongolian_glyphs[i].set_feature_tag(tag::FINA);
143

            
144
                match mongolian_glyphs[previous_i].feature_tag() {
145
                    tag::ISOL => mongolian_glyphs[previous_i].set_feature_tag(tag::INIT),
146
                    tag::FINA => mongolian_glyphs[previous_i].set_feature_tag(tag::MEDI),
147
                    _ => {}
148
                }
149
            }
150

            
151
            previous_i = i;
152
        }
153
    }
154

            
155
    // 3. Applying the stch feature
156
    //
157
    // TODO hold off for future generalised solution
158

            
159
    // 4. Applying the language-form substitution features from GSUB
160

            
161
    const LANGUAGE_FEATURES: &[(Feature, bool)] = &[
162
        (Feature::LOCL, true),
163
        (Feature::ISOL, false),
164
        (Feature::FINA, false),
165
        (Feature::MEDI, false),
166
        (Feature::INIT, false),
167
        (Feature::RLIG, true),
168
        (Feature::RCLT, true),
169
        (Feature::CALT, true),
170
    ];
171

            
172
    for &(feature, is_global) in LANGUAGE_FEATURES {
173
        apply_lookups(
174
            feature.mask(),
175
            gsub_cache,
176
            gsub_table,
177
            gdef_table,
178
            script_tag,
179
            lang_tag,
180
            feature_variations,
181
            mongolian_glyphs,
182
            max_glyphs,
183
            |g, feature_tag| is_global || g.feature_tag() == feature_tag,
184
        )?;
185
    }
186

            
187
    // 5. Applying the typographic-form substitution features from GSUB
188

            
189
    let typographic_features = Feature::LIGA | Feature::MSET;
190

            
191
    apply_lookups(
192
        typographic_features | extra_features,
193
        gsub_cache,
194
        gsub_table,
195
        gdef_table,
196
        script_tag,
197
        lang_tag,
198
        feature_variations,
199
        mongolian_glyphs,
200
        max_glyphs,
201
        |_, _| true,
202
    )?;
203

            
204
    // 6. Mark reordering
205
    //
206
    // Handled in the text preprocessing stage.
207

            
208
    *raw_glyphs = mongolian_glyphs.iter().map(RawGlyph::from).collect();
209

            
210
    Ok(())
211
}
212

            
213
fn apply_lookups(
214
    feature_mask: FeatureMask,
215
    gsub_cache: &LayoutCache<GSUB>,
216
    gsub_table: &LayoutTable<GSUB>,
217
    gdef_table: Option<&GDEFTable>,
218
    script_tag: u32,
219
    lang_tag: Option<u32>,
220
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
221
    mongolian_glyphs: &mut Vec<MongolianGlyph>,
222
    max_glyphs: usize,
223
    pred: impl Fn(&MongolianGlyph, u32) -> bool + Copy,
224
) -> Result<(), ParseError> {
225
    let index = gsub::get_lookups_cache_index(
226
        gsub_cache,
227
        script_tag,
228
        lang_tag,
229
        feature_variations,
230
        feature_mask,
231
    )?;
232
    let lookups = &gsub_cache.cached_lookups.lock().unwrap()[index];
233

            
234
    for &(lookup_index, feature_tag) in lookups {
235
        gsub::gsub_apply_lookup(
236
            gsub_cache,
237
            gsub_table,
238
            gdef_table,
239
            lookup_index,
240
            feature_tag,
241
            None,
242
            mongolian_glyphs,
243
            max_glyphs,
244
            0,
245
            mongolian_glyphs.len(),
246
            |g| pred(g, feature_tag),
247
        )?;
248
    }
249

            
250
    Ok(())
251
}