makepad_rustybuzz/
shape.rs

1use core::convert::TryFrom;
2
3use crate::{aat, ot, fallback, normalize, Direction, Face, Feature, GlyphBuffer, UnicodeBuffer};
4use crate::buffer::{
5    glyph_flag, Buffer, BufferClusterLevel, BufferFlags, BufferScratchFlags, GlyphInfo,
6    GlyphPropsFlags,
7};
8use crate::complex::ZeroWidthMarksMode;
9use crate::plan::ShapePlan;
10use crate::unicode::{CharExt, GeneralCategory};
11
12/// Shapes the buffer content using provided font and features.
13///
14/// Consumes the buffer. You can then run `GlyphBuffer::clear` to get the `UnicodeBuffer` back
15/// without allocating a new one.
16pub fn shape(face: &Face, features: &[Feature], buffer: UnicodeBuffer) -> GlyphBuffer {
17    let mut buffer = buffer.0;
18    buffer.guess_segment_properties();
19
20    if buffer.len > 0 {
21        let plan = ShapePlan::new(
22            face,
23            buffer.direction,
24            buffer.script,
25            buffer.language.as_ref(),
26            features,
27        );
28
29        // Save the original direction, we use it later.
30        let target_direction = buffer.direction;
31        shape_internal(&mut ShapeContext {
32            plan: &plan,
33            face,
34            buffer: &mut buffer,
35            user_features: features,
36            target_direction,
37        });
38    }
39
40    GlyphBuffer(buffer)
41}
42
43struct ShapeContext<'a> {
44    plan: &'a ShapePlan,
45    face: &'a Face<'a>,
46    buffer: &'a mut Buffer,
47    user_features: &'a [Feature],
48    // Transient stuff
49    target_direction: Direction,
50}
51
52// Pull it all together!
53fn shape_internal(ctx: &mut ShapeContext) {
54    ctx.buffer.scratch_flags = BufferScratchFlags::empty();
55
56    if let Some(len) = ctx.buffer.len.checked_mul(Buffer::MAX_LEN_FACTOR) {
57        ctx.buffer.max_len = len.max(Buffer::MAX_LEN_MIN);
58    }
59
60    if let Ok(len) = i32::try_from(ctx.buffer.len) {
61        if let Some(ops) = len.checked_mul(Buffer::MAX_OPS_FACTOR) {
62            ctx.buffer.max_ops = ops.max(Buffer::MAX_OPS_MIN);
63        }
64    }
65
66    ctx.buffer.clear_output();
67
68    initialize_masks(ctx);
69    set_unicode_props(ctx.buffer);
70    insert_dotted_circle(ctx.buffer, ctx.face);
71
72    form_clusters(ctx.buffer);
73
74    ensure_native_direction(ctx.buffer);
75
76    if let Some(func) = ctx.plan.shaper.preprocess_text {
77        func(ctx.plan, ctx.face, ctx.buffer);
78    }
79
80    substitute_pre(ctx);
81    position(ctx);
82    substitute_post(ctx);
83
84    propagate_flags(ctx.buffer);
85
86    ctx.buffer.direction = ctx.target_direction;
87    ctx.buffer.max_len = Buffer::MAX_LEN_DEFAULT;
88    ctx.buffer.max_ops = Buffer::MAX_OPS_DEFAULT;
89}
90
91fn substitute_pre(ctx: &mut ShapeContext) {
92    substitute_default(ctx);
93    substitute_complex(ctx);
94}
95
96fn substitute_post(ctx: &mut ShapeContext) {
97    hide_default_ignorables(ctx.buffer, ctx.face);
98
99    if ctx.plan.apply_morx {
100        aat::remove_deleted_glyphs(ctx.buffer);
101    }
102
103    if let Some(func) = ctx.plan.shaper.postprocess_glyphs {
104        func(ctx.plan, ctx.face, ctx.buffer);
105    }
106}
107
108fn substitute_default(ctx: &mut ShapeContext) {
109    rotate_chars(ctx);
110
111    normalize::normalize(ctx.plan, ctx.face, ctx.buffer);
112
113    setup_masks(ctx);
114
115    // This is unfortunate to go here, but necessary...
116    if ctx.plan.fallback_mark_positioning {
117        fallback::recategorize_marks(ctx.plan, ctx.face, ctx.buffer);
118    }
119
120    map_glyphs_fast(ctx.buffer);
121}
122
123fn substitute_complex(ctx: &mut ShapeContext) {
124    ot::substitute_start(ctx.face, ctx.buffer);
125
126    if ctx.plan.fallback_glyph_classes {
127        synthesize_glyph_classes(ctx.buffer);
128    }
129
130    substitute_by_plan(ctx.plan, ctx.face, ctx.buffer);
131}
132
133fn substitute_by_plan(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) {
134    if plan.apply_morx {
135        aat::substitute(plan, face, buffer);
136    } else {
137        ot::substitute(plan, face, buffer);
138    }
139}
140
141fn position(ctx: &mut ShapeContext) {
142    ctx.buffer.clear_positions();
143
144    position_default(ctx);
145
146    position_complex(ctx);
147
148    if ctx.buffer.direction.is_backward() {
149        ctx.buffer.reverse();
150    }
151}
152
153fn position_default(ctx: &mut ShapeContext) {
154    let len = ctx.buffer.len;
155
156    if ctx.buffer.direction.is_horizontal() {
157        for (info, pos) in ctx.buffer.info[..len].iter().zip(&mut ctx.buffer.pos[..len]) {
158            pos.x_advance = ctx.face.glyph_h_advance(info.as_glyph());
159        }
160    } else {
161        for (info, pos) in ctx.buffer.info[..len].iter().zip(&mut ctx.buffer.pos[..len]) {
162            let glyph = info.as_glyph();
163            pos.y_advance = ctx.face.glyph_v_advance(glyph);
164            pos.x_offset -= ctx.face.glyph_h_origin(glyph);
165            pos.y_offset -= ctx.face.glyph_v_origin(glyph);
166        }
167    }
168
169    if ctx.buffer.scratch_flags.contains(BufferScratchFlags::HAS_SPACE_FALLBACK) {
170        fallback::adjust_spaces(ctx.plan, ctx.face, ctx.buffer);
171    }
172}
173
174fn position_complex(ctx: &mut ShapeContext) {
175    // If the font has no GPOS and direction is forward, then when
176    // zeroing mark widths, we shift the mark with it, such that the
177    // mark is positioned hanging over the previous glyph.  When
178    // direction is backward we don't shift and it will end up
179    // hanging over the next glyph after the final reordering.
180    //
181    // Note: If fallback positioning happens, we don't care about
182    // this as it will be overriden.
183    let adjust_offsets_when_zeroing = ctx.plan.adjust_mark_positioning_when_zeroing
184        && ctx.buffer.direction.is_forward();
185
186    // We change glyph origin to what GPOS expects (horizontal), apply GPOS, change it back.
187
188    ot::position_start(ctx.face, ctx.buffer);
189
190    if ctx.plan.zero_marks && ctx.plan.shaper.zero_width_marks == Some(ZeroWidthMarksMode::ByGdefEarly) {
191        zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing);
192    }
193
194    position_by_plan(ctx.plan, ctx.face, ctx.buffer);
195
196    if ctx.plan.zero_marks && ctx.plan.shaper.zero_width_marks == Some(ZeroWidthMarksMode::ByGdefLate) {
197        zero_mark_widths_by_gdef(ctx.buffer, adjust_offsets_when_zeroing);
198    }
199
200    // Finish off.  Has to follow a certain order.
201    ot::position_finish_advances(ctx.face, ctx.buffer);
202    zero_width_default_ignorables(ctx.buffer);
203
204    if ctx.plan.apply_morx {
205        aat::zero_width_deleted_glyphs(ctx.buffer);
206    }
207
208    ot::position_finish_offsets(ctx.face, ctx.buffer);
209
210    if ctx.plan.fallback_mark_positioning {
211        fallback::position_marks(ctx.plan, ctx.face, ctx.buffer, adjust_offsets_when_zeroing);
212    }
213}
214
215fn position_by_plan(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) {
216    if plan.apply_gpos {
217        ot::position(plan, face, buffer);
218    } else if plan.apply_kerx {
219        aat::position(plan, face, buffer);
220    } else if plan.apply_kern {
221        ot::kern(plan, face, buffer);
222    }
223
224    if plan.apply_trak {
225        aat::track(plan, face, buffer);
226    }
227}
228
229fn initialize_masks(ctx: &mut ShapeContext) {
230    let global_mask = ctx.plan.ot_map.global_mask();
231    ctx.buffer.reset_masks(global_mask);
232}
233
234fn setup_masks(ctx: &mut ShapeContext) {
235    setup_masks_fraction(ctx);
236
237    if let Some(func) = ctx.plan.shaper.setup_masks {
238        func(ctx.plan, ctx.face, ctx.buffer);
239    }
240
241    for feature in ctx.user_features {
242        if !feature.is_global() {
243            let (mask, shift) = ctx.plan.ot_map.mask(feature.tag);
244            ctx.buffer.set_masks(feature.value << shift, mask, feature.start, feature.end);
245        }
246    }
247}
248
249fn setup_masks_fraction(ctx: &mut ShapeContext) {
250    let buffer = &mut ctx.buffer;
251    if !buffer.scratch_flags.contains(BufferScratchFlags::HAS_NON_ASCII) || !ctx.plan.has_frac {
252        return;
253    }
254
255    let (pre_mask, post_mask) = if buffer.direction.is_forward() {
256        (ctx.plan.numr_mask | ctx.plan.frac_mask, ctx.plan.frac_mask | ctx.plan.dnom_mask)
257    } else {
258        (ctx.plan.frac_mask | ctx.plan.dnom_mask, ctx.plan.numr_mask | ctx.plan.frac_mask)
259    };
260
261    let len = buffer.len;
262    let mut i = 0;
263    while i < len {
264        // FRACTION SLASH
265        if buffer.info[i].glyph_id == 0x2044 {
266            let mut start = i;
267            while start > 0 && buffer.info[start - 1].general_category() == GeneralCategory::DecimalNumber {
268                start -= 1;
269            }
270
271            let mut end = i + 1;
272            while end < len && buffer.info[end].general_category() == GeneralCategory::DecimalNumber {
273                end += 1;
274            }
275
276            buffer.unsafe_to_break(start, end);
277
278            for info in &mut buffer.info[start..i] {
279                info.mask |= pre_mask;
280            }
281
282            buffer.info[i].mask |= ctx.plan.frac_mask;
283
284            for info in &mut buffer.info[i+1..end] {
285                info.mask |= post_mask;
286            }
287
288            i = end;
289        } else {
290            i += 1;
291        }
292    }
293}
294
295fn set_unicode_props(buffer: &mut Buffer) {
296    // Implement enough of Unicode Graphemes here that shaping
297    // in reverse-direction wouldn't break graphemes.  Namely,
298    // we mark all marks and ZWJ and ZWJ,Extended_Pictographic
299    // sequences as continuations.  The foreach_grapheme()
300    // macro uses this bit.
301    //
302    // https://www.unicode.org/reports/tr29/#Regex_Definitions
303
304    let len = buffer.len;
305
306    let mut i = 0;
307    while i < len {
308        let info = &mut buffer.info[i];
309        info.init_unicode_props(&mut buffer.scratch_flags);
310
311        // Marks are already set as continuation by the above line.
312        // Handle Emoji_Modifier and ZWJ-continuation.
313        if info.general_category() == GeneralCategory::ModifierSymbol
314            && matches!(info.glyph_id, 0x1F3FB..=0x1F3FF)
315        {
316            info.set_continuation();
317        } else if info.is_zwj() {
318            info.set_continuation();
319            if let Some(next) = buffer.info[..len].get_mut(i + 1) {
320                if next.as_char().is_emoji_extended_pictographic() {
321                    next.init_unicode_props(&mut buffer.scratch_flags);
322                    next.set_continuation();
323                    i += 1;
324                }
325            }
326        } else if matches!(info.glyph_id, 0xE0020..=0xE007F) {
327            // Or part of the Other_Grapheme_Extend that is not marks.
328            // As of Unicode 11 that is just:
329            //
330            // 200C          ; Other_Grapheme_Extend # Cf       ZERO WIDTH NON-JOINER
331            // FF9E..FF9F    ; Other_Grapheme_Extend # Lm   [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA
332            // SEMI-VOICED SOUND MARK E0020..E007F  ; Other_Grapheme_Extend # Cf  [96] TAG SPACE..CANCEL TAG
333            //
334            // ZWNJ is special, we don't want to merge it as there's no need, and keeping
335            // it separate results in more granular clusters.  Ignore Katakana for now.
336            // Tags are used for Emoji sub-region flag sequences:
337            // https://github.com/harfbuzz/harfbuzz/issues/1556
338            info.set_continuation();
339        }
340
341        i += 1;
342    }
343}
344
345fn insert_dotted_circle(buffer: &mut Buffer, face: &Face) {
346    if !buffer.flags.contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE)
347        && buffer.flags.contains(BufferFlags::BEGINNING_OF_TEXT)
348        && buffer.context_len[0] == 0
349        && buffer.info[0].is_unicode_mark()
350        && face.has_glyph(0x25CC)
351    {
352        let mut info = GlyphInfo {
353            glyph_id: 0x25CC,
354            mask: buffer.cur(0).mask,
355            cluster: buffer.cur(0).cluster,
356            var1: 0,
357            var2: 0,
358        };
359
360        info.init_unicode_props(&mut buffer.scratch_flags);
361        buffer.clear_output();
362        buffer.output_info(info);
363
364        while buffer.idx < buffer.len && buffer.successful {
365            buffer.next_glyph();
366        }
367
368        buffer.swap_buffers();
369    }
370}
371
372fn form_clusters(buffer: &mut Buffer) {
373    if buffer.scratch_flags.contains(BufferScratchFlags::HAS_NON_ASCII) {
374        if buffer.cluster_level == BufferClusterLevel::MonotoneGraphemes {
375            foreach_grapheme!(buffer, start, end, {
376                buffer.merge_clusters(start, end)
377            });
378        } else {
379            foreach_grapheme!(buffer, start, end, {
380                buffer.unsafe_to_break(start, end);
381            });
382        }
383    }
384}
385
386fn ensure_native_direction(buffer: &mut Buffer) {
387    let dir = buffer.direction;
388    let hor = buffer.script.and_then(Direction::from_script).unwrap_or_default();
389
390    if (dir.is_horizontal() && dir != hor && hor != Direction::Invalid)
391        || (dir.is_vertical() && dir != Direction::TopToBottom)
392    {
393        if buffer.cluster_level == BufferClusterLevel::MonotoneCharacters {
394            foreach_grapheme!(buffer, start, end, {
395                buffer.merge_clusters(start, end);
396                buffer.reverse_range(start, end);
397            });
398        } else {
399            foreach_grapheme!(buffer, start, end, {
400                // form_clusters() merged clusters already, we don't merge.
401                buffer.reverse_range(start, end);
402            })
403        }
404
405        buffer.reverse();
406        buffer.direction = buffer.direction.reverse();
407    }
408}
409
410fn rotate_chars(ctx: &mut ShapeContext) {
411    let len = ctx.buffer.len;
412
413    if ctx.target_direction.is_backward() {
414        let rtlm_mask = ctx.plan.rtlm_mask;
415
416        for info in &mut ctx.buffer.info[..len] {
417            if let Some(c) = info.as_char().mirrored().map(u32::from) {
418                if ctx.face.has_glyph(c) {
419                    info.glyph_id = c;
420                    continue;
421                }
422            }
423            info.mask |= rtlm_mask;
424        }
425    }
426
427    if ctx.target_direction.is_vertical() && !ctx.plan.has_vert {
428        for info in &mut ctx.buffer.info[..len] {
429            if let Some(c) = info.as_char().vertical().map(u32::from) {
430                if ctx.face.has_glyph(c) {
431                    info.glyph_id = c;
432                }
433            }
434        }
435    }
436}
437
438fn map_glyphs_fast(buffer: &mut Buffer) {
439    // Normalization process sets up glyph_index(), we just copy it.
440    let len = buffer.len;
441    for info in &mut buffer.info[..len] {
442        info.glyph_id = info.glyph_index();
443    }
444}
445
446fn synthesize_glyph_classes(buffer: &mut Buffer) {
447    let len = buffer.len;
448    for info in &mut buffer.info[..len] {
449        // Never mark default-ignorables as marks.
450        // They won't get in the way of lookups anyway,
451        // but having them as mark will cause them to be skipped
452        // over if the lookup-flag says so, but at least for the
453        // Mongolian variation selectors, looks like Uniscribe
454        // marks them as non-mark.  Some Mongolian fonts without
455        // GDEF rely on this.  Another notable character that
456        // this applies to is COMBINING GRAPHEME JOINER.
457        let class = if info.general_category() != GeneralCategory::NonspacingMark
458            || info.is_default_ignorable()
459        {
460            GlyphPropsFlags::BASE_GLYPH
461        } else {
462            GlyphPropsFlags::MARK
463        };
464
465        info.set_glyph_props(class.bits());
466    }
467}
468
469fn zero_width_default_ignorables(buffer: &mut Buffer) {
470    if buffer.scratch_flags.contains(BufferScratchFlags::HAS_DEFAULT_IGNORABLES)
471        && !buffer.flags.contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES)
472        && !buffer.flags.contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES)
473    {
474        let len = buffer.len;
475        for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
476            if info.is_default_ignorable() {
477                pos.x_advance = 0;
478                pos.y_advance = 0;
479                pos.x_offset = 0;
480                pos.y_offset = 0;
481            }
482        }
483    }
484}
485
486fn zero_mark_widths_by_gdef(buffer: &mut Buffer, adjust_offsets: bool) {
487    let len = buffer.len;
488    for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
489        if info.is_mark() {
490            if adjust_offsets {
491                pos.x_offset -= pos.x_advance;
492                pos.y_offset -= pos.y_advance;
493            }
494
495            pos.x_advance = 0;
496            pos.y_advance = 0;
497        }
498    }
499}
500
501fn hide_default_ignorables(buffer: &mut Buffer, face: &Face) {
502    if buffer.scratch_flags.contains(BufferScratchFlags::HAS_DEFAULT_IGNORABLES)
503        && !buffer.flags.contains(BufferFlags::PRESERVE_DEFAULT_IGNORABLES)
504    {
505        if !buffer.flags.contains(BufferFlags::REMOVE_DEFAULT_IGNORABLES) {
506            if let Some(invisible) = buffer.invisible.or_else(|| face.glyph_index(u32::from(' '))) {
507                let len = buffer.len;
508                for info in &mut buffer.info[..len] {
509                    if info.is_default_ignorable() {
510                        info.glyph_id = u32::from(invisible.0);
511                    }
512                }
513                return;
514            }
515        }
516
517        buffer.delete_glyphs_inplace(GlyphInfo::is_default_ignorable);
518    }
519}
520
521fn propagate_flags(buffer: &mut Buffer) {
522    // Propagate cluster-level glyph flags to be the same on all cluster glyphs.
523    // Simplifies using them.
524    if buffer.scratch_flags.contains(BufferScratchFlags::HAS_UNSAFE_TO_BREAK) {
525        foreach_cluster!(buffer, start, end, {
526            for info in &buffer.info[start..end] {
527                if info.mask & glyph_flag::UNSAFE_TO_BREAK != 0 {
528                    for info in &mut buffer.info[start..end] {
529                        info.mask |= glyph_flag::UNSAFE_TO_BREAK;
530                    }
531                    break;
532                }
533            }
534        });
535    }
536}