Skip to main content

harfrust/hb/
ot_shape_fallback.rs

1use read_fonts::types::GlyphId;
2
3use super::buffer::{hb_buffer_t, GlyphPosition};
4use super::face::GlyphExtents;
5use super::font_funcs::FontFuncsDispatch;
6use super::ot_shape_plan::hb_ot_shape_plan_t;
7use super::unicode::*;
8use super::{hb_font_t, Direction};
9
10struct FallbackShapeContext<'a, 'x, 'u> {
11    plan: &'a hb_ot_shape_plan_t,
12    face: &'a hb_font_t<'a>,
13    buffer: &'x mut hb_buffer_t,
14    adjust_offsets_when_zeroing: bool,
15    font_funcs: &'x mut FontFuncsDispatch<'a, 'u>,
16}
17
18fn recategorize_combining_class(u: u32, mut class: u8) -> u8 {
19    use modified_combining_class as mcc;
20
21    if class >= 200 {
22        return class;
23    }
24
25    // Thai / Lao need some per-character work.
26    if u & !0xFF == 0x0E00 {
27        if class == 0 {
28            match u {
29                0x0E31 | 0x0E34 | 0x0E35 | 0x0E36 | 0x0E37 | 0x0E47 | 0x0E4C | 0x0E4D | 0x0E4E => {
30                    class = combining_class::AboveRight;
31                }
32
33                0x0EB1 | 0x0EB4 | 0x0EB5 | 0x0EB6 | 0x0EB7 | 0x0EBB | 0x0ECC | 0x0ECD => {
34                    class = combining_class::Above;
35                }
36
37                0x0EBC => class = combining_class::Below,
38
39                _ => {}
40            }
41        } else {
42            // Thai virama is below-right
43            if u == 0x0E3A {
44                class = combining_class::BelowRight;
45            }
46        }
47    }
48
49    match class {
50        // Hebrew
51        mcc::CCC10 => combining_class::Below,         // sheva
52        mcc::CCC11 => combining_class::Below,         // hataf segol
53        mcc::CCC12 => combining_class::Below,         // hataf patah
54        mcc::CCC13 => combining_class::Below,         // hataf qamats
55        mcc::CCC14 => combining_class::Below,         // hiriq
56        mcc::CCC15 => combining_class::Below,         // tsere
57        mcc::CCC16 => combining_class::Below,         // segol
58        mcc::CCC17 => combining_class::Below,         // patah
59        mcc::CCC18 => combining_class::Below,         // qamats & qamats qatan
60        mcc::CCC20 => combining_class::Below,         // qubuts
61        mcc::CCC22 => combining_class::Below,         // meteg
62        mcc::CCC23 => combining_class::AttachedAbove, // rafe
63        mcc::CCC24 => combining_class::AboveRight,    // shin dot
64        mcc::CCC25 => combining_class::AboveLeft,     // sin dot
65        mcc::CCC19 => combining_class::AboveLeft,     // holam & holam haser for vav
66        mcc::CCC26 => combining_class::Above,         // point varika
67        mcc::CCC21 => class,                          // dagesh
68
69        // Arabic and Syriac
70        mcc::CCC27 => combining_class::Above, // fathatan
71        mcc::CCC28 => combining_class::Above, // dammatan
72        mcc::CCC30 => combining_class::Above, // fatha
73        mcc::CCC31 => combining_class::Above, // damma
74        mcc::CCC33 => combining_class::Above, // shadda
75        mcc::CCC34 => combining_class::Above, // sukun
76        mcc::CCC35 => combining_class::Above, // superscript alef
77        mcc::CCC36 => combining_class::Above, // superscript alaph
78        mcc::CCC29 => combining_class::Below, // kasratan
79        mcc::CCC32 => combining_class::Below, // kasra
80
81        // Thai
82        mcc::CCC103 => combining_class::BelowRight, // sara u / sara uu
83        mcc::CCC107 => combining_class::AboveRight, // mai
84
85        // Lao
86        mcc::CCC118 => combining_class::Below, // sign u / sign uu
87        mcc::CCC122 => combining_class::Above, // mai
88
89        // Tibetian
90        mcc::CCC129 => combining_class::Below, // sign aa
91        mcc::CCC130 => combining_class::Above, // sign i
92        mcc::CCC132 => combining_class::Below, // sign u
93
94        _ => class,
95    }
96}
97
98pub fn _hb_ot_shape_fallback_mark_position_recategorize_marks(
99    _: &hb_ot_shape_plan_t,
100    _: &hb_font_t,
101    buffer: &mut hb_buffer_t,
102) {
103    let len = buffer.len;
104    for info in &mut buffer.info[..len] {
105        if info.general_category() == GeneralCategory::NON_SPACING_MARK {
106            let mut class = info.modified_combining_class();
107            class = recategorize_combining_class(info.glyph_id, class);
108            info.set_modified_combining_class(class);
109        }
110    }
111}
112
113fn zero_mark_advances(
114    buffer: &mut hb_buffer_t,
115    start: usize,
116    end: usize,
117    adjust_offsets_when_zeroing: bool,
118) {
119    for (info, pos) in buffer.info[start..end]
120        .iter()
121        .zip(&mut buffer.pos[start..end])
122    {
123        if info.general_category() == GeneralCategory::NON_SPACING_MARK {
124            if adjust_offsets_when_zeroing {
125                pos.x_offset -= pos.x_advance;
126                pos.y_offset -= pos.y_advance;
127            }
128            pos.x_advance = 0;
129            pos.y_advance = 0;
130        }
131    }
132}
133
134fn position_mark(
135    face: &hb_font_t,
136    direction: Direction,
137    font_funcs: &mut FontFuncsDispatch<'_, '_>,
138    glyph: GlyphId,
139    pos: &mut GlyphPosition,
140    base_extents: &mut GlyphExtents,
141    combining_class: u8,
142) {
143    let Some(mark_extents) = font_funcs.extents(glyph) else {
144        return;
145    };
146
147    let y_gap = font_funcs.scale().scale_y(face.units_per_em as i32 / 16);
148    pos.x_offset = 0;
149    pos.y_offset = 0;
150
151    // We don't position LEFT and RIGHT marks.
152
153    // X positioning
154    match combining_class {
155        combining_class::DoubleBelow | combining_class::DoubleAbove
156            if direction.is_horizontal() =>
157        {
158            pos.x_offset = pos
159                .x_offset
160                .saturating_add(base_extents.x_bearing)
161                .saturating_add(if direction.is_forward() {
162                    base_extents.width
163                } else {
164                    0
165                })
166                .saturating_sub(mark_extents.width / 2)
167                .saturating_sub(mark_extents.x_bearing);
168        }
169
170        combining_class::AttachedBelowLeft
171        | combining_class::BelowLeft
172        | combining_class::AboveLeft => {
173            // Left align.
174            pos.x_offset = pos
175                .x_offset
176                .saturating_add(base_extents.x_bearing)
177                .saturating_sub(mark_extents.x_bearing);
178        }
179
180        combining_class::AttachedAboveRight
181        | combining_class::BelowRight
182        | combining_class::AboveRight => {
183            // Right align.
184            pos.x_offset = pos
185                .x_offset
186                .saturating_add(base_extents.x_bearing)
187                .saturating_add(base_extents.width)
188                .saturating_sub(mark_extents.width)
189                .saturating_sub(mark_extents.x_bearing);
190        }
191
192        combining_class::AttachedBelow
193        | combining_class::AttachedAbove
194        | combining_class::Below
195        | combining_class::Above
196        | _ => {
197            // Center align.
198            pos.x_offset = pos
199                .x_offset
200                .saturating_add(base_extents.x_bearing)
201                .saturating_add((base_extents.width.saturating_sub(mark_extents.width)) / 2)
202                .saturating_sub(mark_extents.x_bearing);
203        }
204    }
205
206    let is_attached = matches!(
207        combining_class,
208        combining_class::AttachedBelowLeft
209            | combining_class::AttachedBelow
210            | combining_class::AttachedAbove
211            | combining_class::AttachedAboveRight
212    );
213
214    // Y positioning.
215    match combining_class {
216        combining_class::DoubleBelow
217        | combining_class::BelowLeft
218        | combining_class::Below
219        | combining_class::BelowRight
220        | combining_class::AttachedBelowLeft
221        | combining_class::AttachedBelow => {
222            if !is_attached {
223                // Add gap.
224                base_extents.height = base_extents.height.saturating_sub(y_gap);
225            }
226
227            pos.y_offset = base_extents
228                .y_bearing
229                .saturating_add(base_extents.height)
230                .saturating_sub(mark_extents.y_bearing);
231
232            // Never shift up "below" marks.
233            if (y_gap > 0) == (pos.y_offset > 0) {
234                base_extents.height = base_extents.height.saturating_sub(pos.y_offset);
235                pos.y_offset = 0;
236            }
237
238            base_extents.height = base_extents.height.saturating_add(mark_extents.height);
239        }
240
241        combining_class::DoubleAbove
242        | combining_class::AboveLeft
243        | combining_class::Above
244        | combining_class::AboveRight
245        | combining_class::AttachedAbove
246        | combining_class::AttachedAboveRight => {
247            if !is_attached {
248                // Add gap.
249                base_extents.y_bearing = base_extents.y_bearing.saturating_add(y_gap);
250                base_extents.height = base_extents.height.saturating_sub(y_gap);
251            }
252
253            pos.y_offset = base_extents
254                .y_bearing
255                .saturating_sub(mark_extents.y_bearing.saturating_add(mark_extents.height));
256
257            // Don't shift down "above" marks too much.
258            if (y_gap > 0) != (pos.y_offset > 0) {
259                let correction = -pos.y_offset / 2;
260                base_extents.y_bearing = base_extents.y_bearing.saturating_add(correction);
261                base_extents.height = base_extents.height.saturating_sub(correction);
262                pos.y_offset = pos.y_offset.saturating_add(correction);
263            }
264
265            base_extents.y_bearing = base_extents.y_bearing.saturating_sub(mark_extents.height);
266            base_extents.height = base_extents.height.saturating_add(mark_extents.height);
267        }
268
269        _ => {}
270    }
271}
272
273fn position_around_base(ctx: &mut FallbackShapeContext, base: usize, end: usize) {
274    let mut horizontal_dir = Direction::Invalid;
275    let face = ctx.face;
276    ctx.buffer.unsafe_to_break(Some(base), Some(end));
277
278    let base_info = ctx.buffer.info[base];
279    let base_pos = ctx.buffer.pos[base];
280    let base_glyph = base_info.as_glyph();
281
282    let Some(mut base_extents) = ctx.font_funcs.extents(base_glyph) else {
283        zero_mark_advances(ctx.buffer, base + 1, end, ctx.adjust_offsets_when_zeroing);
284        return;
285    };
286
287    base_extents.y_bearing += base_pos.y_offset;
288    base_extents.x_bearing = 0;
289
290    // Use horizontal advance for horizontal positioning.
291    // Generally a better idea. Also works for zero-ink glyphs. See:
292    // https://github.com/harfbuzz/harfbuzz/issues/1532
293    base_extents.width = ctx.font_funcs.advance_width(base_glyph);
294
295    let lig_id = base_info.lig_id() as u32;
296    let num_lig_components = base_info.lig_num_comps() as i32;
297
298    let mut x_offset = 0;
299    let mut y_offset = 0;
300    if ctx.buffer.direction.is_forward() {
301        x_offset -= base_pos.x_advance;
302        y_offset -= base_pos.y_advance;
303    }
304
305    let mut last_lig_component: i32 = -1;
306    let mut last_combining_class: u8 = 255;
307    let mut component_extents = base_extents;
308    let mut cluster_extents = base_extents;
309    let direction = ctx.buffer.direction;
310    let font_funcs = &mut *ctx.font_funcs;
311
312    for (info, pos) in ctx.buffer.info[base + 1..end]
313        .iter()
314        .zip(&mut ctx.buffer.pos[base + 1..end])
315    {
316        if info.modified_combining_class() != 0 {
317            if num_lig_components > 1 {
318                let this_lig_id = info.lig_id() as u32;
319                let mut this_lig_component = info.lig_comp() as i32 - 1;
320
321                // Conditions for attaching to the last component.
322                if lig_id == 0 || lig_id != this_lig_id || this_lig_component >= num_lig_components
323                {
324                    this_lig_component = num_lig_components - 1;
325                }
326
327                if last_lig_component != this_lig_component {
328                    last_lig_component = this_lig_component;
329                    last_combining_class = 255;
330                    component_extents = base_extents;
331
332                    if horizontal_dir == Direction::Invalid {
333                        horizontal_dir = if ctx.plan.direction.is_horizontal() {
334                            ctx.plan.direction
335                        } else {
336                            ctx.plan
337                                .script
338                                .and_then(Direction::from_script)
339                                .unwrap_or(Direction::LeftToRight)
340                        };
341                    }
342
343                    component_extents.x_bearing += (if horizontal_dir == Direction::LeftToRight {
344                        this_lig_component
345                    } else {
346                        num_lig_components - 1 - this_lig_component
347                    } * component_extents.width)
348                        / num_lig_components;
349
350                    component_extents.width /= num_lig_components;
351                }
352            }
353
354            let this_combining_class = info.modified_combining_class();
355            if last_combining_class != this_combining_class {
356                last_combining_class = this_combining_class;
357                cluster_extents = component_extents;
358            }
359
360            position_mark(
361                face,
362                direction,
363                font_funcs,
364                info.as_glyph(),
365                pos,
366                &mut cluster_extents,
367                this_combining_class,
368            );
369
370            pos.x_advance = 0;
371            pos.y_advance = 0;
372            pos.x_offset += x_offset;
373            pos.y_offset += y_offset;
374        } else {
375            if direction.is_forward() {
376                x_offset -= pos.x_advance;
377                y_offset -= pos.y_advance;
378            } else {
379                x_offset += pos.x_advance;
380                y_offset += pos.y_advance;
381            }
382        }
383    }
384}
385
386fn position_cluster_impl(ctx: &mut FallbackShapeContext, start: usize, end: usize) {
387    // Find the base glyph
388    let mut i = start;
389    while i < end {
390        if !ctx.buffer.info[i].is_unicode_mark() {
391            // Find mark glyphs
392            let mut j = i + 1;
393            while j < end
394                && (ctx.buffer.info[j].is_hidden()
395                    || ctx.buffer.info[j].is_default_ignorable()
396                    || ctx.buffer.info[j].is_unicode_mark())
397            {
398                j += 1;
399            }
400
401            position_around_base(ctx, i, j);
402            i = j - 1;
403        }
404        i += 1;
405    }
406}
407
408#[inline(always)]
409fn position_cluster(ctx: &mut FallbackShapeContext, start: usize, end: usize) {
410    if end - start < 2 {
411        return;
412    }
413
414    position_cluster_impl(ctx, start, end);
415}
416
417pub fn position_marks<'a, 'x>(
418    plan: &'a hb_ot_shape_plan_t,
419    face: &'a hb_font_t<'a>,
420    buffer: &'x mut hb_buffer_t,
421    adjust_offsets_when_zeroing: bool,
422    font_funcs: &'x mut FontFuncsDispatch<'a, '_>,
423) {
424    let mut ctx = FallbackShapeContext {
425        plan,
426        face,
427        buffer,
428        adjust_offsets_when_zeroing,
429        font_funcs,
430    };
431
432    ctx.buffer.assert_gsubgpos_vars();
433
434    let mut start = 0;
435    let len = ctx.buffer.len;
436    for i in 1..len {
437        if !ctx.buffer.info[i].is_unicode_mark()
438            && !ctx.buffer.info[i].is_hidden()
439            && !ctx.buffer.info[i].is_default_ignorable()
440        {
441            position_cluster(&mut ctx, start, i);
442            start = i;
443        }
444    }
445
446    position_cluster(&mut ctx, start, len);
447}
448
449pub fn _hb_ot_shape_fallback_kern(_: &hb_ot_shape_plan_t, _: &hb_font_t, _: &mut hb_buffer_t) {
450    // STUB: this is deprecated in HarfBuzz
451}
452
453pub fn _hb_ot_shape_fallback_spaces<'a, 'x>(
454    plan: &'a hb_ot_shape_plan_t,
455    face: &'a hb_font_t<'a>,
456    buffer: &'x mut hb_buffer_t,
457    font_funcs: &'x mut FontFuncsDispatch<'a, '_>,
458) {
459    use super::unicode::hb_unicode_funcs_t as t;
460
461    let _ = plan;
462    let len = buffer.len;
463    let horizontal = buffer.direction.is_horizontal();
464    let scale = *font_funcs.scale();
465    for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
466        if info.is_unicode_space() && !info.ligated() {
467            let space_type = info.unicode_space_fallback_type();
468            match space_type {
469                t::SPACE_EM
470                | t::SPACE_EM_2
471                | t::SPACE_EM_3
472                | t::SPACE_EM_4
473                | t::SPACE_EM_5
474                | t::SPACE_EM_6
475                | t::SPACE_EM_16 => {
476                    let length =
477                        (face.units_per_em as i32 + (space_type as i32) / 2) / space_type as i32;
478                    if horizontal {
479                        pos.x_advance = scale.scale_x(length);
480                    } else {
481                        pos.y_advance = -scale.scale_y(length);
482                    }
483                }
484
485                t::SPACE_4_EM_18 => {
486                    let length = ((face.units_per_em as i64) * 4 / 18) as i32;
487                    if horizontal {
488                        pos.x_advance = scale.scale_x(length);
489                    } else {
490                        pos.y_advance = -scale.scale_y(length);
491                    }
492                }
493
494                t::SPACE_FIGURE => {
495                    for u in '0'..='9' {
496                        if let Some(glyph) = font_funcs.nominal_glyph(u as u32) {
497                            if horizontal {
498                                pos.x_advance = font_funcs.advance_width(glyph);
499                            } else {
500                                pos.y_advance = font_funcs.advance_height(glyph);
501                            }
502                            break;
503                        }
504                    }
505                }
506
507                t::SPACE_PUNCTUATION => {
508                    let punct = font_funcs
509                        .nominal_glyph('.' as u32)
510                        .or_else(|| font_funcs.nominal_glyph(',' as u32));
511
512                    if let Some(glyph) = punct {
513                        if horizontal {
514                            pos.x_advance = font_funcs.advance_width(glyph);
515                        } else {
516                            pos.y_advance = font_funcs.advance_height(glyph);
517                        }
518                    }
519                }
520
521                t::SPACE_NARROW => {
522                    // Half-space?
523                    // Unicode doc https://unicode.org/charts/PDF/U2000.pdf says ~1/4 or 1/5 of EM.
524                    // However, in my testing, many fonts have their regular space being about that
525                    // size. To me, a percentage of the space width makes more sense. Half is as
526                    // good as any.
527                    if horizontal {
528                        pos.x_advance /= 2;
529                    } else {
530                        pos.y_advance /= 2;
531                    }
532                }
533
534                _ => {}
535            }
536        }
537    }
538}