rustybuzz/hb/
ot_shape.rs

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