1 | use alloc::boxed::Box; |
2 | use alloc::vec::Vec; |
3 | use core::any::Any; |
4 | |
5 | use crate::complex::{complex_categorize, ComplexShaper, DEFAULT_SHAPER, DUMBER_SHAPER}; |
6 | use crate::ot::{self, feature, FeatureFlags, TableIndex}; |
7 | use crate::{aat, Direction, Face, Feature, Language, Mask, Script, Tag}; |
8 | |
9 | /// A reusable plan for shaping a text buffer. |
10 | pub struct ShapePlan { |
11 | pub(crate) direction: Direction, |
12 | pub(crate) script: Option<Script>, |
13 | pub(crate) shaper: &'static ComplexShaper, |
14 | pub(crate) ot_map: ot::Map, |
15 | pub(crate) aat_map: aat::Map, |
16 | data: Option<Box<dyn Any + Send + Sync>>, |
17 | |
18 | pub(crate) frac_mask: Mask, |
19 | pub(crate) numr_mask: Mask, |
20 | pub(crate) dnom_mask: Mask, |
21 | pub(crate) rtlm_mask: Mask, |
22 | pub(crate) kern_mask: Mask, |
23 | pub(crate) trak_mask: Mask, |
24 | |
25 | pub(crate) requested_kerning: bool, |
26 | pub(crate) has_frac: bool, |
27 | pub(crate) has_vert: bool, |
28 | pub(crate) has_gpos_mark: bool, |
29 | pub(crate) zero_marks: bool, |
30 | pub(crate) fallback_glyph_classes: bool, |
31 | pub(crate) fallback_mark_positioning: bool, |
32 | pub(crate) adjust_mark_positioning_when_zeroing: bool, |
33 | |
34 | pub(crate) apply_gpos: bool, |
35 | pub(crate) apply_fallback_kern: bool, |
36 | pub(crate) apply_kern: bool, |
37 | pub(crate) apply_kerx: bool, |
38 | pub(crate) apply_morx: bool, |
39 | pub(crate) apply_trak: bool, |
40 | |
41 | pub(crate) user_features: Vec<Feature>, |
42 | } |
43 | |
44 | impl ShapePlan { |
45 | /// Returns a plan that can be used for shaping any buffer with the |
46 | /// provided properties. |
47 | pub fn new( |
48 | face: &Face, |
49 | direction: Direction, |
50 | script: Option<Script>, |
51 | language: Option<&Language>, |
52 | user_features: &[Feature], |
53 | ) -> Self { |
54 | assert_ne!(direction, Direction::Invalid); |
55 | let mut planner: ShapePlanner<'_> = ShapePlanner::new(face, direction, script, language); |
56 | planner.collect_features(user_features); |
57 | planner.compile(user_features) |
58 | } |
59 | |
60 | pub(crate) fn data<T: 'static>(&self) -> &T { |
61 | self.data.as_ref().unwrap().downcast_ref().unwrap() |
62 | } |
63 | } |
64 | |
65 | pub struct ShapePlanner<'a> { |
66 | pub face: &'a Face<'a>, |
67 | pub direction: Direction, |
68 | pub script: Option<Script>, |
69 | pub ot_map: ot::MapBuilder<'a>, |
70 | pub aat_map: aat::MapBuilder, |
71 | pub apply_morx: bool, |
72 | pub script_zero_marks: bool, |
73 | pub script_fallback_mark_positioning: bool, |
74 | pub shaper: &'static ComplexShaper, |
75 | } |
76 | |
77 | impl<'a> ShapePlanner<'a> { |
78 | fn new( |
79 | face: &'a Face<'a>, |
80 | direction: Direction, |
81 | script: Option<Script>, |
82 | language: Option<&Language>, |
83 | ) -> Self { |
84 | let ot_map = ot::MapBuilder::new(face, script, language); |
85 | let aat_map = aat::MapBuilder::default(); |
86 | |
87 | let mut shaper = match script { |
88 | Some(script) => { |
89 | complex_categorize(script, direction, ot_map.chosen_script(TableIndex::GSUB)) |
90 | } |
91 | None => &DEFAULT_SHAPER, |
92 | }; |
93 | |
94 | let script_zero_marks = shaper.zero_width_marks.is_some(); |
95 | let script_fallback_mark_positioning = shaper.fallback_position; |
96 | |
97 | // https://github.com/harfbuzz/harfbuzz/issues/2124 |
98 | let apply_morx = |
99 | face.tables().morx.is_some() && (direction.is_horizontal() || face.gsub.is_none()); |
100 | |
101 | // https://github.com/harfbuzz/harfbuzz/issues/1528 |
102 | if apply_morx && shaper as *const _ != &DEFAULT_SHAPER as *const _ { |
103 | shaper = &DUMBER_SHAPER; |
104 | } |
105 | |
106 | ShapePlanner { |
107 | face, |
108 | direction, |
109 | script, |
110 | ot_map, |
111 | aat_map, |
112 | apply_morx, |
113 | script_zero_marks, |
114 | script_fallback_mark_positioning, |
115 | shaper, |
116 | } |
117 | } |
118 | |
119 | fn collect_features(&mut self, user_features: &[Feature]) { |
120 | const COMMON_FEATURES: &[(Tag, FeatureFlags)] = &[ |
121 | (feature::ABOVE_BASE_MARK_POSITIONING, FeatureFlags::GLOBAL), |
122 | (feature::BELOW_BASE_MARK_POSITIONING, FeatureFlags::GLOBAL), |
123 | ( |
124 | feature::GLYPH_COMPOSITION_DECOMPOSITION, |
125 | FeatureFlags::GLOBAL, |
126 | ), |
127 | (feature::LOCALIZED_FORMS, FeatureFlags::GLOBAL), |
128 | ( |
129 | feature::MARK_POSITIONING, |
130 | FeatureFlags::GLOBAL_MANUAL_JOINERS, |
131 | ), |
132 | ( |
133 | feature::MARK_TO_MARK_POSITIONING, |
134 | FeatureFlags::GLOBAL_MANUAL_JOINERS, |
135 | ), |
136 | (feature::REQUIRED_LIGATURES, FeatureFlags::GLOBAL), |
137 | ]; |
138 | |
139 | const HORIZONTAL_FEATURES: &[(Tag, FeatureFlags)] = &[ |
140 | (feature::CONTEXTUAL_ALTERNATES, FeatureFlags::GLOBAL), |
141 | (feature::CONTEXTUAL_LIGATURES, FeatureFlags::GLOBAL), |
142 | (feature::CURSIVE_POSITIONING, FeatureFlags::GLOBAL), |
143 | (feature::DISTANCES, FeatureFlags::GLOBAL), |
144 | (feature::KERNING, FeatureFlags::GLOBAL_HAS_FALLBACK), |
145 | (feature::STANDARD_LIGATURES, FeatureFlags::GLOBAL), |
146 | ( |
147 | feature::REQUIRED_CONTEXTUAL_ALTERNATES, |
148 | FeatureFlags::GLOBAL, |
149 | ), |
150 | ]; |
151 | |
152 | let empty = FeatureFlags::empty(); |
153 | |
154 | self.ot_map |
155 | .enable_feature(feature::REQUIRED_VARIATION_ALTERNATES, empty, 1); |
156 | self.ot_map.add_gsub_pause(None); |
157 | |
158 | match self.direction { |
159 | Direction::LeftToRight => { |
160 | self.ot_map |
161 | .enable_feature(feature::LEFT_TO_RIGHT_ALTERNATES, empty, 1); |
162 | self.ot_map |
163 | .enable_feature(feature::LEFT_TO_RIGHT_MIRRORED_FORMS, empty, 1); |
164 | } |
165 | Direction::RightToLeft => { |
166 | self.ot_map |
167 | .enable_feature(feature::RIGHT_TO_LEFT_ALTERNATES, empty, 1); |
168 | self.ot_map |
169 | .add_feature(feature::RIGHT_TO_LEFT_MIRRORED_FORMS, empty, 1); |
170 | } |
171 | _ => {} |
172 | } |
173 | |
174 | // Automatic fractions. |
175 | self.ot_map.add_feature(feature::FRACTIONS, empty, 1); |
176 | self.ot_map.add_feature(feature::NUMERATORS, empty, 1); |
177 | self.ot_map.add_feature(feature::DENOMINATORS, empty, 1); |
178 | |
179 | // Random! |
180 | self.ot_map |
181 | .enable_feature(feature::RANDOMIZE, FeatureFlags::RANDOM, ot::Map::MAX_VALUE); |
182 | |
183 | // Tracking. We enable dummy feature here just to allow disabling |
184 | // AAT 'trak' table using features. |
185 | // https://github.com/harfbuzz/harfbuzz/issues/1303 |
186 | self.ot_map |
187 | .enable_feature(Tag::from_bytes(b"trak" ), FeatureFlags::HAS_FALLBACK, 1); |
188 | |
189 | self.ot_map |
190 | .enable_feature(Tag::from_bytes(b"Harf" ), empty, 1); // Considered required. |
191 | self.ot_map |
192 | .enable_feature(Tag::from_bytes(b"HARF" ), empty, 1); // Considered discretionary. |
193 | |
194 | if let Some(func) = self.shaper.collect_features { |
195 | func(self); |
196 | } |
197 | |
198 | self.ot_map |
199 | .enable_feature(Tag::from_bytes(b"Buzz" ), empty, 1); // Considered required. |
200 | self.ot_map |
201 | .enable_feature(Tag::from_bytes(b"BUZZ" ), empty, 1); // Considered discretionary. |
202 | |
203 | for &(tag, flags) in COMMON_FEATURES { |
204 | self.ot_map.add_feature(tag, flags, 1); |
205 | } |
206 | |
207 | if self.direction.is_horizontal() { |
208 | for &(tag, flags) in HORIZONTAL_FEATURES { |
209 | self.ot_map.add_feature(tag, flags, 1); |
210 | } |
211 | } else { |
212 | // We only apply `vert` feature. See: |
213 | // https://github.com/harfbuzz/harfbuzz/commit/d71c0df2d17f4590d5611239577a6cb532c26528 |
214 | // https://lists.freedesktop.org/archives/harfbuzz/2013-August/003490.html |
215 | |
216 | // We really want to find a 'vert' feature if there's any in the font, no |
217 | // matter which script/langsys it is listed (or not) under. |
218 | // See various bugs referenced from: |
219 | // https://github.com/harfbuzz/harfbuzz/issues/63 |
220 | self.ot_map |
221 | .enable_feature(feature::VERTICAL_WRITING, FeatureFlags::GLOBAL_SEARCH, 1); |
222 | } |
223 | |
224 | for feature in user_features { |
225 | let flags = if feature.is_global() { |
226 | FeatureFlags::GLOBAL |
227 | } else { |
228 | empty |
229 | }; |
230 | self.ot_map.add_feature(feature.tag, flags, feature.value); |
231 | } |
232 | |
233 | if self.apply_morx { |
234 | for feature in user_features { |
235 | self.aat_map |
236 | .add_feature(self.face, feature.tag, feature.value); |
237 | } |
238 | } |
239 | |
240 | if let Some(func) = self.shaper.override_features { |
241 | func(self); |
242 | } |
243 | } |
244 | |
245 | fn compile(mut self, user_features: &[Feature]) -> ShapePlan { |
246 | let ot_map = self.ot_map.compile(); |
247 | |
248 | let aat_map = if self.apply_morx { |
249 | self.aat_map.compile(self.face) |
250 | } else { |
251 | aat::Map::default() |
252 | }; |
253 | |
254 | let frac_mask = ot_map.one_mask(feature::FRACTIONS); |
255 | let numr_mask = ot_map.one_mask(feature::NUMERATORS); |
256 | let dnom_mask = ot_map.one_mask(feature::DENOMINATORS); |
257 | let has_frac = frac_mask != 0 || (numr_mask != 0 && dnom_mask != 0); |
258 | |
259 | let rtlm_mask = ot_map.one_mask(feature::RIGHT_TO_LEFT_MIRRORED_FORMS); |
260 | let has_vert = ot_map.one_mask(feature::VERTICAL_WRITING) != 0; |
261 | |
262 | let horizontal = self.direction.is_horizontal(); |
263 | let kern_tag = if horizontal { |
264 | feature::KERNING |
265 | } else { |
266 | feature::VERTICAL_KERNING |
267 | }; |
268 | let kern_mask = ot_map.mask(kern_tag).0; |
269 | let requested_kerning = kern_mask != 0; |
270 | let trak_mask = ot_map.mask(Tag::from_bytes(b"trak" )).0; |
271 | let requested_tracking = trak_mask != 0; |
272 | |
273 | let has_gpos_kern = ot_map.feature_index(TableIndex::GPOS, kern_tag).is_some(); |
274 | let disable_gpos = self.shaper.gpos_tag.is_some() |
275 | && self.shaper.gpos_tag != ot_map.chosen_script(TableIndex::GPOS); |
276 | |
277 | // Decide who provides glyph classes. GDEF or Unicode. |
278 | let fallback_glyph_classes = !self |
279 | .face |
280 | .tables() |
281 | .gdef |
282 | .map_or(false, |table| table.has_glyph_classes()); |
283 | |
284 | // Decide who does substitutions. GSUB, morx, or fallback. |
285 | let apply_morx = self.apply_morx; |
286 | |
287 | let mut apply_gpos = false; |
288 | let mut apply_kerx = false; |
289 | let mut apply_kern = false; |
290 | |
291 | // Decide who does positioning. GPOS, kerx, kern, or fallback. |
292 | let has_kerx = self.face.tables().kerx.is_some(); |
293 | let has_gsub = self.face.tables().gsub.is_some(); |
294 | let has_gpos = !disable_gpos && self.face.tables().gpos.is_some(); |
295 | |
296 | // Prefer GPOS over kerx if GSUB is present; |
297 | // https://github.com/harfbuzz/harfbuzz/issues/3008 |
298 | if has_kerx && !(has_gsub && has_gpos) { |
299 | apply_kerx = true; |
300 | } else if has_gpos { |
301 | apply_gpos = true; |
302 | } |
303 | |
304 | if !apply_kerx && (!has_gpos_kern || !apply_gpos) { |
305 | if has_kerx { |
306 | apply_kerx = true; |
307 | } else if ot::has_kerning(self.face) { |
308 | apply_kern = true; |
309 | } |
310 | } |
311 | |
312 | let apply_fallback_kern = !(apply_gpos || apply_kerx || apply_kern); |
313 | let zero_marks = self.script_zero_marks |
314 | && !apply_kerx |
315 | && (!apply_kern || !ot::has_machine_kerning(self.face)); |
316 | |
317 | let has_gpos_mark = ot_map.one_mask(feature::MARK_POSITIONING) != 0; |
318 | |
319 | let mut adjust_mark_positioning_when_zeroing = |
320 | !apply_gpos && !apply_kerx && (!apply_kern || !ot::has_cross_kerning(self.face)); |
321 | |
322 | let fallback_mark_positioning = |
323 | adjust_mark_positioning_when_zeroing && self.script_fallback_mark_positioning; |
324 | |
325 | // If we're using morx shaping, we cancel mark position adjustment because |
326 | // Apple Color Emoji assumes this will NOT be done when forming emoji sequences; |
327 | // https://github.com/harfbuzz/harfbuzz/issues/2967. |
328 | if apply_morx { |
329 | adjust_mark_positioning_when_zeroing = false; |
330 | } |
331 | |
332 | // Currently we always apply trak. |
333 | let apply_trak = requested_tracking && self.face.tables().trak.is_some(); |
334 | |
335 | let mut plan = ShapePlan { |
336 | direction: self.direction, |
337 | script: self.script, |
338 | shaper: self.shaper, |
339 | ot_map, |
340 | aat_map, |
341 | data: None, |
342 | frac_mask, |
343 | numr_mask, |
344 | dnom_mask, |
345 | rtlm_mask, |
346 | kern_mask, |
347 | trak_mask, |
348 | requested_kerning, |
349 | has_frac, |
350 | has_vert, |
351 | has_gpos_mark, |
352 | zero_marks, |
353 | fallback_glyph_classes, |
354 | fallback_mark_positioning, |
355 | adjust_mark_positioning_when_zeroing, |
356 | apply_gpos, |
357 | apply_kern, |
358 | apply_fallback_kern, |
359 | apply_kerx, |
360 | apply_morx, |
361 | apply_trak, |
362 | user_features: user_features.to_vec(), |
363 | }; |
364 | |
365 | if let Some(func) = self.shaper.create_data { |
366 | plan.data = Some(func(&plan)); |
367 | } |
368 | |
369 | plan |
370 | } |
371 | } |
372 | |
373 | #[cfg (test)] |
374 | mod tests { |
375 | use super::ShapePlan; |
376 | |
377 | #[test ] |
378 | fn test_shape_plan_is_send_and_sync() { |
379 | fn ensure_send_and_sync<T: Send + Sync>() {} |
380 | ensure_send_and_sync::<ShapePlan>(); |
381 | } |
382 | } |
383 | |