1use super::algs::*;
2use super::buffer::*;
3use super::font_funcs::FontFuncsDispatch;
4use super::ot_map::*;
5use super::ot_shape::*;
6use super::ot_shape_normalize::HB_OT_SHAPE_NORMALIZATION_MODE_AUTO;
7use super::ot_shape_plan::hb_ot_shape_plan_t;
8use super::ot_shaper::*;
9use super::unicode::*;
10use super::{hb_mask_t, hb_tag_t, script, GlyphInfo, Script};
11use crate::Direction;
12use alloc::boxed::Box;
13
14const HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH: hb_buffer_scratch_flags_t =
15 HB_BUFFER_SCRATCH_FLAG_SHAPER0;
16
17fn is_word_category(gc: GeneralCategory) -> bool {
20 (rb_flag_unsafe(gc.to_u8() as u32)
21 & (rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)
22 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE)
23 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER)
24 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER)
25 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK)
26 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
27 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
28 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER)
29 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER)
30 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER)
31 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL)
32 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL)
33 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL)
34 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL)))
35 != 0
36}
37
38#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
39pub enum hb_arabic_joining_type_t {
40 U = 0,
41 L = 1,
42 R = 2,
43 D = 3,
44 GroupAlaph = 4,
46 GroupDalathRish = 5,
47 T = 6,
48 X = 7, }
50
51fn get_joining_type(u: Codepoint, gc: GeneralCategory) -> hb_arabic_joining_type_t {
52 let j_type = super::ot_shaper_arabic_table::joining_type(u);
53 if j_type != hb_arabic_joining_type_t::X {
54 return j_type;
55 }
56
57 let ok = rb_flag_unsafe(gc.to_u8() as u32)
58 & (rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
59 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
60 | rb_flag(hb_gc::HB_UNICODE_GENERAL_CATEGORY_FORMAT));
61
62 if ok != 0 {
63 hb_arabic_joining_type_t::T
64 } else {
65 hb_arabic_joining_type_t::U
66 }
67}
68
69fn feature_is_syriac(tag: hb_tag_t) -> bool {
70 matches!(tag.to_be_bytes()[3], b'2' | b'3')
71}
72
73const ARABIC_FEATURES: &[hb_tag_t] = &[
74 hb_tag_t::new(b"isol"),
75 hb_tag_t::new(b"fina"),
76 hb_tag_t::new(b"fin2"),
77 hb_tag_t::new(b"fin3"),
78 hb_tag_t::new(b"medi"),
79 hb_tag_t::new(b"med2"),
80 hb_tag_t::new(b"init"),
81];
82
83mod arabic_action_t {
84 pub const ISOL: u8 = 0;
85 pub const FINA: u8 = 1;
86 pub const FIN2: u8 = 2;
87 pub const FIN3: u8 = 3;
88 pub const MEDI: u8 = 4;
89 pub const MED2: u8 = 5;
90 pub const INIT: u8 = 6;
91 pub const NONE: u8 = 7;
92
93 pub const STRETCHING_FIXED: u8 = 8;
95 pub const STRETCHING_REPEATING: u8 = 9;
96
97 #[inline]
98 pub fn is_stch(n: u8) -> bool {
99 matches!(n, STRETCHING_FIXED | STRETCHING_REPEATING)
100 }
101}
102
103static STATE_TABLE: &[[(u8, u8, u16); 6]] = &[
104 [
109 (arabic_action_t::NONE, arabic_action_t::NONE, 0),
110 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
111 (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
112 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
113 (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
114 (arabic_action_t::NONE, arabic_action_t::ISOL, 6),
115 ],
116 [
118 (arabic_action_t::NONE, arabic_action_t::NONE, 0),
119 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
120 (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
121 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
122 (arabic_action_t::NONE, arabic_action_t::FIN2, 5),
123 (arabic_action_t::NONE, arabic_action_t::ISOL, 6),
124 ],
125 [
127 (arabic_action_t::NONE, arabic_action_t::NONE, 0),
128 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
129 (arabic_action_t::INIT, arabic_action_t::FINA, 1),
130 (arabic_action_t::INIT, arabic_action_t::FINA, 3),
131 (arabic_action_t::INIT, arabic_action_t::FINA, 4),
132 (arabic_action_t::INIT, arabic_action_t::FINA, 6),
133 ],
134 [
136 (arabic_action_t::NONE, arabic_action_t::NONE, 0),
137 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
138 (arabic_action_t::MEDI, arabic_action_t::FINA, 1),
139 (arabic_action_t::MEDI, arabic_action_t::FINA, 3),
140 (arabic_action_t::MEDI, arabic_action_t::FINA, 4),
141 (arabic_action_t::MEDI, arabic_action_t::FINA, 6),
142 ],
143 [
145 (arabic_action_t::NONE, arabic_action_t::NONE, 0),
146 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
147 (arabic_action_t::MED2, arabic_action_t::ISOL, 1),
148 (arabic_action_t::MED2, arabic_action_t::ISOL, 2),
149 (arabic_action_t::MED2, arabic_action_t::FIN2, 5),
150 (arabic_action_t::MED2, arabic_action_t::ISOL, 6),
151 ],
152 [
154 (arabic_action_t::NONE, arabic_action_t::NONE, 0),
155 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
156 (arabic_action_t::ISOL, arabic_action_t::ISOL, 1),
157 (arabic_action_t::ISOL, arabic_action_t::ISOL, 2),
158 (arabic_action_t::ISOL, arabic_action_t::FIN2, 5),
159 (arabic_action_t::ISOL, arabic_action_t::ISOL, 6),
160 ],
161 [
163 (arabic_action_t::NONE, arabic_action_t::NONE, 0),
164 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
165 (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
166 (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
167 (arabic_action_t::NONE, arabic_action_t::FIN3, 5),
168 (arabic_action_t::NONE, arabic_action_t::ISOL, 6),
169 ],
170];
171
172impl GlyphInfo {
173 declare_buffer_var_alias!(
174 OT_SHAPER_VAR_U8_AUXILIARY_VAR,
175 u8,
176 ARABIC_SHAPING_ACTION_VAR,
177 arabic_shaping_action,
178 set_arabic_shaping_action
179 );
180}
181
182fn deallocate_buffer_var(
183 _: &hb_ot_shape_plan_t,
184 _: &mut FontFuncsDispatch,
185 buffer: &mut hb_buffer_t,
186) -> bool {
187 buffer.deallocate_var(GlyphInfo::ARABIC_SHAPING_ACTION_VAR);
188
189 false
190}
191
192fn collect_features(planner: &mut hb_ot_shape_planner_t) {
193 planner
215 .ot_map
216 .enable_feature(hb_tag_t::new(b"stch"), F_NONE, 1);
217 planner.ot_map.add_gsub_pause(Some(record_stch));
218
219 planner
220 .ot_map
221 .enable_feature(hb_tag_t::new(b"ccmp"), F_MANUAL_ZWJ, 1);
222 planner
223 .ot_map
224 .enable_feature(hb_tag_t::new(b"locl"), F_MANUAL_ZWJ, 1);
225
226 planner.ot_map.add_gsub_pause(None);
227
228 for feature in ARABIC_FEATURES {
229 let has_fallback = planner.script == Some(script::ARABIC) && !feature_is_syriac(*feature);
230 let flags = if has_fallback { F_HAS_FALLBACK } else { F_NONE };
231 planner
232 .ot_map
233 .add_feature(*feature, F_MANUAL_ZWJ | flags, 1);
234 planner.ot_map.add_gsub_pause(None);
235 }
236 planner.ot_map.add_gsub_pause(Some(deallocate_buffer_var));
237
238 planner
243 .ot_map
244 .enable_feature(hb_tag_t::new(b"rlig"), F_MANUAL_ZWJ | F_HAS_FALLBACK, 1);
245
246 if planner.script == Some(script::ARABIC) {
247 planner.ot_map.add_gsub_pause(Some(arabic_fallback_shape));
248 }
249
250 planner
253 .ot_map
254 .enable_feature(hb_tag_t::new(b"calt"), F_MANUAL_ZWJ, 1);
255 if !planner.ot_map.has_feature(hb_tag_t::new(b"rclt")) {
257 planner.ot_map.add_gsub_pause(None);
258 }
259
260 planner
261 .ot_map
262 .enable_feature(hb_tag_t::new(b"liga"), F_MANUAL_ZWJ, 1);
263 planner
264 .ot_map
265 .enable_feature(hb_tag_t::new(b"clig"), F_MANUAL_ZWJ, 1);
266
267 planner
278 .ot_map
279 .enable_feature(hb_tag_t::new(b"mset"), F_MANUAL_ZWJ, 1);
280}
281
282pub struct arabic_shape_plan_t {
283 mask_array: [hb_mask_t; ARABIC_FEATURES.len() + 1],
288 has_stch: bool,
289}
290
291pub fn data_create_arabic(plan: &hb_ot_shape_plan_t) -> arabic_shape_plan_t {
292 let has_stch = plan.ot_map.get_1_mask(hb_tag_t::new(b"stch")) != 0;
293
294 let mut mask_array = [0; ARABIC_FEATURES.len() + 1];
295 for i in 0..ARABIC_FEATURES.len() {
296 mask_array[i] = plan.ot_map.get_1_mask(ARABIC_FEATURES[i]);
297 }
298
299 arabic_shape_plan_t {
300 mask_array,
301 has_stch,
302 }
303}
304
305fn arabic_joining(buffer: &mut hb_buffer_t) {
306 let mut prev: Option<usize> = None;
307 let mut state = 0;
308
309 for i in 0..buffer.context_len[0] {
311 let c = buffer.context[0][i] as Codepoint;
312 let this_type = get_joining_type(c, c.general_category());
313 if this_type == hb_arabic_joining_type_t::T {
314 continue;
315 }
316
317 state = STATE_TABLE[state][this_type as usize].2 as usize;
318 break;
319 }
320
321 for i in 0..buffer.len {
322 let this_type = get_joining_type(
323 buffer.info[i].as_codepoint(),
324 buffer.info[i].general_category(),
325 );
326 if this_type == hb_arabic_joining_type_t::T {
327 buffer.info[i].set_arabic_shaping_action(arabic_action_t::NONE);
328 continue;
329 }
330
331 let entry = &STATE_TABLE[state][this_type as usize];
332 if entry.0 != arabic_action_t::NONE && prev.is_some() {
333 if let Some(prev) = prev {
334 buffer.info[prev].set_arabic_shaping_action(entry.0);
335 buffer.safe_to_insert_tatweel(Some(prev), Some(i + 1));
336 }
337 }
338 else {
340 if let Some(prev) = prev {
341 if this_type >= hb_arabic_joining_type_t::R || (2 <= state && state <= 5) {
342 buffer.unsafe_to_concat(Some(prev), Some(i + 1));
343 }
344 } else {
345 if this_type >= hb_arabic_joining_type_t::R {
346 buffer.unsafe_to_concat_from_outbuffer(Some(0), Some(i + 1));
347 }
348 }
349 }
350
351 buffer.info[i].set_arabic_shaping_action(entry.1);
352
353 prev = Some(i);
354 state = entry.2 as usize;
355 }
356
357 for i in 0..buffer.context_len[1] {
358 let c = buffer.context[1][i] as Codepoint;
359 let this_type = get_joining_type(c, c.general_category());
360 if this_type == hb_arabic_joining_type_t::T {
361 continue;
362 }
363
364 let entry = &STATE_TABLE[state][this_type as usize];
365 if entry.0 != arabic_action_t::NONE && prev.is_some() {
366 if let Some(prev) = prev {
367 buffer.info[prev].set_arabic_shaping_action(entry.0);
368 buffer.safe_to_insert_tatweel(Some(prev), Some(buffer.len));
369 }
370 }
371 else if 2 <= state && state <= 5 {
373 if let Some(prev) = prev {
374 buffer.unsafe_to_concat(Some(prev), Some(buffer.len));
375 }
376 }
377
378 break;
379 }
380}
381
382fn mongolian_variation_selectors(buffer: &mut hb_buffer_t) {
383 let len = buffer.len;
385 let info = &mut buffer.info;
386 for i in 1..len {
387 if (0x180B..=0x180D).contains(&info[i].glyph_id) || info[i].glyph_id == 0x180F {
388 let a = info[i - 1].arabic_shaping_action();
389 info[i].set_arabic_shaping_action(a);
390 }
391 }
392}
393
394fn setup_masks_arabic_plan(
395 plan: &hb_ot_shape_plan_t,
396 _: &mut FontFuncsDispatch,
397 buffer: &mut hb_buffer_t,
398) {
399 buffer.allocate_var(GlyphInfo::ARABIC_SHAPING_ACTION_VAR);
400
401 let arabic_plan = plan.data::<arabic_shape_plan_t>();
402 setup_masks_inner(arabic_plan, plan.script, buffer);
403}
404
405pub fn setup_masks_inner(
406 arabic_plan: &arabic_shape_plan_t,
407 script: Option<Script>,
408 buffer: &mut hb_buffer_t,
409) {
410 arabic_joining(buffer);
411 if script == Some(script::MONGOLIAN) {
412 mongolian_variation_selectors(buffer);
413 }
414
415 for info in buffer.info_slice_mut() {
416 info.mask |= arabic_plan.mask_array[info.arabic_shaping_action() as usize];
417 }
418}
419
420fn arabic_fallback_shape(
421 _: &hb_ot_shape_plan_t,
422 _: &mut FontFuncsDispatch,
423 _: &mut hb_buffer_t,
424) -> bool {
425 false
426}
427
428fn record_stch(
434 plan: &hb_ot_shape_plan_t,
435 _: &mut FontFuncsDispatch,
436 buffer: &mut hb_buffer_t,
437) -> bool {
438 let arabic_plan = plan.data::<arabic_shape_plan_t>();
439 if !arabic_plan.has_stch {
440 return false;
441 }
442
443 let len = buffer.len;
449 let info = &mut buffer.info;
450 let mut has_stch = false;
451 for glyph_info in &mut info[..len] {
452 if glyph_info.multiplied() {
453 let comp = if glyph_info.lig_comp() % 2 != 0 {
454 arabic_action_t::STRETCHING_REPEATING
455 } else {
456 arabic_action_t::STRETCHING_FIXED
457 };
458
459 glyph_info.set_arabic_shaping_action(comp);
460 has_stch = true;
461 }
462 }
463
464 if has_stch {
465 buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH;
466 }
467
468 false
469}
470
471fn apply_stch(face: &mut FontFuncsDispatch, buffer: &mut hb_buffer_t) {
472 if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH == 0 {
473 return;
474 }
475
476 let rtl = buffer.direction == Direction::RightToLeft;
477
478 if !rtl {
479 buffer.reverse();
480 }
481
482 let mut extra_glyphs_needed: usize = 0; const MEASURE: usize = 0;
489 const CUT: usize = 1;
490
491 for step in 0..2 {
492 let new_len = buffer.len + extra_glyphs_needed; let mut i = buffer.len;
494 let mut j = new_len;
495 while i != 0 {
496 if !arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
497 if step == CUT {
498 j -= 1;
499 buffer.info[j] = buffer.info[i - 1];
500 buffer.pos[j] = buffer.pos[i - 1];
501 }
502
503 i -= 1;
504 continue;
505 }
506
507 let mut w_total = 0; let mut w_fixed = 0; let mut w_repeating = 0; let mut n_repeating: i32 = 0;
513
514 let end = i;
515 while i != 0 && arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
516 i -= 1;
517 let width = face.advance_width(buffer.info[i].as_glyph());
518
519 if buffer.info[i].arabic_shaping_action() == arabic_action_t::STRETCHING_FIXED {
520 w_fixed += width;
521 } else {
522 w_repeating += width;
523 n_repeating += 1;
524 }
525 }
526
527 let start = i;
528 let mut context = i;
529 while context != 0
530 && !arabic_action_t::is_stch(buffer.info[context - 1].arabic_shaping_action())
531 && (buffer.info[context - 1].is_default_ignorable()
532 || is_word_category(buffer.info[context - 1].general_category()))
533 {
534 context -= 1;
535 w_total += buffer.pos[context].x_advance;
536 }
537
538 i += 1; let mut n_copies: i32 = 0;
542
543 let mut w_remaining = w_total - w_fixed;
544 if w_remaining > w_repeating && w_repeating > 0 {
545 n_copies = w_remaining / (w_repeating) - 1;
546 }
547
548 let mut extra_repeat_overlap = 0;
550 let shortfall = w_remaining - w_repeating * (n_copies + 1);
551 if shortfall > 0 && n_repeating > 0 {
552 n_copies += 1;
553 let excess = (n_copies + 1) * w_repeating - w_remaining;
554 if excess > 0 {
555 extra_repeat_overlap = excess / (n_copies * n_repeating);
556 w_remaining = 0;
557 }
558 }
559
560 if step == MEASURE {
561 extra_glyphs_needed += (n_copies * n_repeating) as usize;
562 } else {
563 buffer.unsafe_to_break(Some(context), Some(end));
564 let mut x_offset = w_remaining / 2;
565 for k in (start + 1..=end).rev() {
566 let width = face.advance_width(buffer.info[k - 1].as_glyph());
567
568 let mut repeat = 1;
569 if buffer.info[k - 1].arabic_shaping_action()
570 == arabic_action_t::STRETCHING_REPEATING
571 {
572 repeat += n_copies;
573 }
574
575 buffer.pos[k - 1].x_advance = 0;
576
577 for n in 0..repeat {
578 if rtl {
579 x_offset -= width;
580 if n > 0 {
581 x_offset += extra_repeat_overlap;
582 }
583 }
584
585 buffer.pos[k - 1].x_offset = x_offset;
586
587 j -= 1;
589 buffer.info[j] = buffer.info[k - 1];
590 buffer.pos[j] = buffer.pos[k - 1];
591
592 if !rtl {
593 x_offset += width;
594
595 if n > 0 {
596 x_offset -= extra_repeat_overlap;
597 }
598 }
599 }
600 }
601 }
602
603 i -= 1;
604 }
605
606 if step == MEASURE {
607 if !buffer.ensure(buffer.len + extra_glyphs_needed) {
608 break;
609 }
610 } else {
611 debug_assert_eq!(j, 0);
612 buffer.len = new_len;
613 }
614 }
615
616 if !rtl {
617 buffer.reverse();
618 }
619}
620
621fn postprocess_glyphs_arabic(
622 _: &hb_ot_shape_plan_t,
623 face: &mut FontFuncsDispatch,
624 buffer: &mut hb_buffer_t,
625) {
626 apply_stch(face, buffer);
627}
628
629static MODIFIER_COMBINING_MARKS: &[u32] = &[
631 0x0654, 0x0655, 0x0658, 0x06DC, 0x06E3, 0x06E7, 0x06E8, 0x08CA, 0x08CB, 0x08CD, 0x08CE, 0x08CF, 0x08D3, 0x08F3, ];
646
647fn reorder_marks_arabic(
648 _: &hb_ot_shape_plan_t,
649 buffer: &mut hb_buffer_t,
650 mut start: usize,
651 end: usize,
652) {
653 let mut i = start;
654 for cc in [220u8, 230] {
655 while i < end && buffer.info[i].modified_combining_class() < cc {
656 i += 1;
657 }
658
659 if i == end {
660 break;
661 }
662
663 if buffer.info[i].modified_combining_class() > cc {
664 continue;
665 }
666
667 let mut j = i;
668 while j < end
669 && buffer.info[j].modified_combining_class() == cc
670 && MODIFIER_COMBINING_MARKS.contains(&buffer.info[j].glyph_id)
671 {
672 j += 1;
673 }
674
675 if i == j {
676 continue;
677 }
678
679 let mut temp = [GlyphInfo::default(); MAX_COMBINING_MARKS];
681 debug_assert!(j - i <= MAX_COMBINING_MARKS);
682 buffer.merge_clusters(start, j);
683
684 temp[..j - i].copy_from_slice(&buffer.info[i..j]);
685
686 for k in (0..i - start).rev() {
687 buffer.info[k + start + j - i] = buffer.info[k + start];
688 }
689
690 buffer.info[start..][..j - i].copy_from_slice(&temp[..j - i]);
691
692 let new_start = start + j - i;
704 let new_cc = if cc == 220 {
705 modified_combining_class::CCC22
706 } else {
707 modified_combining_class::CCC26
708 };
709
710 while start < new_start {
711 buffer.info[start].set_modified_combining_class(new_cc);
712 start += 1;
713 }
714
715 i = j;
716 }
717}
718
719pub const ARABIC_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
720 collect_features: Some(collect_features),
721 override_features: None,
722 create_data: Some(|plan| Box::new(data_create_arabic(plan))),
723 preprocess_text: None,
724 postprocess_glyphs: Some(postprocess_glyphs_arabic),
725 normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
726 decompose: None,
727 compose: None,
728 setup_masks: Some(setup_masks_arabic_plan),
729 gpos_tag: None,
730 reorder_marks: Some(reorder_marks_arabic),
731 zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
732 fallback_position: true,
733};