1use super::buffer::*;
2use super::ot_layout::*;
3use super::ot_layout_gpos_table::GPOS;
4use super::ot_map::*;
5use super::ot_shape_plan::hb_ot_shape_plan_t;
6use super::ot_shaper::*;
7use super::unicode::{hb_unicode_general_category_t, CharExt, GeneralCategoryExt};
8use super::*;
9use super::{hb_font_t, hb_tag_t};
10use crate::hb::aat_layout::hb_aat_layout_remove_deleted_glyphs;
11use crate::hb::algs::{rb_flag, rb_flag_unsafe};
12use crate::hb::buffer::glyph_flag::{SAFE_TO_INSERT_TATWEEL, UNSAFE_TO_BREAK, UNSAFE_TO_CONCAT};
13use 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};
18use crate::BufferFlags;
19use crate::{Direction, Feature, Language, Script};
20
21pub 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
32impl<'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
310pub 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!
319pub 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
344fn 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
353fn 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
366fn 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
383fn 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
397fn 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
409fn 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
436fn 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
486fn 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
503fn 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
508fn 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
524fn 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
595fn 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
672pub(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
684fn 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
707fn 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
719fn 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
775fn 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
803fn 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
815fn 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
839fn 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
860fn 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
887fn 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
902fn 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
930fn 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