1//! OpenType layout.
2
3use core::ops::{Index, IndexMut};
4
5use ttf_parser::opentype_layout::{FeatureIndex, LanguageIndex, LookupIndex, ScriptIndex};
6use ttf_parser::GlyphId;
7
8use super::apply::{Apply, ApplyContext};
9use crate::buffer::Buffer;
10use crate::common::TagExt;
11use crate::plan::ShapePlan;
12use crate::{Face, Tag};
13
14pub const MAX_NESTING_LEVEL: usize = 6;
15pub const MAX_CONTEXT_LENGTH: usize = 64;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum TableIndex {
19 GSUB = 0,
20 GPOS = 1,
21}
22
23impl TableIndex {
24 pub fn iter() -> impl Iterator<Item = TableIndex> {
25 [Self::GSUB, Self::GPOS].iter().copied()
26 }
27}
28
29impl<T> Index<TableIndex> for [T] {
30 type Output = T;
31
32 fn index(&self, table_index: TableIndex) -> &Self::Output {
33 &self[table_index as usize]
34 }
35}
36
37impl<T> IndexMut<TableIndex> for [T] {
38 fn index_mut(&mut self, table_index: TableIndex) -> &mut Self::Output {
39 &mut self[table_index as usize]
40 }
41}
42
43/// A lookup-based layout table (GSUB or GPOS).
44pub trait LayoutTable {
45 /// The index of this table.
46 const INDEX: TableIndex;
47
48 /// Whether lookups in this table can be applied to the buffer in-place.
49 const IN_PLACE: bool;
50
51 /// The kind of lookup stored in this table.
52 type Lookup: LayoutLookup;
53
54 /// Get the lookup at the specified index.
55 fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup>;
56}
57
58/// A lookup in a layout table.
59pub trait LayoutLookup: Apply {
60 /// The lookup's lookup_props.
61 fn props(&self) -> u32;
62
63 /// Whether the lookup has to be applied backwards.
64 fn is_reverse(&self) -> bool;
65
66 /// Whether any subtable of the lookup could apply at a specific glyph.
67 fn covers(&self, glyph: GlyphId) -> bool;
68}
69
70pub trait LayoutTableExt {
71 fn select_script(&self, script_tags: &[Tag]) -> Option<(bool, ScriptIndex, Tag)>;
72 fn select_script_language(
73 &self,
74 script_index: ScriptIndex,
75 lang_tags: &[Tag],
76 ) -> Option<LanguageIndex>;
77 fn get_required_language_feature(
78 &self,
79 script_index: ScriptIndex,
80 lang_index: Option<LanguageIndex>,
81 ) -> Option<(FeatureIndex, Tag)>;
82 fn find_language_feature(
83 &self,
84 script_index: ScriptIndex,
85 lang_index: Option<LanguageIndex>,
86 feature_tag: Tag,
87 ) -> Option<FeatureIndex>;
88}
89
90impl LayoutTableExt for ttf_parser::opentype_layout::LayoutTable<'_> {
91 /// Returns true + index and tag of the first found script tag in the given GSUB or GPOS table
92 /// or false + index and tag if falling back to a default script.
93 fn select_script(&self, script_tags: &[Tag]) -> Option<(bool, ScriptIndex, Tag)> {
94 for &tag in script_tags {
95 if let Some(index) = self.scripts.index(tag) {
96 return Some((true, index, tag));
97 }
98 }
99
100 for &tag in &[
101 // try finding 'DFLT'
102 Tag::default_script(),
103 // try with 'dflt'; MS site has had typos and many fonts use it now :(
104 Tag::default_language(),
105 // try with 'latn'; some old fonts put their features there even though
106 // they're really trying to support Thai, for example :(
107 Tag::from_bytes(b"latn"),
108 ] {
109 if let Some(index) = self.scripts.index(tag) {
110 return Some((false, index, tag));
111 }
112 }
113
114 None
115 }
116
117 /// Returns the index of the first found language tag in the given GSUB or GPOS table,
118 /// underneath the specified script index.
119 fn select_script_language(
120 &self,
121 script_index: ScriptIndex,
122 lang_tags: &[Tag],
123 ) -> Option<LanguageIndex> {
124 let script = self.scripts.get(script_index)?;
125
126 for &tag in lang_tags {
127 if let Some(index) = script.languages.index(tag) {
128 return Some(index);
129 }
130 }
131
132 // try finding 'dflt'
133 if let Some(index) = script.languages.index(Tag::default_language()) {
134 return Some(index);
135 }
136
137 None
138 }
139
140 /// Returns the index and tag of a required feature in the given GSUB or GPOS table,
141 /// underneath the specified script and language.
142 fn get_required_language_feature(
143 &self,
144 script_index: ScriptIndex,
145 lang_index: Option<LanguageIndex>,
146 ) -> Option<(FeatureIndex, Tag)> {
147 let script = self.scripts.get(script_index)?;
148 let sys = match lang_index {
149 Some(index) => script.languages.get(index)?,
150 None => script.default_language?,
151 };
152 let idx = sys.required_feature?;
153 let tag = self.features.get(idx)?.tag;
154 Some((idx, tag))
155 }
156
157 /// Returns the index of a given feature tag in the given GSUB or GPOS table,
158 /// underneath the specified script and language.
159 fn find_language_feature(
160 &self,
161 script_index: ScriptIndex,
162 lang_index: Option<LanguageIndex>,
163 feature_tag: Tag,
164 ) -> Option<FeatureIndex> {
165 let script = self.scripts.get(script_index)?;
166 let sys = match lang_index {
167 Some(index) => script.languages.get(index)?,
168 None => script.default_language?,
169 };
170
171 for i in 0..sys.feature_indices.len() {
172 if let Some(index) = sys.feature_indices.get(i) {
173 if self.features.get(index).map(|v| v.tag) == Some(feature_tag) {
174 return Some(index);
175 }
176 }
177 }
178
179 None
180 }
181}
182
183/// Applies the lookups in the given GSUB or GPOS table.
184pub fn apply_layout_table<T: LayoutTable>(
185 plan: &ShapePlan,
186 face: &Face,
187 buffer: &mut Buffer,
188 table: Option<&T>,
189) {
190 let mut ctx: ApplyContext<'_, '_> = ApplyContext::new(T::INDEX, face, buffer);
191
192 for (stage_index: usize, stage: &StageMap) in plan.ot_map.stages(T::INDEX).iter().enumerate() {
193 for lookup: &LookupMap in plan.ot_map.stage_lookups(T::INDEX, stage_index) {
194 ctx.lookup_index = lookup.index;
195 ctx.lookup_mask = lookup.mask;
196 ctx.auto_zwj = lookup.auto_zwj;
197 ctx.auto_zwnj = lookup.auto_zwnj;
198
199 ctx.random = lookup.random;
200
201 if let Some(table: &&T) = &table {
202 if let Some(lookup: &::Lookup) = table.get_lookup(lookup.index) {
203 apply_string::<T>(&mut ctx, lookup);
204 }
205 }
206 }
207
208 if let Some(func: fn(&ShapePlan, &Face<'_>, …)) = stage.pause_func {
209 func(plan, face, ctx.buffer);
210 }
211 }
212}
213
214fn apply_string<T: LayoutTable>(ctx: &mut ApplyContext, lookup: &T::Lookup) {
215 if ctx.buffer.is_empty() || ctx.lookup_mask == 0 {
216 return;
217 }
218
219 ctx.lookup_props = lookup.props();
220
221 if !lookup.is_reverse() {
222 // in/out forward substitution/positioning
223 if !T::IN_PLACE {
224 ctx.buffer.clear_output();
225 }
226 ctx.buffer.idx = 0;
227 apply_forward(ctx, lookup);
228
229 if !T::IN_PLACE {
230 ctx.buffer.sync();
231 }
232 } else {
233 // in-place backward substitution/positioning
234 assert!(!ctx.buffer.have_output);
235
236 ctx.buffer.idx = ctx.buffer.len - 1;
237 apply_backward(ctx, lookup);
238 }
239}
240
241fn apply_forward(ctx: &mut ApplyContext, lookup: &impl Apply) -> bool {
242 let mut ret: bool = false;
243 while ctx.buffer.idx < ctx.buffer.len && ctx.buffer.successful {
244 let cur: &GlyphInfo = ctx.buffer.cur(0);
245 if (cur.mask & ctx.lookup_mask) != 0
246 && ctx.check_glyph_property(info:cur, match_props:ctx.lookup_props)
247 && lookup.apply(ctx).is_some()
248 {
249 ret = true;
250 } else {
251 ctx.buffer.next_glyph();
252 }
253 }
254 ret
255}
256
257fn apply_backward(ctx: &mut ApplyContext, lookup: &impl Apply) -> bool {
258 let mut ret: bool = false;
259 loop {
260 let cur: &GlyphInfo = ctx.buffer.cur(0);
261 ret |= (cur.mask & ctx.lookup_mask) != 0
262 && ctx.check_glyph_property(info:cur, match_props:ctx.lookup_props)
263 && lookup.apply(ctx).is_some();
264
265 if ctx.buffer.idx == 0 {
266 break;
267 }
268
269 ctx.buffer.idx -= 1;
270 }
271 ret
272}
273
274pub fn clear_substitution_flags(_: &ShapePlan, _: &Face, buffer: &mut Buffer) {
275 let len: usize = buffer.len;
276 for info: &mut GlyphInfo in &mut buffer.info[..len] {
277 info.clear_substituted();
278 }
279}
280
281pub fn clear_syllables(_: &ShapePlan, _: &Face, buffer: &mut Buffer) {
282 let len: usize = buffer.len;
283 for info: &mut GlyphInfo in &mut buffer.info[..len] {
284 info.set_syllable(0);
285 }
286}
287