1 | use super::buffer::*; |
2 | use super::ot_layout::*; |
3 | use super::ot_layout_gpos_table::GPOS; |
4 | use super::ot_map::*; |
5 | use super::ot_shape_plan::hb_ot_shape_plan_t; |
6 | use super::ot_shaper::*; |
7 | use super::unicode::{hb_unicode_general_category_t, CharExt, GeneralCategoryExt}; |
8 | use super::*; |
9 | use super::{hb_font_t, hb_tag_t}; |
10 | use crate::hb::aat_layout::hb_aat_layout_remove_deleted_glyphs; |
11 | use crate::hb::algs::{rb_flag, rb_flag_unsafe}; |
12 | use crate::hb::buffer::glyph_flag::{SAFE_TO_INSERT_TATWEEL, UNSAFE_TO_BREAK, UNSAFE_TO_CONCAT}; |
13 | use crate::hb::unicode::hb_gc::{ |
14 | RB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER, RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER, |
15 | RB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR, RB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER, |
16 | RB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER, |
17 | }; |
18 | use crate::BufferFlags; |
19 | use crate::{Direction, Feature, Language, Script}; |
20 | |
21 | pub struct hb_ot_shape_planner_t<'a> { |
22 | pub face: &'a hb_font_t<'a>, |
23 | pub direction: Direction, |
24 | pub script: Option<Script>, |
25 | pub ot_map: hb_ot_map_builder_t<'a>, |
26 | pub apply_morx: bool, |
27 | pub script_zero_marks: bool, |
28 | pub script_fallback_mark_positioning: bool, |
29 | pub shaper: &'static hb_ot_shaper_t, |
30 | } |
31 | |
32 | impl<'a> hb_ot_shape_planner_t<'a> { |
33 | pub fn new( |
34 | face: &'a hb_font_t<'a>, |
35 | direction: Direction, |
36 | script: Option<Script>, |
37 | language: Option<&Language>, |
38 | ) -> Self { |
39 | let ot_map = hb_ot_map_builder_t::new(face, script, language); |
40 | |
41 | let mut shaper = match script { |
42 | Some(script) => hb_ot_shape_complex_categorize( |
43 | script, |
44 | direction, |
45 | ot_map.chosen_script(TableIndex::GSUB), |
46 | ), |
47 | None => &DEFAULT_SHAPER, |
48 | }; |
49 | |
50 | let script_zero_marks = shaper.zero_width_marks != HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE; |
51 | let script_fallback_mark_positioning = shaper.fallback_position; |
52 | |
53 | // https://github.com/harfbuzz/harfbuzz/issues/2124 |
54 | let apply_morx = |
55 | face.tables().morx.is_some() && (direction.is_horizontal() || face.gsub.is_none()); |
56 | |
57 | // https://github.com/harfbuzz/harfbuzz/issues/1528 |
58 | if apply_morx && shaper as *const _ != &DEFAULT_SHAPER as *const _ { |
59 | shaper = &DUMBER_SHAPER; |
60 | } |
61 | |
62 | hb_ot_shape_planner_t { |
63 | face, |
64 | direction, |
65 | script, |
66 | ot_map, |
67 | apply_morx, |
68 | script_zero_marks, |
69 | script_fallback_mark_positioning, |
70 | shaper, |
71 | } |
72 | } |
73 | |
74 | pub fn collect_features(&mut self, user_features: &[Feature]) { |
75 | const COMMON_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[ |
76 | (hb_tag_t::from_bytes(b"abvm" ), F_GLOBAL), |
77 | (hb_tag_t::from_bytes(b"blwm" ), F_GLOBAL), |
78 | (hb_tag_t::from_bytes(b"ccmp" ), F_GLOBAL), |
79 | (hb_tag_t::from_bytes(b"locl" ), F_GLOBAL), |
80 | (hb_tag_t::from_bytes(b"mark" ), F_GLOBAL_MANUAL_JOINERS), |
81 | (hb_tag_t::from_bytes(b"mkmk" ), F_GLOBAL_MANUAL_JOINERS), |
82 | (hb_tag_t::from_bytes(b"rlig" ), F_GLOBAL), |
83 | ]; |
84 | |
85 | const HORIZONTAL_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[ |
86 | (hb_tag_t::from_bytes(b"calt" ), F_GLOBAL), |
87 | (hb_tag_t::from_bytes(b"clig" ), F_GLOBAL), |
88 | (hb_tag_t::from_bytes(b"curs" ), F_GLOBAL), |
89 | (hb_tag_t::from_bytes(b"dist" ), F_GLOBAL), |
90 | (hb_tag_t::from_bytes(b"kern" ), F_GLOBAL_HAS_FALLBACK), |
91 | (hb_tag_t::from_bytes(b"liga" ), F_GLOBAL), |
92 | (hb_tag_t::from_bytes(b"rclt" ), F_GLOBAL), |
93 | ]; |
94 | |
95 | let empty = F_NONE; |
96 | |
97 | self.ot_map.is_simple = true; |
98 | |
99 | self.ot_map |
100 | .enable_feature(hb_tag_t::from_bytes(b"rvrn" ), empty, 1); |
101 | self.ot_map.add_gsub_pause(None); |
102 | |
103 | match self.direction { |
104 | Direction::LeftToRight => { |
105 | self.ot_map |
106 | .enable_feature(hb_tag_t::from_bytes(b"ltra" ), empty, 1); |
107 | self.ot_map |
108 | .enable_feature(hb_tag_t::from_bytes(b"ltrm" ), empty, 1); |
109 | } |
110 | Direction::RightToLeft => { |
111 | self.ot_map |
112 | .enable_feature(hb_tag_t::from_bytes(b"rtla" ), empty, 1); |
113 | self.ot_map |
114 | .add_feature(hb_tag_t::from_bytes(b"rtlm" ), empty, 1); |
115 | } |
116 | _ => {} |
117 | } |
118 | |
119 | // Automatic fractions. |
120 | self.ot_map |
121 | .add_feature(hb_tag_t::from_bytes(b"frac" ), empty, 1); |
122 | self.ot_map |
123 | .add_feature(hb_tag_t::from_bytes(b"numr" ), empty, 1); |
124 | self.ot_map |
125 | .add_feature(hb_tag_t::from_bytes(b"dnom" ), empty, 1); |
126 | |
127 | // Random! |
128 | self.ot_map.enable_feature( |
129 | hb_tag_t::from_bytes(b"rand" ), |
130 | F_RANDOM, |
131 | hb_ot_map_t::MAX_VALUE, |
132 | ); |
133 | |
134 | // Tracking. We enable dummy feature here just to allow disabling |
135 | // AAT 'trak' table using features. |
136 | // https://github.com/harfbuzz/harfbuzz/issues/1303 |
137 | self.ot_map |
138 | .enable_feature(hb_tag_t::from_bytes(b"trak" ), F_HAS_FALLBACK, 1); |
139 | |
140 | self.ot_map |
141 | .enable_feature(hb_tag_t::from_bytes(b"Harf" ), empty, 1); // Considered required. |
142 | self.ot_map |
143 | .enable_feature(hb_tag_t::from_bytes(b"HARF" ), empty, 1); // Considered discretionary. |
144 | |
145 | if let Some(func) = self.shaper.collect_features { |
146 | self.ot_map.is_simple = false; |
147 | func(self); |
148 | } |
149 | |
150 | self.ot_map |
151 | .enable_feature(hb_tag_t::from_bytes(b"Buzz" ), empty, 1); // Considered required. |
152 | self.ot_map |
153 | .enable_feature(hb_tag_t::from_bytes(b"BUZZ" ), empty, 1); // Considered discretionary. |
154 | |
155 | for &(tag, flags) in COMMON_FEATURES { |
156 | self.ot_map.add_feature(tag, flags, 1); |
157 | } |
158 | |
159 | if self.direction.is_horizontal() { |
160 | for &(tag, flags) in HORIZONTAL_FEATURES { |
161 | self.ot_map.add_feature(tag, flags, 1); |
162 | } |
163 | } else { |
164 | // We only apply `vert` feature. See: |
165 | // https://github.com/harfbuzz/harfbuzz/commit/d71c0df2d17f4590d5611239577a6cb532c26528 |
166 | // https://lists.freedesktop.org/archives/harfbuzz/2013-August/003490.html |
167 | |
168 | // We really want to find a 'vert' feature if there's any in the font, no |
169 | // matter which script/langsys it is listed (or not) under. |
170 | // See various bugs referenced from: |
171 | // https://github.com/harfbuzz/harfbuzz/issues/63 |
172 | self.ot_map |
173 | .enable_feature(hb_tag_t::from_bytes(b"vert" ), F_GLOBAL_SEARCH, 1); |
174 | } |
175 | |
176 | if !user_features.is_empty() { |
177 | self.ot_map.is_simple = false; |
178 | } |
179 | |
180 | for feature in user_features { |
181 | let flags = if feature.is_global() { F_GLOBAL } else { empty }; |
182 | self.ot_map.add_feature(feature.tag, flags, feature.value); |
183 | } |
184 | |
185 | if let Some(func) = self.shaper.override_features { |
186 | func(self); |
187 | } |
188 | } |
189 | |
190 | pub fn compile(mut self, user_features: &[Feature]) -> hb_ot_shape_plan_t { |
191 | let ot_map = self.ot_map.compile(); |
192 | |
193 | let frac_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"frac" )); |
194 | let numr_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"numr" )); |
195 | let dnom_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"dnom" )); |
196 | let has_frac = frac_mask != 0 || (numr_mask != 0 && dnom_mask != 0); |
197 | |
198 | let rtlm_mask = ot_map.get_1_mask(hb_tag_t::from_bytes(b"rtlm" )); |
199 | let has_vert = ot_map.get_1_mask(hb_tag_t::from_bytes(b"vert" )) != 0; |
200 | |
201 | let horizontal = self.direction.is_horizontal(); |
202 | let kern_tag = if horizontal { |
203 | hb_tag_t::from_bytes(b"kern" ) |
204 | } else { |
205 | hb_tag_t::from_bytes(b"vkrn" ) |
206 | }; |
207 | let kern_mask = ot_map.get_mask(kern_tag).0; |
208 | let requested_kerning = kern_mask != 0; |
209 | let trak_mask = ot_map.get_mask(hb_tag_t::from_bytes(b"trak" )).0; |
210 | let requested_tracking = trak_mask != 0; |
211 | |
212 | let has_gpos_kern = ot_map |
213 | .get_feature_index(TableIndex::GPOS, kern_tag) |
214 | .is_some(); |
215 | let disable_gpos = self.shaper.gpos_tag.is_some() |
216 | && self.shaper.gpos_tag != ot_map.chosen_script(TableIndex::GPOS); |
217 | |
218 | // Decide who provides glyph classes. GDEF or Unicode. |
219 | let fallback_glyph_classes = !hb_ot_layout_has_glyph_classes(self.face); |
220 | |
221 | // Decide who does substitutions. GSUB, morx, or fallback. |
222 | let apply_morx = self.apply_morx; |
223 | |
224 | let mut apply_gpos = false; |
225 | let mut apply_kerx = false; |
226 | let mut apply_kern = false; |
227 | |
228 | // Decide who does positioning. GPOS, kerx, kern, or fallback. |
229 | let has_kerx = self.face.tables().kerx.is_some(); |
230 | let has_gsub = !apply_morx && self.face.tables().gsub.is_some(); |
231 | let has_gpos = !disable_gpos && self.face.tables().gpos.is_some(); |
232 | |
233 | // Prefer GPOS over kerx if GSUB is present; |
234 | // https://github.com/harfbuzz/harfbuzz/issues/3008 |
235 | if has_kerx && !(has_gsub && has_gpos) { |
236 | apply_kerx = true; |
237 | } else if has_gpos { |
238 | apply_gpos = true; |
239 | } |
240 | |
241 | if !apply_kerx && (!has_gpos_kern || !apply_gpos) { |
242 | if has_kerx { |
243 | apply_kerx = true; |
244 | } else if hb_ot_layout_has_kerning(self.face) { |
245 | apply_kern = true; |
246 | } |
247 | } |
248 | |
249 | let apply_fallback_kern = !(apply_gpos || apply_kerx || apply_kern); |
250 | let zero_marks = self.script_zero_marks |
251 | && !apply_kerx |
252 | && (!apply_kern || !hb_ot_layout_has_machine_kerning(self.face)); |
253 | |
254 | let has_gpos_mark = ot_map.get_1_mask(hb_tag_t::from_bytes(b"mark" )) != 0; |
255 | |
256 | let mut adjust_mark_positioning_when_zeroing = !apply_gpos |
257 | && !apply_kerx |
258 | && (!apply_kern || !hb_ot_layout_has_cross_kerning(self.face)); |
259 | |
260 | let fallback_mark_positioning = |
261 | adjust_mark_positioning_when_zeroing && self.script_fallback_mark_positioning; |
262 | |
263 | // If we're using morx shaping, we cancel mark position adjustment because |
264 | // Apple Color Emoji assumes this will NOT be done when forming emoji sequences; |
265 | // https://github.com/harfbuzz/harfbuzz/issues/2967. |
266 | if apply_morx { |
267 | adjust_mark_positioning_when_zeroing = false; |
268 | } |
269 | |
270 | // Currently we always apply trak. |
271 | let apply_trak = requested_tracking && self.face.tables().trak.is_some(); |
272 | |
273 | let mut plan = hb_ot_shape_plan_t { |
274 | direction: self.direction, |
275 | script: self.script, |
276 | shaper: self.shaper, |
277 | ot_map, |
278 | data: None, |
279 | frac_mask, |
280 | numr_mask, |
281 | dnom_mask, |
282 | rtlm_mask, |
283 | kern_mask, |
284 | trak_mask, |
285 | requested_kerning, |
286 | has_frac, |
287 | has_vert, |
288 | has_gpos_mark, |
289 | zero_marks, |
290 | fallback_glyph_classes, |
291 | fallback_mark_positioning, |
292 | adjust_mark_positioning_when_zeroing, |
293 | apply_gpos, |
294 | apply_kern, |
295 | apply_fallback_kern, |
296 | apply_kerx, |
297 | apply_morx, |
298 | apply_trak, |
299 | user_features: user_features.to_vec(), |
300 | }; |
301 | |
302 | if let Some(func) = self.shaper.create_data { |
303 | plan.data = Some(func(&plan)); |
304 | } |
305 | |
306 | plan |
307 | } |
308 | } |
309 | |
310 | pub struct hb_ot_shape_context_t<'a> { |
311 | pub plan: &'a hb_ot_shape_plan_t, |
312 | pub face: &'a hb_font_t<'a>, |
313 | pub buffer: &'a mut hb_buffer_t, |
314 | // Transient stuff |
315 | pub target_direction: Direction, |
316 | } |
317 | |
318 | // Pull it all together! |
319 | pub fn shape_internal(ctx: &mut hb_ot_shape_context_t) { |
320 | ctx.buffer.enter(); |
321 | |
322 | initialize_masks(ctx); |
323 | set_unicode_props(ctx.buffer); |
324 | insert_dotted_circle(ctx.buffer, ctx.face); |
325 | |
326 | form_clusters(ctx.buffer); |
327 | |
328 | ensure_native_direction(ctx.buffer); |
329 | |
330 | if let Some(func: fn(&hb_ot_shape_plan_t, &hb_font_t<'_>, …)) = ctx.plan.shaper.preprocess_text { |
331 | func(ctx.plan, ctx.face, ctx.buffer); |
332 | } |
333 | |
334 | substitute_pre(ctx); |
335 | position(ctx); |
336 | substitute_post(ctx); |
337 | |
338 | propagate_flags(ctx.buffer); |
339 | |
340 | ctx.buffer.direction = ctx.target_direction; |
341 | ctx.buffer.leave(); |
342 | } |
343 | |
344 | fn substitute_pre(ctx: &mut hb_ot_shape_context_t) { |
345 | hb_ot_substitute_default(ctx); |
346 | hb_ot_substitute_plan(ctx); |
347 | |
348 | if ctx.plan.apply_morx && ctx.plan.apply_gpos { |
349 | hb_aat_layout_remove_deleted_glyphs(ctx.buffer); |
350 | } |
351 | } |
352 | |
353 | fn substitute_post(ctx: &mut hb_ot_shape_context_t) { |
354 | if ctx.plan.apply_morx && !ctx.plan.apply_gpos { |
355 | aat_layout::hb_aat_layout_remove_deleted_glyphs(ctx.buffer); |
356 | } |
357 | |
358 | deal_with_variation_selectors(ctx.buffer); |
359 | hide_default_ignorables(ctx.buffer, ctx.face); |
360 | |
361 | if let Some(func: fn(&hb_ot_shape_plan_t, &hb_font_t<'_>, …)) = ctx.plan.shaper.postprocess_glyphs { |
362 | func(ctx.plan, ctx.face, ctx.buffer); |
363 | } |
364 | } |
365 | |
366 | fn hb_ot_substitute_default(ctx: &mut hb_ot_shape_context_t) { |
367 | rotate_chars(ctx); |
368 | |
369 | ot_shape_normalize::_hb_ot_shape_normalize(ctx.plan, ctx.buffer, ctx.face); |
370 | |
371 | setup_masks(ctx); |
372 | |
373 | // This is unfortunate to go here, but necessary... |
374 | if ctx.plan.fallback_mark_positioning { |
375 | ot_shape_fallback::_hb_ot_shape_fallback_mark_position_recategorize_marks( |
376 | ctx.plan, ctx.face, ctx.buffer, |
377 | ); |
378 | } |
379 | |
380 | map_glyphs_fast(ctx.buffer); |
381 | } |
382 | |
383 | fn hb_ot_substitute_plan(ctx: &mut hb_ot_shape_context_t) { |
384 | hb_ot_layout_substitute_start(ctx.face, ctx.buffer); |
385 | |
386 | if ctx.plan.fallback_glyph_classes { |
387 | hb_synthesize_glyph_classes(ctx.buffer); |
388 | } |
389 | |
390 | if ctx.plan.apply_morx { |
391 | aat_layout::hb_aat_layout_substitute(ctx.plan, ctx.face, ctx.buffer); |
392 | } else { |
393 | super::ot_layout_gsub_table::substitute(ctx.plan, ctx.face, ctx.buffer); |
394 | } |
395 | } |
396 | |
397 | fn position(ctx: &mut hb_ot_shape_context_t) { |
398 | ctx.buffer.clear_positions(); |
399 | |
400 | position_default(ctx); |
401 | |
402 | position_complex(ctx); |
403 | |
404 | if ctx.buffer.direction.is_backward() { |
405 | ctx.buffer.reverse(); |
406 | } |
407 | } |
408 | |
409 | fn position_default(ctx: &mut hb_ot_shape_context_t) { |
410 | let len = ctx.buffer.len; |
411 | |
412 | if ctx.buffer.direction.is_horizontal() { |
413 | for (info, pos) in ctx.buffer.info[..len] |
414 | .iter() |
415 | .zip(&mut ctx.buffer.pos[..len]) |
416 | { |
417 | pos.x_advance = ctx.face.glyph_h_advance(info.as_glyph()); |
418 | } |
419 | } else { |
420 | for (info, pos) in ctx.buffer.info[..len] |
421 | .iter() |
422 | .zip(&mut ctx.buffer.pos[..len]) |
423 | { |
424 | let glyph = info.as_glyph(); |
425 | pos.y_advance = ctx.face.glyph_v_advance(glyph); |
426 | pos.x_offset -= ctx.face.glyph_h_origin(glyph); |
427 | pos.y_offset -= ctx.face.glyph_v_origin(glyph); |
428 | } |
429 | } |
430 | |
431 | if ctx.buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_SPACE_FALLBACK != 0 { |
432 | ot_shape_fallback::_hb_ot_shape_fallback_spaces(ctx.plan, ctx.face, ctx.buffer); |
433 | } |
434 | } |
435 | |
436 | fn position_complex(ctx: &mut hb_ot_shape_context_t) { |
437 | // If the font has no GPOS and direction is forward, then when |
438 | // zeroing mark widths, we shift the mark with it, such that the |
439 | // mark is positioned hanging over the previous glyph. When |
440 | // direction is backward we don't shift and it will end up |
441 | // hanging over the next glyph after the final reordering. |
442 | // |
443 | // Note: If fallback positioning happens, we don't care about |
444 | // this as it will be overridden. |
445 | let adjust_offsets_when_zeroing = |
446 | ctx.plan.adjust_mark_positioning_when_zeroing && ctx.buffer.direction.is_forward(); |
447 | |
448 | // We change glyph origin to what GPOS expects (horizontal), apply GPOS, change it back. |
449 | |
450 | GPOS::position_start(ctx.face, ctx.buffer); |
451 | |
452 | if ctx.plan.zero_marks |
453 | && ctx.plan.shaper.zero_width_marks == HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY |
454 | { |
455 | zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing); |
456 | } |
457 | |
458 | position_by_plan(ctx.plan, ctx.face, ctx.buffer); |
459 | |
460 | if ctx.plan.zero_marks |
461 | && ctx.plan.shaper.zero_width_marks == HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE |
462 | { |
463 | zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing); |
464 | } |
465 | |
466 | // Finish off. Has to follow a certain order. |
467 | GPOS::position_finish_advances(ctx.face, ctx.buffer); |
468 | zero_width_default_ignorables(ctx.buffer); |
469 | |
470 | if ctx.plan.apply_morx { |
471 | aat_layout::hb_aat_layout_zero_width_deleted_glyphs(ctx.buffer); |
472 | } |
473 | |
474 | GPOS::position_finish_offsets(ctx.face, ctx.buffer); |
475 | |
476 | if ctx.plan.fallback_mark_positioning { |
477 | ot_shape_fallback::position_marks( |
478 | ctx.plan, |
479 | ctx.face, |
480 | ctx.buffer, |
481 | adjust_offsets_when_zeroing, |
482 | ); |
483 | } |
484 | } |
485 | |
486 | fn position_by_plan(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) { |
487 | if plan.apply_gpos { |
488 | super::ot_layout_gpos_table::position(plan, face, buffer); |
489 | } else if plan.apply_kerx { |
490 | aat_layout::hb_aat_layout_position(plan, face, buffer); |
491 | } |
492 | if plan.apply_kern { |
493 | super::kerning::hb_ot_layout_kern(plan, face, buffer); |
494 | } else if plan.apply_fallback_kern { |
495 | ot_shape_fallback::_hb_ot_shape_fallback_kern(plan, face, buffer); |
496 | } |
497 | |
498 | if plan.apply_trak { |
499 | aat_layout::hb_aat_layout_track(plan, face, buffer); |
500 | } |
501 | } |
502 | |
503 | fn initialize_masks(ctx: &mut hb_ot_shape_context_t) { |
504 | let global_mask: u32 = ctx.plan.ot_map.get_global_mask(); |
505 | ctx.buffer.reset_masks(global_mask); |
506 | } |
507 | |
508 | fn setup_masks(ctx: &mut hb_ot_shape_context_t) { |
509 | setup_masks_fraction(ctx); |
510 | |
511 | if let Some(func: fn(&hb_ot_shape_plan_t, &hb_font_t<'_>, …)) = ctx.plan.shaper.setup_masks { |
512 | func(ctx.plan, ctx.face, ctx.buffer); |
513 | } |
514 | |
515 | for feature: &Feature in &ctx.plan.user_features { |
516 | if !feature.is_global() { |
517 | let (mask: u32, shift: u32) = ctx.plan.ot_map.get_mask(feature.tag); |
518 | ctx.buffer |
519 | .set_masks(value:feature.value << shift, mask, cluster_start:feature.start, cluster_end:feature.end); |
520 | } |
521 | } |
522 | } |
523 | |
524 | fn setup_masks_fraction(ctx: &mut hb_ot_shape_context_t) { |
525 | let buffer = &mut ctx.buffer; |
526 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII == 0 || !ctx.plan.has_frac { |
527 | return; |
528 | } |
529 | |
530 | let (pre_mask, post_mask) = if buffer.direction.is_forward() { |
531 | ( |
532 | ctx.plan.numr_mask | ctx.plan.frac_mask, |
533 | ctx.plan.frac_mask | ctx.plan.dnom_mask, |
534 | ) |
535 | } else { |
536 | ( |
537 | ctx.plan.frac_mask | ctx.plan.dnom_mask, |
538 | ctx.plan.numr_mask | ctx.plan.frac_mask, |
539 | ) |
540 | }; |
541 | |
542 | let len = buffer.len; |
543 | let mut i = 0; |
544 | while i < len { |
545 | // FRACTION SLASH |
546 | if buffer.info[i].glyph_id == 0x2044 { |
547 | let mut start = i; |
548 | while start > 0 |
549 | && _hb_glyph_info_get_general_category(&buffer.info[start - 1]) |
550 | == hb_unicode_general_category_t::DecimalNumber |
551 | { |
552 | start -= 1; |
553 | } |
554 | |
555 | let mut end = i + 1; |
556 | while end < len |
557 | && _hb_glyph_info_get_general_category(&buffer.info[end]) |
558 | == hb_unicode_general_category_t::DecimalNumber |
559 | { |
560 | end += 1; |
561 | } |
562 | |
563 | if start == i || end == i + 1 { |
564 | if start == i { |
565 | buffer.unsafe_to_concat(Some(start), Some(start + 1)); |
566 | } |
567 | |
568 | if end == i + 1 { |
569 | buffer.unsafe_to_concat(Some(end - 1), Some(end)); |
570 | } |
571 | |
572 | i += 1; |
573 | continue; |
574 | } |
575 | |
576 | buffer.unsafe_to_break(Some(start), Some(end)); |
577 | |
578 | for info in &mut buffer.info[start..i] { |
579 | info.mask |= pre_mask; |
580 | } |
581 | |
582 | buffer.info[i].mask |= ctx.plan.frac_mask; |
583 | |
584 | for info in &mut buffer.info[i + 1..end] { |
585 | info.mask |= post_mask; |
586 | } |
587 | |
588 | i = end; |
589 | } else { |
590 | i += 1; |
591 | } |
592 | } |
593 | } |
594 | |
595 | fn set_unicode_props(buffer: &mut hb_buffer_t) { |
596 | // Implement enough of Unicode Graphemes here that shaping |
597 | // in reverse-direction wouldn't break graphemes. Namely, |
598 | // we mark all marks and ZWJ and ZWJ,Extended_Pictographic |
599 | // sequences as continuations. The foreach_grapheme() |
600 | // macro uses this bit. |
601 | // |
602 | // https://www.unicode.org/reports/tr29/#Regex_Definitions |
603 | |
604 | let len = buffer.len; |
605 | |
606 | let mut i = 0; |
607 | while i < len { |
608 | // Mutably borrow buffer.info[i] and immutably borrow |
609 | // buffer.info[i - 1] (if present) in a way that the borrow |
610 | // checker can understand. |
611 | let (prior, later) = buffer.info.split_at_mut(i); |
612 | let info = &mut later[0]; |
613 | info.init_unicode_props(&mut buffer.scratch_flags); |
614 | |
615 | let gen_cat = _hb_glyph_info_get_general_category(info); |
616 | |
617 | if (rb_flag_unsafe(gen_cat.to_rb()) |
618 | & (rb_flag(RB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER) |
619 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER) |
620 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER) |
621 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER) |
622 | | rb_flag(RB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR))) |
623 | != 0 |
624 | { |
625 | i += 1; |
626 | continue; |
627 | } |
628 | |
629 | // Marks are already set as continuation by the above line. |
630 | // Handle Emoji_Modifier and ZWJ-continuation. |
631 | if gen_cat == hb_unicode_general_category_t::ModifierSymbol |
632 | && matches!(info.glyph_id, 0x1F3FB..=0x1F3FF) |
633 | { |
634 | _hb_glyph_info_set_continuation(info); |
635 | } else if i != 0 && matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) { |
636 | // Should never fail because we checked for i > 0. |
637 | // TODO: use let chains when they become stable |
638 | let prev = prior.last().unwrap(); |
639 | if matches!(prev.glyph_id, 0x1F1E6..=0x1F1FF) && !_hb_glyph_info_is_continuation(prev) { |
640 | _hb_glyph_info_set_continuation(info); |
641 | } |
642 | } else if _hb_glyph_info_is_zwj(info) { |
643 | _hb_glyph_info_set_continuation(info); |
644 | if let Some(next) = buffer.info[..len].get_mut(i + 1) { |
645 | if next.as_char().is_emoji_extended_pictographic() { |
646 | next.init_unicode_props(&mut buffer.scratch_flags); |
647 | _hb_glyph_info_set_continuation(next); |
648 | i += 1; |
649 | } |
650 | } |
651 | } else if matches!(info.glyph_id, 0xFF9E..=0xFF9F | 0xE0020..=0xE007F) { |
652 | // Or part of the Other_Grapheme_Extend that is not marks. |
653 | // As of Unicode 15 that is just: |
654 | // |
655 | // 200C ; Other_Grapheme_Extend # Cf ZERO WIDTH NON-JOINER |
656 | // FF9E..FF9F ; Other_Grapheme_Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA |
657 | // SEMI-VOICED SOUND MARK E0020..E007F ; Other_Grapheme_Extend # Cf [96] TAG SPACE..CANCEL TAG |
658 | // |
659 | // ZWNJ is special, we don't want to merge it as there's no need, and keeping |
660 | // it separate results in more granular clusters. |
661 | // Tags are used for Emoji sub-region flag sequences: |
662 | // https://github.com/harfbuzz/harfbuzz/issues/1556 |
663 | // Katakana ones were requested: |
664 | // https://github.com/harfbuzz/harfbuzz/issues/3844 |
665 | _hb_glyph_info_set_continuation(info); |
666 | } |
667 | |
668 | i += 1; |
669 | } |
670 | } |
671 | |
672 | pub(crate) fn syllabic_clear_var( |
673 | _: &hb_ot_shape_plan_t, |
674 | _: &hb_font_t, |
675 | buffer: &mut hb_buffer_t, |
676 | ) -> bool { |
677 | for info: &mut hb_glyph_info_t in &mut buffer.info { |
678 | info.set_syllable(0); |
679 | } |
680 | |
681 | false |
682 | } |
683 | |
684 | fn insert_dotted_circle(buffer: &mut hb_buffer_t, face: &hb_font_t) { |
685 | if !bufferBufferFlags |
686 | .flags |
687 | .contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE) |
688 | && buffer.flags.contains(BufferFlags::BEGINNING_OF_TEXT) |
689 | && buffer.context_len[0] == 0 |
690 | && _hb_glyph_info_is_unicode_mark(&buffer.info[0]) |
691 | && face.has_glyph(0x25CC) |
692 | { |
693 | let mut info: hb_glyph_info_t = hb_glyph_info_t { |
694 | glyph_id: 0x25CC, |
695 | mask: buffer.cur(0).mask, |
696 | cluster: buffer.cur(0).cluster, |
697 | ..hb_glyph_info_t::default() |
698 | }; |
699 | |
700 | info.init_unicode_props(&mut buffer.scratch_flags); |
701 | buffer.clear_output(); |
702 | buffer.output_info(info); |
703 | buffer.sync(); |
704 | } |
705 | } |
706 | |
707 | fn form_clusters(buffer: &mut hb_buffer_t) { |
708 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII != 0 { |
709 | if buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES { |
710 | foreach_grapheme!(buffer, start, end, { buffer.merge_clusters(start, end) }); |
711 | } else { |
712 | foreach_grapheme!(buffer, start, end, { |
713 | buffer.unsafe_to_break(Some(start), Some(end)); |
714 | }); |
715 | } |
716 | } |
717 | } |
718 | |
719 | fn ensure_native_direction(buffer: &mut hb_buffer_t) { |
720 | let dir = buffer.direction; |
721 | let mut hor = buffer |
722 | .script |
723 | .and_then(Direction::from_script) |
724 | .unwrap_or_default(); |
725 | |
726 | // Numeric runs in natively-RTL scripts are actually native-LTR, so we reset |
727 | // the horiz_dir if the run contains at least one decimal-number char, and no |
728 | // letter chars (ideally we should be checking for chars with strong |
729 | // directionality but hb-unicode currently lacks bidi categories). |
730 | // |
731 | // This allows digit sequences in Arabic etc to be shaped in "native" |
732 | // direction, so that features like ligatures will work as intended. |
733 | // |
734 | // https://github.com/harfbuzz/harfbuzz/issues/501 |
735 | // |
736 | // Similar thing about Regional_Indicators; They are bidi=L, but Script=Common. |
737 | // If they are present in a run of natively-RTL text, they get assigned a script |
738 | // with natively RTL direction, which would result in wrong shaping if we |
739 | // assign such native RTL direction to them then. Detect that as well. |
740 | // |
741 | // https://github.com/harfbuzz/harfbuzz/issues/3314 |
742 | |
743 | if hor == Direction::RightToLeft && dir == Direction::LeftToRight { |
744 | let mut found_number = false; |
745 | let mut found_letter = false; |
746 | let mut found_ri = false; |
747 | for info in &buffer.info { |
748 | let gc = _hb_glyph_info_get_general_category(info); |
749 | if gc == hb_unicode_general_category_t::DecimalNumber { |
750 | found_number = true; |
751 | } else if gc.is_letter() { |
752 | found_letter = true; |
753 | break; |
754 | } else if matches!(info.glyph_id, 0x1F1E6..=0x1F1FF) { |
755 | found_ri = true; |
756 | } |
757 | } |
758 | if (found_number || found_ri) && !found_letter { |
759 | hor = Direction::LeftToRight; |
760 | } |
761 | } |
762 | |
763 | // TODO vertical: |
764 | // The only BTT vertical script is Ogham, but it's not clear to me whether OpenType |
765 | // Ogham fonts are supposed to be implemented BTT or not. Need to research that |
766 | // first. |
767 | if (dir.is_horizontal() && dir != hor && hor != Direction::Invalid) |
768 | || (dir.is_vertical() && dir != Direction::TopToBottom) |
769 | { |
770 | _hb_ot_layout_reverse_graphemes(buffer); |
771 | buffer.direction = buffer.direction.reverse(); |
772 | } |
773 | } |
774 | |
775 | fn rotate_chars(ctx: &mut hb_ot_shape_context_t) { |
776 | let len = ctx.buffer.len; |
777 | |
778 | if ctx.target_direction.is_backward() { |
779 | let rtlm_mask = ctx.plan.rtlm_mask; |
780 | |
781 | for info in &mut ctx.buffer.info[..len] { |
782 | if let Some(c) = info.as_char().mirrored().map(u32::from) { |
783 | if ctx.face.has_glyph(c) { |
784 | info.glyph_id = c; |
785 | continue; |
786 | } |
787 | } |
788 | info.mask |= rtlm_mask; |
789 | } |
790 | } |
791 | |
792 | if ctx.target_direction.is_vertical() && !ctx.plan.has_vert { |
793 | for info in &mut ctx.buffer.info[..len] { |
794 | if let Some(c) = info.as_char().vertical().map(u32::from) { |
795 | if ctx.face.has_glyph(c) { |
796 | info.glyph_id = c; |
797 | } |
798 | } |
799 | } |
800 | } |
801 | } |
802 | |
803 | fn map_glyphs_fast(buffer: &mut hb_buffer_t) { |
804 | // Normalization process sets up glyph_index(), we just copy it. |
805 | let len: usize = buffer.len; |
806 | for info: &mut hb_glyph_info_t in &mut buffer.info[..len] { |
807 | info.glyph_id = info.glyph_index(); |
808 | } |
809 | |
810 | for info: &mut hb_glyph_info_t in &mut buffer.out_info_mut()[..len] { |
811 | info.glyph_id = info.glyph_index(); |
812 | } |
813 | } |
814 | |
815 | fn hb_synthesize_glyph_classes(buffer: &mut hb_buffer_t) { |
816 | let len: usize = buffer.len; |
817 | for info: &mut hb_glyph_info_t in &mut buffer.info[..len] { |
818 | // Never mark default-ignorables as marks. |
819 | // They won't get in the way of lookups anyway, |
820 | // but having them as mark will cause them to be skipped |
821 | // over if the lookup-flag says so, but at least for the |
822 | // Mongolian variation selectors, looks like Uniscribe |
823 | // marks them as non-mark. Some Mongolian fonts without |
824 | // GDEF rely on this. Another notable character that |
825 | // this applies to is COMBINING GRAPHEME JOINER. |
826 | let class: GlyphPropsFlags = if _hb_glyph_info_get_general_category(info) |
827 | != hb_unicode_general_category_t::NonspacingMark |
828 | || _hb_glyph_info_is_default_ignorable(info) |
829 | { |
830 | GlyphPropsFlags::BASE_GLYPH |
831 | } else { |
832 | GlyphPropsFlags::MARK |
833 | }; |
834 | |
835 | info.set_glyph_props(class.bits()); |
836 | } |
837 | } |
838 | |
839 | fn zero_width_default_ignorables(buffer: &mut hb_buffer_t) { |
840 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0 |
841 | && !bufferBufferFlags |
842 | .flags |
843 | .contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES) |
844 | && !bufferBufferFlags |
845 | .flags |
846 | .contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES) |
847 | { |
848 | let len: usize = buffer.len; |
849 | for (info: &hb_glyph_info_t, pos: &mut GlyphPosition) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) { |
850 | if _hb_glyph_info_is_default_ignorable(info) { |
851 | pos.x_advance = 0; |
852 | pos.y_advance = 0; |
853 | pos.x_offset = 0; |
854 | pos.y_offset = 0; |
855 | } |
856 | } |
857 | } |
858 | } |
859 | |
860 | fn deal_with_variation_selectors(buffer: &mut hb_buffer_t) { |
861 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_VARIATION_SELECTOR_FALLBACK == 0 { |
862 | return; |
863 | } |
864 | |
865 | // Note: In harfbuzz, this is part of the condition above (with OR), so it needs to stay |
866 | // in sync. |
867 | let Some(nf) = buffer.not_found_variation_selector else { |
868 | return; |
869 | }; |
870 | |
871 | let count = buffer.len; |
872 | let info = &mut buffer.info; |
873 | let pos = &mut buffer.pos; |
874 | |
875 | for i in 0..count { |
876 | if _hb_glyph_info_is_variation_selector(&info[i]) { |
877 | info[i].glyph_id = nf; |
878 | pos[i].x_advance = 0; |
879 | pos[i].y_advance = 0; |
880 | pos[i].x_offset = 0; |
881 | pos[i].y_offset = 0; |
882 | _hb_glyph_info_set_variation_selector(&mut info[i], false); |
883 | } |
884 | } |
885 | } |
886 | |
887 | fn zero_mark_widths_by_gdef(buffer: &mut hb_buffer_t, adjust_offsets: bool) { |
888 | let len: usize = buffer.len; |
889 | for (info: &hb_glyph_info_t, pos: &mut GlyphPosition) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) { |
890 | if _hb_glyph_info_is_mark(info) { |
891 | if adjust_offsets { |
892 | pos.x_offset -= pos.x_advance; |
893 | pos.y_offset -= pos.y_advance; |
894 | } |
895 | |
896 | pos.x_advance = 0; |
897 | pos.y_advance = 0; |
898 | } |
899 | } |
900 | } |
901 | |
902 | fn hide_default_ignorables(buffer: &mut hb_buffer_t, face: &hb_font_t) { |
903 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES != 0 |
904 | && !buffer |
905 | .flags |
906 | .contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES) |
907 | { |
908 | if !buffer |
909 | .flags |
910 | .contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES) |
911 | { |
912 | if let Some(invisible) = buffer |
913 | .invisible |
914 | .or_else(|| face.get_nominal_glyph(u32::from(' ' ))) |
915 | { |
916 | let len = buffer.len; |
917 | for info in &mut buffer.info[..len] { |
918 | if _hb_glyph_info_is_default_ignorable(info) { |
919 | info.glyph_id = u32::from(invisible.0); |
920 | } |
921 | } |
922 | return; |
923 | } |
924 | } |
925 | |
926 | buffer.delete_glyphs_inplace(_hb_glyph_info_is_default_ignorable); |
927 | } |
928 | } |
929 | |
930 | fn propagate_flags(buffer: &mut hb_buffer_t) { |
931 | // Propagate cluster-level glyph flags to be the same on all cluster glyphs. |
932 | // Simplifies using them. |
933 | |
934 | if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GLYPH_FLAGS == 0 { |
935 | return; |
936 | } |
937 | |
938 | /* If we are producing SAFE_TO_INSERT_TATWEEL, then do two things: |
939 | * |
940 | * - If the places that the Arabic shaper marked as SAFE_TO_INSERT_TATWEEL, |
941 | * are UNSAFE_TO_BREAK, then clear the SAFE_TO_INSERT_TATWEEL, |
942 | * - Any place that is SAFE_TO_INSERT_TATWEEL, is also now UNSAFE_TO_BREAK. |
943 | * |
944 | * We couldn't make this interaction earlier. It has to be done here. |
945 | */ |
946 | let flip_tatweel = buffer |
947 | .flags |
948 | .contains(BufferFlags::PRODUCE_SAFE_TO_INSERT_TATWEEL); |
949 | |
950 | let clear_concat = !buffer.flags.contains(BufferFlags::PRODUCE_UNSAFE_TO_CONCAT); |
951 | |
952 | foreach_cluster!(buffer, start, end, { |
953 | let mut mask = 0; |
954 | for info in &buffer.info[start..end] { |
955 | mask |= info.mask & glyph_flag::DEFINED; |
956 | } |
957 | |
958 | if flip_tatweel { |
959 | if mask & UNSAFE_TO_BREAK != 0 { |
960 | mask &= !SAFE_TO_INSERT_TATWEEL; |
961 | } |
962 | |
963 | if mask & SAFE_TO_INSERT_TATWEEL != 0 { |
964 | mask |= UNSAFE_TO_BREAK | UNSAFE_TO_CONCAT; |
965 | } |
966 | } |
967 | |
968 | if clear_concat { |
969 | mask &= !UNSAFE_TO_CONCAT; |
970 | |
971 | for info in &mut buffer.info[start..end] { |
972 | info.mask = mask; |
973 | } |
974 | } |
975 | }); |
976 | } |
977 | |