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); |
191 | |
192 | if let Some(func) = self.shaper.collect_features { |
193 | func(self); |
194 | } |
195 | |
196 | self.ot_map |
197 | .enable_feature(Tag::from_bytes(b"BUZZ" ), empty, 1); |
198 | |
199 | for &(tag, flags) in COMMON_FEATURES { |
200 | self.ot_map.add_feature(tag, flags, 1); |
201 | } |
202 | |
203 | if self.direction.is_horizontal() { |
204 | for &(tag, flags) in HORIZONTAL_FEATURES { |
205 | self.ot_map.add_feature(tag, flags, 1); |
206 | } |
207 | } else { |
208 | // We only apply `vert` feature. See: |
209 | // https://github.com/harfbuzz/harfbuzz/commit/d71c0df2d17f4590d5611239577a6cb532c26528 |
210 | // https://lists.freedesktop.org/archives/harfbuzz/2013-August/003490.html |
211 | |
212 | // We really want to find a 'vert' feature if there's any in the font, no |
213 | // matter which script/langsys it is listed (or not) under. |
214 | // See various bugs referenced from: |
215 | // https://github.com/harfbuzz/harfbuzz/issues/63 |
216 | self.ot_map |
217 | .enable_feature(feature::VERTICAL_WRITING, FeatureFlags::GLOBAL_SEARCH, 1); |
218 | } |
219 | |
220 | for feature in user_features { |
221 | let flags = if feature.is_global() { |
222 | FeatureFlags::GLOBAL |
223 | } else { |
224 | empty |
225 | }; |
226 | self.ot_map.add_feature(feature.tag, flags, feature.value); |
227 | } |
228 | |
229 | if self.apply_morx { |
230 | for feature in user_features { |
231 | self.aat_map |
232 | .add_feature(self.face, feature.tag, feature.value); |
233 | } |
234 | } |
235 | |
236 | if let Some(func) = self.shaper.override_features { |
237 | func(self); |
238 | } |
239 | } |
240 | |
241 | fn compile(mut self, user_features: &[Feature]) -> ShapePlan { |
242 | let ot_map = self.ot_map.compile(); |
243 | |
244 | let aat_map = if self.apply_morx { |
245 | self.aat_map.compile(self.face) |
246 | } else { |
247 | aat::Map::default() |
248 | }; |
249 | |
250 | let frac_mask = ot_map.one_mask(feature::FRACTIONS); |
251 | let numr_mask = ot_map.one_mask(feature::NUMERATORS); |
252 | let dnom_mask = ot_map.one_mask(feature::DENOMINATORS); |
253 | let has_frac = frac_mask != 0 || (numr_mask != 0 && dnom_mask != 0); |
254 | |
255 | let rtlm_mask = ot_map.one_mask(feature::RIGHT_TO_LEFT_MIRRORED_FORMS); |
256 | let has_vert = ot_map.one_mask(feature::VERTICAL_WRITING) != 0; |
257 | |
258 | let horizontal = self.direction.is_horizontal(); |
259 | let kern_tag = if horizontal { |
260 | feature::KERNING |
261 | } else { |
262 | feature::VERTICAL_KERNING |
263 | }; |
264 | let kern_mask = ot_map.mask(kern_tag).0; |
265 | let requested_kerning = kern_mask != 0; |
266 | let trak_mask = ot_map.mask(Tag::from_bytes(b"trak" )).0; |
267 | let requested_tracking = trak_mask != 0; |
268 | |
269 | let has_gpos_kern = ot_map.feature_index(TableIndex::GPOS, kern_tag).is_some(); |
270 | let disable_gpos = self.shaper.gpos_tag.is_some() |
271 | && self.shaper.gpos_tag != ot_map.chosen_script(TableIndex::GPOS); |
272 | |
273 | // Decide who provides glyph classes. GDEF or Unicode. |
274 | let fallback_glyph_classes = !self |
275 | .face |
276 | .tables() |
277 | .gdef |
278 | .map_or(false, |table| table.has_glyph_classes()); |
279 | |
280 | // Decide who does substitutions. GSUB, morx, or fallback. |
281 | let apply_morx = self.apply_morx; |
282 | |
283 | let mut apply_gpos = false; |
284 | let mut apply_kerx = false; |
285 | let mut apply_kern = false; |
286 | |
287 | // Decide who does positioning. GPOS, kerx, kern, or fallback. |
288 | let has_gsub = self.face.tables().gsub.is_some(); |
289 | let has_gpos = !disable_gpos && self.face.tables().gpos.is_some(); |
290 | |
291 | if self.face.tables().kerx.is_some() && !(has_gsub && has_gpos) { |
292 | apply_kerx = true; |
293 | } else if !apply_morx && has_gpos { |
294 | apply_gpos = true; |
295 | } |
296 | |
297 | if !apply_kerx && (!has_gpos_kern || !apply_gpos) { |
298 | // Apparently Apple applies kerx if GPOS kern was not applied. |
299 | if self.face.tables().kerx.is_some() { |
300 | apply_kerx = true; |
301 | } else if ot::has_kerning(self.face) { |
302 | apply_kern = true; |
303 | } |
304 | } |
305 | |
306 | let apply_fallback_kern = !(apply_gpos || apply_kerx || apply_kern); |
307 | let zero_marks = self.script_zero_marks |
308 | && !apply_kerx |
309 | && (!apply_kern || !ot::has_machine_kerning(self.face)); |
310 | |
311 | let has_gpos_mark = ot_map.one_mask(feature::MARK_POSITIONING) != 0; |
312 | |
313 | let mut adjust_mark_positioning_when_zeroing = |
314 | !apply_gpos && !apply_kerx && (!apply_kern || !ot::has_cross_kerning(self.face)); |
315 | |
316 | let fallback_mark_positioning = |
317 | adjust_mark_positioning_when_zeroing && self.script_fallback_mark_positioning; |
318 | |
319 | // If we're using morx shaping, we cancel mark position adjustment because |
320 | // Apple Color Emoji assumes this will NOT be done when forming emoji sequences; |
321 | // https://github.com/harfbuzz/harfbuzz/issues/2967. |
322 | if apply_morx { |
323 | adjust_mark_positioning_when_zeroing = false; |
324 | } |
325 | |
326 | // Currently we always apply trak. |
327 | let apply_trak = requested_tracking && self.face.tables().trak.is_some(); |
328 | |
329 | let mut plan = ShapePlan { |
330 | direction: self.direction, |
331 | script: self.script, |
332 | shaper: self.shaper, |
333 | ot_map, |
334 | aat_map, |
335 | data: None, |
336 | frac_mask, |
337 | numr_mask, |
338 | dnom_mask, |
339 | rtlm_mask, |
340 | kern_mask, |
341 | trak_mask, |
342 | requested_kerning, |
343 | has_frac, |
344 | has_vert, |
345 | has_gpos_mark, |
346 | zero_marks, |
347 | fallback_glyph_classes, |
348 | fallback_mark_positioning, |
349 | adjust_mark_positioning_when_zeroing, |
350 | apply_gpos, |
351 | apply_kern, |
352 | apply_fallback_kern, |
353 | apply_kerx, |
354 | apply_morx, |
355 | apply_trak, |
356 | user_features: user_features.to_vec(), |
357 | }; |
358 | |
359 | if let Some(func) = self.shaper.create_data { |
360 | plan.data = Some(func(&plan)); |
361 | } |
362 | |
363 | plan |
364 | } |
365 | } |
366 | |
367 | #[cfg (test)] |
368 | mod tests { |
369 | use super::ShapePlan; |
370 | |
371 | #[test ] |
372 | fn test_shape_plan_is_send_and_sync() { |
373 | fn ensure_send_and_sync<T: Send + Sync>() {} |
374 | ensure_send_and_sync::<ShapePlan>(); |
375 | } |
376 | } |
377 | |