Skip to main content

harfrust/hb/
ot_shape.rs

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