use alloc::boxed::Box;
use super::algs::*;
use super::buffer::*;
use super::ot_layout::*;
use super::ot_map::*;
use super::ot_shape::*;
use super::ot_shape_complex::*;
use super::ot_shape_normalize::HB_OT_SHAPE_NORMALIZATION_MODE_AUTO;
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::*;
use super::{hb_font_t, hb_glyph_info_t, hb_mask_t, hb_tag_t, script, Script};
const HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH: hb_buffer_scratch_flags_t =
HB_BUFFER_SCRATCH_FLAG_COMPLEX0;
fn is_word_category(gc: hb_unicode_general_category_t) -> bool {
(rb_flag_unsafe(gc.to_rb())
& (rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_SPACING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL)))
!= 0
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub enum hb_arabic_joining_type_t {
U = 0,
L = 1,
R = 2,
D = 3,
GroupAlaph = 4,
GroupDalathRish = 5,
T = 7,
X = 8, }
fn get_joining_type(u: char, gc: hb_unicode_general_category_t) -> hb_arabic_joining_type_t {
let j_type = super::ot_shape_complex_arabic_table::joining_type(u);
if j_type != hb_arabic_joining_type_t::X {
return j_type;
}
let ok = rb_flag_unsafe(gc.to_rb())
& (rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
| rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_FORMAT));
if ok != 0 {
hb_arabic_joining_type_t::T
} else {
hb_arabic_joining_type_t::U
}
}
fn feature_is_syriac(tag: hb_tag_t) -> bool {
matches!(tag.to_bytes()[3], b'2' | b'3')
}
const ARABIC_FEATURES: &[hb_tag_t] = &[
hb_tag_t::from_bytes(b"isol"),
hb_tag_t::from_bytes(b"fina"),
hb_tag_t::from_bytes(b"fin2"),
hb_tag_t::from_bytes(b"fin3"),
hb_tag_t::from_bytes(b"medi"),
hb_tag_t::from_bytes(b"med2"),
hb_tag_t::from_bytes(b"init"),
];
mod arabic_action_t {
pub const ISOL: u8 = 0;
pub const FINA: u8 = 1;
pub const FIN2: u8 = 2;
pub const FIN3: u8 = 3;
pub const MEDI: u8 = 4;
pub const MED2: u8 = 5;
pub const INIT: u8 = 6;
pub const NONE: u8 = 7;
pub const STRETCHING_FIXED: u8 = 8;
pub const STRETCHING_REPEATING: u8 = 9;
#[inline]
pub fn is_stch(n: u8) -> bool {
matches!(n, STRETCHING_FIXED | STRETCHING_REPEATING)
}
}
const STATE_TABLE: &[[(u8, u8, u16); 6]] = &[
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 6),
],
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::FIN2, 5),
(arabic_action_t::NONE, arabic_action_t::ISOL, 6),
],
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::INIT, arabic_action_t::FINA, 1),
(arabic_action_t::INIT, arabic_action_t::FINA, 3),
(arabic_action_t::INIT, arabic_action_t::FINA, 4),
(arabic_action_t::INIT, arabic_action_t::FINA, 6),
],
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::MEDI, arabic_action_t::FINA, 1),
(arabic_action_t::MEDI, arabic_action_t::FINA, 3),
(arabic_action_t::MEDI, arabic_action_t::FINA, 4),
(arabic_action_t::MEDI, arabic_action_t::FINA, 6),
],
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::MED2, arabic_action_t::ISOL, 1),
(arabic_action_t::MED2, arabic_action_t::ISOL, 2),
(arabic_action_t::MED2, arabic_action_t::FIN2, 5),
(arabic_action_t::MED2, arabic_action_t::ISOL, 6),
],
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::ISOL, arabic_action_t::ISOL, 1),
(arabic_action_t::ISOL, arabic_action_t::ISOL, 2),
(arabic_action_t::ISOL, arabic_action_t::FIN2, 5),
(arabic_action_t::ISOL, arabic_action_t::ISOL, 6),
],
[
(arabic_action_t::NONE, arabic_action_t::NONE, 0),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::ISOL, 1),
(arabic_action_t::NONE, arabic_action_t::ISOL, 2),
(arabic_action_t::NONE, arabic_action_t::FIN3, 5),
(arabic_action_t::NONE, arabic_action_t::ISOL, 6),
],
];
impl hb_glyph_info_t {
fn arabic_shaping_action(&self) -> u8 {
self.complex_var_u8_auxiliary()
}
fn set_arabic_shaping_action(&mut self, action: u8) {
self.set_complex_var_u8_auxiliary(action)
}
}
fn collect_features(planner: &mut hb_ot_shape_planner_t) {
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"stch"), F_NONE, 1);
planner.ot_map.add_gsub_pause(Some(record_stch));
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_NONE, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"locl"), F_NONE, 1);
planner.ot_map.add_gsub_pause(None);
for feature in ARABIC_FEATURES {
let has_fallback = planner.script == Some(script::ARABIC) && !feature_is_syriac(*feature);
let flags = if has_fallback { F_HAS_FALLBACK } else { F_NONE };
planner.ot_map.add_feature(*feature, flags, 1);
planner.ot_map.add_gsub_pause(None);
}
planner.ot_map.enable_feature(
hb_tag_t::from_bytes(b"rlig"),
F_MANUAL_ZWJ | F_HAS_FALLBACK,
1,
);
if planner.script == Some(script::ARABIC) {
planner.ot_map.add_gsub_pause(Some(arabic_fallback_shape));
}
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"rclt"), F_MANUAL_ZWJ, 1);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"calt"), F_MANUAL_ZWJ, 1);
planner.ot_map.add_gsub_pause(None);
planner
.ot_map
.enable_feature(hb_tag_t::from_bytes(b"mset"), F_NONE, 1);
}
pub struct arabic_shape_plan_t {
mask_array: [hb_mask_t; ARABIC_FEATURES.len() + 1],
has_stch: bool,
}
pub fn data_create_arabic(plan: &hb_ot_shape_plan_t) -> arabic_shape_plan_t {
let has_stch = plan.ot_map.get_1_mask(hb_tag_t::from_bytes(b"stch")) != 0;
let mut mask_array = [0; ARABIC_FEATURES.len() + 1];
for i in 0..ARABIC_FEATURES.len() {
mask_array[i] = plan.ot_map.get_1_mask(ARABIC_FEATURES[i]);
}
arabic_shape_plan_t {
mask_array,
has_stch,
}
}
fn arabic_joining(buffer: &mut hb_buffer_t) {
let mut prev: Option<usize> = None;
let mut state = 0;
for i in 0..buffer.context_len[0] {
let c = buffer.context[0][i];
let this_type = get_joining_type(c, c.general_category());
if this_type == hb_arabic_joining_type_t::T {
continue;
}
state = STATE_TABLE[state][this_type as usize].2 as usize;
break;
}
for i in 0..buffer.len {
let this_type = get_joining_type(
buffer.info[i].as_char(),
_hb_glyph_info_get_general_category(&buffer.info[i]),
);
if this_type == hb_arabic_joining_type_t::T {
buffer.info[i].set_arabic_shaping_action(arabic_action_t::NONE);
continue;
}
let entry = &STATE_TABLE[state][this_type as usize];
if entry.0 != arabic_action_t::NONE && prev.is_some() {
if let Some(prev) = prev {
buffer.info[prev].set_arabic_shaping_action(entry.0);
buffer.unsafe_to_break(Some(prev), Some(i + 1));
}
}
else {
if let Some(prev) = prev {
if this_type >= hb_arabic_joining_type_t::R || (2 <= state && state <= 5) {
buffer.unsafe_to_concat(Some(prev), Some(i + 1));
}
} else {
if this_type >= hb_arabic_joining_type_t::R {
buffer.unsafe_to_concat_from_outbuffer(Some(0), Some(i + 1));
}
}
}
buffer.info[i].set_arabic_shaping_action(entry.1);
prev = Some(i);
state = entry.2 as usize;
}
for i in 0..buffer.context_len[1] {
let c = buffer.context[1][i];
let this_type = get_joining_type(c, c.general_category());
if this_type == hb_arabic_joining_type_t::T {
continue;
}
let entry = &STATE_TABLE[state][this_type as usize];
if entry.0 != arabic_action_t::NONE && prev.is_some() {
if let Some(prev) = prev {
buffer.info[prev].set_arabic_shaping_action(entry.0);
buffer.unsafe_to_break(Some(prev), Some(buffer.len));
}
}
else if 2 <= state && state <= 5 {
if let Some(prev) = prev {
buffer.unsafe_to_concat(Some(prev), Some(buffer.len));
}
}
break;
}
}
fn mongolian_variation_selectors(buffer: &mut hb_buffer_t) {
let len = buffer.len;
let info = &mut buffer.info;
for i in 1..len {
if (0x180B..=0x180D).contains(&info[i].glyph_id) || info[i].glyph_id == 0x180F {
let a = info[i - 1].arabic_shaping_action();
info[i].set_arabic_shaping_action(a);
}
}
}
fn setup_masks_arabic_plan(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let arabic_plan = plan.data::<arabic_shape_plan_t>();
setup_masks_inner(arabic_plan, plan.script, buffer)
}
pub fn setup_masks_inner(
arabic_plan: &arabic_shape_plan_t,
script: Option<Script>,
buffer: &mut hb_buffer_t,
) {
arabic_joining(buffer);
if script == Some(script::MONGOLIAN) {
mongolian_variation_selectors(buffer);
}
for info in buffer.info_slice_mut() {
info.mask |= arabic_plan.mask_array[info.arabic_shaping_action() as usize];
}
}
fn arabic_fallback_shape(_: &hb_ot_shape_plan_t, _: &hb_font_t, _: &mut hb_buffer_t) {}
fn record_stch(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
let arabic_plan = plan.data::<arabic_shape_plan_t>();
if !arabic_plan.has_stch {
return;
}
let len = buffer.len;
let info = &mut buffer.info;
let mut has_stch = false;
for glyph_info in &mut info[..len] {
if _hb_glyph_info_multiplied(glyph_info) {
let comp = if _hb_glyph_info_get_lig_comp(glyph_info) % 2 != 0 {
arabic_action_t::STRETCHING_REPEATING
} else {
arabic_action_t::STRETCHING_FIXED
};
glyph_info.set_arabic_shaping_action(comp);
has_stch = true;
}
}
if has_stch {
buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH;
}
}
fn apply_stch(face: &hb_font_t, buffer: &mut hb_buffer_t) {
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH == 0 {
return;
}
let mut extra_glyphs_needed: usize = 0; const MEASURE: usize = 0;
const CUT: usize = 1;
for step in 0..2 {
let new_len = buffer.len + extra_glyphs_needed; let mut i = buffer.len;
let mut j = new_len;
while i != 0 {
if !arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
if step == CUT {
j -= 1;
buffer.info[j] = buffer.info[i - 1];
buffer.pos[j] = buffer.pos[i - 1];
}
i -= 1;
continue;
}
let mut w_total = 0; let mut w_fixed = 0; let mut w_repeating = 0; let mut n_repeating: i32 = 0;
let end = i;
while i != 0 && arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
i -= 1;
let width = face.glyph_h_advance(buffer.info[i].as_glyph()) as i32;
if buffer.info[i].arabic_shaping_action() == arabic_action_t::STRETCHING_FIXED {
w_fixed += width;
} else {
w_repeating += width;
n_repeating += 1;
}
}
let start = i;
let mut context = i;
while context != 0
&& !arabic_action_t::is_stch(buffer.info[context - 1].arabic_shaping_action())
&& (_hb_glyph_info_is_default_ignorable(&buffer.info[context - 1])
|| is_word_category(_hb_glyph_info_get_general_category(
&buffer.info[context - 1],
)))
{
context -= 1;
w_total += buffer.pos[context].x_advance;
}
i += 1; let mut n_copies: i32 = 0;
let w_remaining = w_total - w_fixed;
if w_remaining > w_repeating && w_repeating > 0 {
n_copies = w_remaining / (w_repeating) - 1;
}
let mut extra_repeat_overlap = 0;
let shortfall = w_remaining - w_repeating * (n_copies + 1);
if shortfall > 0 && n_repeating > 0 {
n_copies += 1;
let excess = (n_copies + 1) * w_repeating - w_remaining;
if excess > 0 {
extra_repeat_overlap = excess / (n_copies * n_repeating);
}
}
if step == MEASURE {
extra_glyphs_needed += (n_copies * n_repeating) as usize;
} else {
buffer.unsafe_to_break(Some(context), Some(end));
let mut x_offset = 0;
for k in (start + 1..=end).rev() {
let width = face.glyph_h_advance(buffer.info[k - 1].as_glyph()) as i32;
let mut repeat = 1;
if buffer.info[k - 1].arabic_shaping_action()
== arabic_action_t::STRETCHING_REPEATING
{
repeat += n_copies;
}
for n in 0..repeat {
x_offset -= width;
if n > 0 {
x_offset += extra_repeat_overlap;
}
buffer.pos[k - 1].x_offset = x_offset;
j -= 1;
buffer.info[j] = buffer.info[k - 1];
buffer.pos[j] = buffer.pos[k - 1];
}
}
}
i -= 1;
}
if step == MEASURE {
buffer.ensure(buffer.len + extra_glyphs_needed);
} else {
debug_assert_eq!(j, 0);
buffer.set_len(new_len);
}
}
}
fn postprocess_glyphs_arabic(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
apply_stch(face, buffer)
}
const MODIFIER_COMBINING_MARKS: &[u32] = &[
0x0654, 0x0655, 0x0658, 0x06DC, 0x06E3, 0x06E7, 0x06E8, 0x08CA, 0x08CB, 0x08CD, 0x08CE, 0x08CF, 0x08D3, 0x08F3, ];
fn reorder_marks_arabic(
_: &hb_ot_shape_plan_t,
buffer: &mut hb_buffer_t,
mut start: usize,
end: usize,
) {
let mut i = start;
for cc in [220u8, 230].iter().cloned() {
while i < end && _hb_glyph_info_get_modified_combining_class(&buffer.info[i]) < cc {
i += 1;
}
if i == end {
break;
}
if _hb_glyph_info_get_modified_combining_class(&buffer.info[i]) > cc {
continue;
}
let mut j = i;
while j < end
&& _hb_glyph_info_get_modified_combining_class(&buffer.info[j]) == cc
&& MODIFIER_COMBINING_MARKS.contains(&buffer.info[j].glyph_id)
{
j += 1;
}
if i == j {
continue;
}
let mut temp = [hb_glyph_info_t::default(); MAX_COMBINING_MARKS];
debug_assert!(j - i <= MAX_COMBINING_MARKS);
buffer.merge_clusters(start, j);
temp[..j - i].copy_from_slice(&buffer.info[i..j]);
for k in (0..i - start).rev() {
buffer.info[k + start + j - i] = buffer.info[k + start];
}
buffer.info[start..][..j - i].copy_from_slice(&temp[..j - i]);
let new_start = start + j - i;
let new_cc = if cc == 220 {
modified_combining_class::CCC22
} else {
modified_combining_class::CCC26
};
while start < new_start {
_hb_glyph_info_set_modified_combining_class(&mut buffer.info[start], new_cc);
start += 1;
}
i = j;
}
}
pub const ARABIC_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
collect_features: Some(collect_features),
override_features: None,
create_data: Some(|plan| Box::new(data_create_arabic(plan))),
preprocess_text: None,
postprocess_glyphs: Some(postprocess_glyphs_arabic),
normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
decompose: None,
compose: None,
setup_masks: Some(setup_masks_arabic_plan),
gpos_tag: None,
reorder_marks: Some(reorder_marks_arabic),
zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
fallback_position: true,
};