rustybuzz/hb/
ot_shape_fallback.rs

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