rustybuzz/hb/
ot_shape.rs

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) = 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) = 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 = 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) = ctx.plan.shaper.setup_masks {
512        func(ctx.plan, ctx.face, ctx.buffer);
513    }
514
515    for feature in &ctx.plan.user_features {
516        if !feature.is_global() {
517            let (mask, shift) = ctx.plan.ot_map.get_mask(feature.tag);
518            ctx.buffer
519                .set_masks(feature.value << shift, mask, feature.start, 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 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 !buffer
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 {
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 = buffer.len;
806    for info in &mut buffer.info[..len] {
807        info.glyph_id = info.glyph_index();
808    }
809
810    for info 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 = buffer.len;
817    for info 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 = 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        && !buffer
842            .flags
843            .contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES)
844        && !buffer
845            .flags
846            .contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES)
847    {
848        let len = buffer.len;
849        for (info, pos) 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 = buffer.len;
889    for (info, pos) 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}