rustybuzz/hb/
ot_layout.rs

1//! OpenType layout.
2
3use core::ops::{Index, IndexMut};
4
5use super::buffer::*;
6use super::common::TagExt;
7use super::ot_layout_gsubgpos::{Apply, OT};
8use super::ot_shape_plan::hb_ot_shape_plan_t;
9use super::unicode::{hb_unicode_funcs_t, hb_unicode_general_category_t, GeneralCategoryExt};
10use super::{hb_font_t, hb_glyph_info_t, hb_tag_t};
11use crate::hb::set_digest::{hb_set_digest_ext, hb_set_digest_t};
12use ttf_parser::opentype_layout::{FeatureIndex, LanguageIndex, LookupIndex, ScriptIndex};
13
14pub const MAX_NESTING_LEVEL: usize = 64;
15pub const MAX_CONTEXT_LENGTH: usize = 64;
16
17pub fn hb_ot_layout_has_kerning(face: &hb_font_t) -> bool {
18    face.tables().kern.is_some()
19}
20
21pub fn hb_ot_layout_has_machine_kerning(face: &hb_font_t) -> bool {
22    match face.tables().kern {
23        Some(ref kern) => kern.subtables.into_iter().any(|s| s.has_state_machine),
24        None => false,
25    }
26}
27
28pub fn hb_ot_layout_has_cross_kerning(face: &hb_font_t) -> bool {
29    match face.tables().kern {
30        Some(ref kern) => kern.subtables.into_iter().any(|s| s.has_cross_stream),
31        None => false,
32    }
33}
34
35// hb_ot_layout_kern
36
37// OT::GDEF::is_blocklisted unsupported
38
39pub fn _hb_ot_layout_set_glyph_props(face: &hb_font_t, buffer: &mut hb_buffer_t) {
40    let len = buffer.len;
41    for info in &mut buffer.info[..len] {
42        info.set_glyph_props(face.glyph_props(info.as_glyph()));
43        info.set_lig_props(0);
44    }
45}
46
47pub fn hb_ot_layout_has_glyph_classes(face: &hb_font_t) -> bool {
48    face.tables()
49        .gdef
50        .map_or(false, |table| table.has_glyph_classes())
51}
52
53// get_gsubgpos_table
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum TableIndex {
57    GSUB = 0,
58    GPOS = 1,
59}
60
61impl TableIndex {
62    pub fn iter() -> impl Iterator<Item = TableIndex> {
63        [Self::GSUB, Self::GPOS].iter().copied()
64    }
65}
66
67impl<T> Index<TableIndex> for [T] {
68    type Output = T;
69
70    fn index(&self, table_index: TableIndex) -> &Self::Output {
71        &self[table_index as usize]
72    }
73}
74
75impl<T> IndexMut<TableIndex> for [T] {
76    fn index_mut(&mut self, table_index: TableIndex) -> &mut Self::Output {
77        &mut self[table_index as usize]
78    }
79}
80
81/// A lookup-based layout table (GSUB or GPOS).
82pub trait LayoutTable {
83    /// The index of this table.
84    const INDEX: TableIndex;
85
86    /// Whether lookups in this table can be applied to the buffer in-place.
87    const IN_PLACE: bool;
88
89    /// The kind of lookup stored in this table.
90    type Lookup: LayoutLookup;
91
92    /// Get the lookup at the specified index.
93    fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup>;
94}
95
96/// A lookup in a layout table.
97pub trait LayoutLookup: Apply {
98    /// The lookup's lookup_props.
99    fn props(&self) -> u32;
100
101    /// Whether the lookup has to be applied backwards.
102    fn is_reverse(&self) -> bool;
103
104    /// The digest of the lookup.
105    fn digest(&self) -> &hb_set_digest_t;
106}
107
108pub trait LayoutTableExt {
109    fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, ScriptIndex, hb_tag_t)>;
110    fn select_script_language(
111        &self,
112        script_index: ScriptIndex,
113        lang_tags: &[hb_tag_t],
114    ) -> Option<LanguageIndex>;
115    fn get_required_language_feature(
116        &self,
117        script_index: ScriptIndex,
118        lang_index: Option<LanguageIndex>,
119    ) -> Option<(FeatureIndex, hb_tag_t)>;
120    fn find_language_feature(
121        &self,
122        script_index: ScriptIndex,
123        lang_index: Option<LanguageIndex>,
124        feature_tag: hb_tag_t,
125    ) -> Option<FeatureIndex>;
126}
127
128impl LayoutTableExt for ttf_parser::opentype_layout::LayoutTable<'_> {
129    // hb_ot_layout_table_select_script
130    /// Returns true + index and tag of the first found script tag in the given GSUB or GPOS table
131    /// or false + index and tag if falling back to a default script.
132    fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, ScriptIndex, hb_tag_t)> {
133        for &tag in script_tags {
134            if let Some(index) = self.scripts.index(tag) {
135                return Some((true, index, tag));
136            }
137        }
138
139        for &tag in &[
140            // try finding 'DFLT'
141            hb_tag_t::default_script(),
142            // try with 'dflt'; MS site has had typos and many fonts use it now :(
143            hb_tag_t::default_language(),
144            // try with 'latn'; some old fonts put their features there even though
145            // they're really trying to support Thai, for example :(
146            hb_tag_t::from_bytes(b"latn"),
147        ] {
148            if let Some(index) = self.scripts.index(tag) {
149                return Some((false, index, tag));
150            }
151        }
152
153        None
154    }
155
156    // hb_ot_layout_script_select_language
157    /// Returns the index of the first found language tag in the given GSUB or GPOS table,
158    /// underneath the specified script index.
159    fn select_script_language(
160        &self,
161        script_index: ScriptIndex,
162        lang_tags: &[hb_tag_t],
163    ) -> Option<LanguageIndex> {
164        let script = self.scripts.get(script_index)?;
165
166        for &tag in lang_tags {
167            if let Some(index) = script.languages.index(tag) {
168                return Some(index);
169            }
170        }
171
172        // try finding 'dflt'
173        if let Some(index) = script.languages.index(hb_tag_t::default_language()) {
174            return Some(index);
175        }
176
177        None
178    }
179
180    // hb_ot_layout_language_get_required_feature
181    /// Returns the index and tag of a required feature in the given GSUB or GPOS table,
182    /// underneath the specified script and language.
183    fn get_required_language_feature(
184        &self,
185        script_index: ScriptIndex,
186        lang_index: Option<LanguageIndex>,
187    ) -> Option<(FeatureIndex, hb_tag_t)> {
188        let script = self.scripts.get(script_index)?;
189        let sys = match lang_index {
190            Some(index) => script.languages.get(index)?,
191            None => script.default_language?,
192        };
193        let idx = sys.required_feature?;
194        let tag = self.features.get(idx)?.tag;
195        Some((idx, tag))
196    }
197
198    // hb_ot_layout_language_find_feature
199    /// Returns the index of a given feature tag in the given GSUB or GPOS table,
200    /// underneath the specified script and language.
201    fn find_language_feature(
202        &self,
203        script_index: ScriptIndex,
204        lang_index: Option<LanguageIndex>,
205        feature_tag: hb_tag_t,
206    ) -> Option<FeatureIndex> {
207        let script = self.scripts.get(script_index)?;
208        let sys = match lang_index {
209            Some(index) => script.languages.get(index)?,
210            None => script.default_language?,
211        };
212
213        for i in 0..sys.feature_indices.len() {
214            if let Some(index) = sys.feature_indices.get(i) {
215                if self.features.get(index).map(|v| v.tag) == Some(feature_tag) {
216                    return Some(index);
217                }
218            }
219        }
220
221        None
222    }
223}
224
225/// Called before substitution lookups are performed, to ensure that glyph
226/// class and other properties are set on the glyphs in the buffer.
227pub fn hb_ot_layout_substitute_start(face: &hb_font_t, buffer: &mut hb_buffer_t) {
228    _hb_ot_layout_set_glyph_props(face, buffer)
229}
230
231/// Applies the lookups in the given GSUB or GPOS table.
232pub fn apply_layout_table<T: LayoutTable>(
233    plan: &hb_ot_shape_plan_t,
234    face: &hb_font_t,
235    buffer: &mut hb_buffer_t,
236    table: Option<&T>,
237) {
238    let mut ctx = OT::hb_ot_apply_context_t::new(T::INDEX, face, buffer);
239
240    for (stage_index, stage) in plan.ot_map.stages(T::INDEX).iter().enumerate() {
241        if let Some(table) = table {
242            for lookup_map in plan.ot_map.stage_lookups(T::INDEX, stage_index) {
243                let Some(lookup) = table.get_lookup(lookup_map.index) else {
244                    continue;
245                };
246
247                if lookup.digest().may_have(&ctx.digest) {
248                    ctx.lookup_index = lookup_map.index;
249                    ctx.set_lookup_mask(lookup_map.mask);
250                    ctx.auto_zwj = lookup_map.auto_zwj;
251                    ctx.auto_zwnj = lookup_map.auto_zwnj;
252
253                    ctx.random = lookup_map.random;
254                    ctx.per_syllable = lookup_map.per_syllable;
255
256                    apply_string::<T>(&mut ctx, lookup);
257                }
258            }
259        }
260
261        if let Some(func) = stage.pause_func {
262            if func(plan, face, ctx.buffer) {
263                ctx.digest = ctx.buffer.digest();
264            }
265        }
266    }
267}
268
269fn apply_string<T: LayoutTable>(ctx: &mut OT::hb_ot_apply_context_t, lookup: &T::Lookup) {
270    if ctx.buffer.is_empty() || ctx.lookup_mask() == 0 {
271        return;
272    }
273
274    ctx.lookup_props = lookup.props();
275
276    if !lookup.is_reverse() {
277        // in/out forward substitution/positioning
278        if !T::IN_PLACE {
279            ctx.buffer.clear_output();
280        }
281        ctx.buffer.idx = 0;
282        apply_forward(ctx, lookup);
283
284        if !T::IN_PLACE {
285            ctx.buffer.sync();
286        }
287    } else {
288        // in-place backward substitution/positioning
289        assert!(!ctx.buffer.have_output);
290
291        ctx.buffer.idx = ctx.buffer.len - 1;
292        apply_backward(ctx, lookup);
293    }
294}
295
296fn apply_forward(ctx: &mut OT::hb_ot_apply_context_t, lookup: &impl Apply) -> bool {
297    let mut ret = false;
298    while ctx.buffer.idx < ctx.buffer.len && ctx.buffer.successful {
299        let cur = ctx.buffer.cur(0);
300        if (cur.mask & ctx.lookup_mask()) != 0
301            && ctx.check_glyph_property(cur, ctx.lookup_props)
302            && lookup.apply(ctx).is_some()
303        {
304            ret = true;
305        } else {
306            ctx.buffer.next_glyph();
307        }
308    }
309    ret
310}
311
312fn apply_backward(ctx: &mut OT::hb_ot_apply_context_t, lookup: &impl Apply) -> bool {
313    let mut ret = false;
314    loop {
315        let cur = ctx.buffer.cur(0);
316        ret |= (cur.mask & ctx.lookup_mask()) != 0
317            && ctx.check_glyph_property(cur, ctx.lookup_props)
318            && lookup.apply(ctx).is_some();
319
320        if ctx.buffer.idx == 0 {
321            break;
322        }
323
324        ctx.buffer.idx -= 1;
325    }
326    ret
327}
328
329/* unicode_props */
330
331/* Design:
332 * unicode_props() is a two-byte number.  The low byte includes:
333 * - Modified General_Category: 5 bits.
334 * - A bit each for:
335 *   * Is it Default_Ignorable(); we have a modified Default_Ignorable().
336 *   * Whether it's one of the four Mongolian Free Variation Selectors,
337 *     CGJ, or other characters that are hidden but should not be ignored
338 *     like most other Default_Ignorable()s do during GSUB matching.
339 *   * Whether it's a grapheme continuation.
340 *
341 * The high-byte has different meanings, switched by the Gen-Cat:
342 * - For Mn,Mc,Me: the modified Combining_Class.
343 * - For Cf: whether it's ZWJ, ZWNJ, or something else.
344 * - For Ws: index of which space character this is, if space fallback
345 *   is needed, ie. we don't set this by default, only if asked to.
346 *
347 * Above I said "modified" General_Category. This is because we need to
348 * remember Variation Selectors, and we don't have bits left. So we
349 * change their Gen_Cat from Mn to Cf, and use a bit of the high byte to
350 * remember them.
351 */
352
353//  enum hb_unicode_props_flags_t {
354//     UPROPS_MASK_GEN_CAT	= 0x001Fu,
355//     UPROPS_MASK_IGNORABLE	= 0x0020u,
356//     UPROPS_MASK_HIDDEN	= 0x0040u, /* MONGOLIAN FREE VARIATION SELECTOR 1..4, or TAG characters */
357//     UPROPS_MASK_CONTINUATION=0x0080u,
358
359//     /* If GEN_CAT=FORMAT, top byte masks: */
360//     UPROPS_MASK_Cf_ZWJ	= 0x0100u,
361//     UPROPS_MASK_Cf_ZWNJ	= 0x0200u
362//   };
363//   HB_MARK_AS_FLAG_T (hb_unicode_props_flags_t);
364
365//   static inline void
366//   _hb_glyph_info_set_unicode_props (hb_glyph_info_t *info, hb_buffer_t *buffer)
367//   {
368//     hb_unicode_funcs_t *unicode = buffer->unicode;
369//     unsigned int u = info->codepoint;
370//     unsigned int gen_cat = (unsigned int) unicode->general_category (u);
371//     unsigned int props = gen_cat;
372
373//     if (u >= 0x80u)
374//     {
375//       buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII;
376
377//       if (unlikely (unicode->is_default_ignorable (u)))
378//       {
379//         buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES;
380//         props |=  UPROPS_MASK_IGNORABLE;
381//         if (u == 0x200Cu) props |= UPROPS_MASK_Cf_ZWNJ;
382//         else if (u == 0x200Du) props |= UPROPS_MASK_Cf_ZWJ;
383//         /* Mongolian Free Variation Selectors need to be remembered
384//          * because although we need to hide them like default-ignorables,
385//          * they need to non-ignorable during shaping.  This is similar to
386//          * what we do for joiners in Indic-like shapers, but since the
387//          * FVSes are GC=Mn, we have use a separate bit to remember them.
388//          * Fixes:
389//          * https://github.com/harfbuzz/harfbuzz/issues/234 */
390//         else if (unlikely (hb_in_ranges<hb_codepoint_t> (u, 0x180Bu, 0x180Du, 0x180Fu, 0x180Fu))) props |= UPROPS_MASK_HIDDEN;
391//         /* TAG characters need similar treatment. Fixes:
392//          * https://github.com/harfbuzz/harfbuzz/issues/463 */
393//         else if (unlikely (hb_in_range<hb_codepoint_t> (u, 0xE0020u, 0xE007Fu))) props |= UPROPS_MASK_HIDDEN;
394//         /* COMBINING GRAPHEME JOINER should not be skipped; at least some times.
395//          * https://github.com/harfbuzz/harfbuzz/issues/554 */
396//         else if (unlikely (u == 0x034Fu))
397//         {
398//       buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_CGJ;
399//       props |= UPROPS_MASK_HIDDEN;
400//         }
401//       }
402
403//       if (unlikely (HB_UNICODE_GENERAL_CATEGORY_IS_MARK (gen_cat)))
404//       {
405//         props |= UPROPS_MASK_CONTINUATION;
406//         props |= unicode->modified_combining_class (u)<<8;
407//       }
408//     }
409
410//     info->unicode_props() = props;
411//   }
412
413#[inline]
414pub fn _hb_glyph_info_set_general_category(
415    info: &mut hb_glyph_info_t,
416    gen_cat: hb_unicode_general_category_t,
417) {
418    /* Clears top-byte. */
419    let gen_cat = gen_cat.to_rb();
420    let n =
421        (gen_cat as u16) | (info.unicode_props() & (0xFF & !UnicodeProps::GENERAL_CATEGORY.bits()));
422    info.set_unicode_props(n);
423}
424
425#[inline]
426pub fn _hb_glyph_info_get_general_category(
427    info: &hb_glyph_info_t,
428) -> hb_unicode_general_category_t {
429    let n = info.unicode_props() & UnicodeProps::GENERAL_CATEGORY.bits();
430    hb_unicode_general_category_t::from_rb(n as u32)
431}
432
433#[inline]
434pub fn _hb_glyph_info_is_unicode_mark(info: &hb_glyph_info_t) -> bool {
435    _hb_glyph_info_get_general_category(info).is_mark()
436}
437
438#[inline]
439pub(crate) fn _hb_glyph_info_set_modified_combining_class(
440    info: &mut hb_glyph_info_t,
441    modified_class: u8,
442) {
443    if !_hb_glyph_info_is_unicode_mark(info) {
444        return;
445    }
446
447    let n = ((modified_class as u16) << 8) | (info.unicode_props() & 0xFF);
448    info.set_unicode_props(n);
449}
450
451#[inline]
452pub fn _hb_glyph_info_get_modified_combining_class(info: &hb_glyph_info_t) -> u8 {
453    if _hb_glyph_info_is_unicode_mark(info) {
454        (info.unicode_props() >> 8) as u8
455    } else {
456        0
457    }
458}
459
460// TODO: use
461// #[inline]
462// pub fn info_cc(info: &hb_glyph_info_t) -> u8 {
463//     _hb_glyph_info_get_modified_combining_class(info)
464// }
465
466#[inline]
467pub(crate) fn _hb_glyph_info_is_unicode_space(info: &hb_glyph_info_t) -> bool {
468    _hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::SpaceSeparator
469}
470
471#[inline]
472pub(crate) fn _hb_glyph_info_set_unicode_space_fallback_type(
473    info: &mut hb_glyph_info_t,
474    s: hb_unicode_funcs_t::space_t,
475) {
476    if !_hb_glyph_info_is_unicode_space(info) {
477        return;
478    }
479
480    let n = ((s as u16) << 8) | (info.unicode_props() & 0xFF);
481    info.set_unicode_props(n);
482}
483
484#[inline]
485pub(crate) fn _hb_glyph_info_get_unicode_space_fallback_type(
486    info: &hb_glyph_info_t,
487) -> hb_unicode_funcs_t::space_t {
488    if _hb_glyph_info_is_unicode_space(info) {
489        (info.unicode_props() >> 8) as u8
490    } else {
491        hb_unicode_funcs_t::NOT_SPACE
492    }
493}
494
495#[inline]
496pub(crate) fn _hb_glyph_info_is_variation_selector(info: &hb_glyph_info_t) -> bool {
497    let a = _hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::Format;
498    let b = (info.unicode_props() & UnicodeProps::CF_VS.bits()) != 0;
499    a && b
500}
501
502#[inline]
503pub(crate) fn _hb_glyph_info_set_variation_selector(info: &mut hb_glyph_info_t, customize: bool) {
504    if customize {
505        _hb_glyph_info_set_general_category(info, hb_unicode_general_category_t::Format);
506        info.set_unicode_props(info.unicode_props() | UnicodeProps::CF_VS.bits())
507    } else {
508        // Reset to their original condition
509        _hb_glyph_info_set_general_category(info, hb_unicode_general_category_t::NonspacingMark);
510    }
511}
512
513#[inline]
514pub(crate) fn _hb_glyph_info_is_default_ignorable(info: &hb_glyph_info_t) -> bool {
515    let n = info.unicode_props() & UnicodeProps::IGNORABLE.bits();
516    n != 0 && !_hb_glyph_info_substituted(info)
517}
518
519#[inline]
520pub(crate) fn _hb_glyph_info_clear_default_ignorable(info: &mut hb_glyph_info_t) {
521    let mut n = info.unicode_props();
522    n &= !UnicodeProps::IGNORABLE.bits();
523    info.set_unicode_props(n);
524}
525
526#[inline]
527pub(crate) fn _hb_glyph_info_is_hidden(info: &hb_glyph_info_t) -> bool {
528    (info.unicode_props() & UnicodeProps::HIDDEN.bits()) != 0
529}
530
531//   static inline void
532//   _hb_glyph_info_unhide (hb_glyph_info_t *info)
533//   {
534//     info->unicode_props() &= ~ UPROPS_MASK_HIDDEN;
535//   }
536
537#[inline]
538pub(crate) fn _hb_glyph_info_set_continuation(info: &mut hb_glyph_info_t) {
539    let mut n = info.unicode_props();
540    n |= UnicodeProps::CONTINUATION.bits();
541    info.set_unicode_props(n);
542}
543
544#[inline]
545pub(crate) fn _hb_glyph_info_reset_continuation(info: &mut hb_glyph_info_t) {
546    let mut n = info.unicode_props();
547    n &= !UnicodeProps::CONTINUATION.bits();
548    info.set_unicode_props(n);
549}
550
551#[inline]
552pub(crate) fn _hb_glyph_info_is_continuation(info: &hb_glyph_info_t) -> bool {
553    info.unicode_props() & UnicodeProps::CONTINUATION.bits() != 0
554}
555
556pub(crate) fn _hb_grapheme_group_func(_: &hb_glyph_info_t, b: &hb_glyph_info_t) -> bool {
557    _hb_glyph_info_is_continuation(b)
558}
559
560pub fn _hb_ot_layout_reverse_graphemes(buffer: &mut hb_buffer_t) {
561    buffer.reverse_groups(
562        _hb_grapheme_group_func,
563        buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS,
564    )
565}
566
567#[inline]
568pub(crate) fn _hb_glyph_info_is_unicode_format(info: &hb_glyph_info_t) -> bool {
569    _hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::Format
570}
571
572#[inline]
573pub(crate) fn _hb_glyph_info_is_zwnj(info: &hb_glyph_info_t) -> bool {
574    _hb_glyph_info_is_unicode_format(info)
575        && (info.unicode_props() & UnicodeProps::CF_ZWNJ.bits() != 0)
576}
577
578#[inline]
579pub(crate) fn _hb_glyph_info_is_zwj(info: &hb_glyph_info_t) -> bool {
580    _hb_glyph_info_is_unicode_format(info)
581        && (info.unicode_props() & UnicodeProps::CF_ZWJ.bits() != 0)
582}
583
584//   static inline bool
585//   _hb_glyph_info_is_joiner (const hb_glyph_info_t *info)
586//   {
587//     return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & (UPROPS_MASK_Cf_ZWNJ|UPROPS_MASK_Cf_ZWJ));
588//   }
589
590//   static inline void
591//   _hb_glyph_info_flip_joiners (hb_glyph_info_t *info)
592//   {
593//     if (!_hb_glyph_info_is_unicode_format (info))
594//       return;
595//     info->unicode_props() ^= UPROPS_MASK_Cf_ZWNJ | UPROPS_MASK_Cf_ZWJ;
596//   }
597
598//   /* lig_props: aka lig_id / lig_comp
599//    *
600//    * When a ligature is formed:
601//    *
602//    *   - The ligature glyph and any marks in between all the same newly allocated
603//    *     lig_id,
604//    *   - The ligature glyph will get lig_num_comps set to the number of components
605//    *   - The marks get lig_comp > 0, reflecting which component of the ligature
606//    *     they were applied to.
607//    *   - This is used in GPOS to attach marks to the right component of a ligature
608//    *     in MarkLigPos,
609//    *   - Note that when marks are ligated together, much of the above is skipped
610//    *     and the current lig_id reused.
611//    *
612//    * When a multiple-substitution is done:
613//    *
614//    *   - All resulting glyphs will have lig_id = 0,
615//    *   - The resulting glyphs will have lig_comp = 0, 1, 2, ... respectively.
616//    *   - This is used in GPOS to attach marks to the first component of a
617//    *     multiple substitution in MarkBasePos.
618//    *
619//    * The numbers are also used in GPOS to do mark-to-mark positioning only
620//    * to marks that belong to the same component of the same ligature.
621//    */
622//   static inline void
623//   _hb_glyph_info_clear_lig_props (hb_glyph_info_t *info)
624//   {
625//     info->lig_props() = 0;
626//   }
627
628const IS_LIG_BASE: u8 = 0x10;
629
630#[inline]
631pub(crate) fn _hb_glyph_info_set_lig_props_for_ligature(
632    info: &mut hb_glyph_info_t,
633    lig_id: u8,
634    lig_num_comps: u8,
635) {
636    info.set_lig_props((lig_id << 5) | IS_LIG_BASE | (lig_num_comps & 0x0F));
637}
638
639#[inline]
640pub(crate) fn _hb_glyph_info_set_lig_props_for_mark(
641    info: &mut hb_glyph_info_t,
642    lig_id: u8,
643    lig_comp: u8,
644) {
645    info.set_lig_props((lig_id << 5) | (lig_comp & 0x0F));
646}
647
648#[inline]
649pub(crate) fn _hb_glyph_info_set_lig_props_for_component(info: &mut hb_glyph_info_t, comp: u8) {
650    _hb_glyph_info_set_lig_props_for_mark(info, 0, comp);
651}
652
653#[inline]
654pub(crate) fn _hb_glyph_info_get_lig_id(info: &hb_glyph_info_t) -> u8 {
655    info.lig_props() >> 5
656}
657
658#[inline]
659pub(crate) fn _hb_glyph_info_ligated_internal(info: &hb_glyph_info_t) -> bool {
660    info.lig_props() & IS_LIG_BASE != 0
661}
662
663#[inline]
664pub(crate) fn _hb_glyph_info_get_lig_comp(info: &hb_glyph_info_t) -> u8 {
665    if _hb_glyph_info_ligated_internal(info) {
666        0
667    } else {
668        info.lig_props() & 0x0F
669    }
670}
671
672#[inline]
673pub(crate) fn _hb_glyph_info_get_lig_num_comps(info: &hb_glyph_info_t) -> u8 {
674    if info.glyph_props() & GlyphPropsFlags::LIGATURE.bits() != 0
675        && _hb_glyph_info_ligated_internal(info)
676    {
677        info.lig_props() & 0x0F
678    } else {
679        1
680    }
681}
682
683//   /* glyph_props: */
684//   static inline void
685//   _hb_glyph_info_set_glyph_props (hb_glyph_info_t *info, unsigned int props)
686//   {
687//     info->glyph_props() = props;
688//   }
689
690//   static inline unsigned int
691//   _hb_glyph_info_get_glyph_props (const hb_glyph_info_t *info)
692//   {
693//     return info->glyph_props();
694//   }
695
696#[inline]
697pub(crate) fn _hb_glyph_info_is_base_glyph(info: &hb_glyph_info_t) -> bool {
698    info.glyph_props() & GlyphPropsFlags::BASE_GLYPH.bits() != 0
699}
700
701#[inline]
702pub(crate) fn _hb_glyph_info_is_ligature(info: &hb_glyph_info_t) -> bool {
703    info.glyph_props() & GlyphPropsFlags::LIGATURE.bits() != 0
704}
705
706#[inline]
707pub(crate) fn _hb_glyph_info_is_mark(info: &hb_glyph_info_t) -> bool {
708    info.glyph_props() & GlyphPropsFlags::MARK.bits() != 0
709}
710
711#[inline]
712pub(crate) fn _hb_glyph_info_substituted(info: &hb_glyph_info_t) -> bool {
713    info.glyph_props() & GlyphPropsFlags::SUBSTITUTED.bits() != 0
714}
715
716#[inline]
717pub(crate) fn _hb_glyph_info_ligated(info: &hb_glyph_info_t) -> bool {
718    info.glyph_props() & GlyphPropsFlags::LIGATED.bits() != 0
719}
720
721#[inline]
722pub(crate) fn _hb_glyph_info_multiplied(info: &hb_glyph_info_t) -> bool {
723    info.glyph_props() & GlyphPropsFlags::MULTIPLIED.bits() != 0
724}
725
726#[inline]
727pub(crate) fn _hb_glyph_info_ligated_and_didnt_multiply(info: &hb_glyph_info_t) -> bool {
728    _hb_glyph_info_ligated(info) && !_hb_glyph_info_multiplied(info)
729}
730
731#[inline]
732pub(crate) fn _hb_glyph_info_clear_ligated_and_multiplied(info: &mut hb_glyph_info_t) {
733    let mut n = info.glyph_props();
734    n &= !(GlyphPropsFlags::LIGATED | GlyphPropsFlags::MULTIPLIED).bits();
735    info.set_glyph_props(n);
736}
737
738#[inline]
739pub(crate) fn _hb_glyph_info_clear_substituted(info: &mut hb_glyph_info_t) {
740    let mut n = info.glyph_props();
741    n &= !GlyphPropsFlags::SUBSTITUTED.bits();
742    info.set_glyph_props(n);
743}
744
745pub fn _hb_clear_substitution_flags(
746    _: &hb_ot_shape_plan_t,
747    _: &hb_font_t,
748    buffer: &mut hb_buffer_t,
749) -> bool {
750    let len = buffer.len;
751    for info in &mut buffer.info[..len] {
752        _hb_glyph_info_clear_substituted(info);
753    }
754
755    false
756}