use crate::{script, Tag, Face, GlyphInfo, Mask, Script};
use crate::buffer::{Buffer, BufferFlags};
use crate::ot::{feature, FeatureFlags};
use crate::plan::{ShapePlan, ShapePlanner};
use crate::unicode::{CharExt, GeneralCategoryExt};
use super::*;
use super::arabic::ArabicShapePlan;
pub const UNIVERSAL_SHAPER: ComplexShaper = ComplexShaper {
collect_features: Some(collect_features),
override_features: None,
create_data: Some(|plan| Box::new(UniversalShapePlan::new(plan))),
preprocess_text: Some(preprocess_text),
postprocess_glyphs: None,
normalization_mode: Some(ShapeNormalizationMode::ComposedDiacriticsNoShortCircuit),
decompose: None,
compose: Some(compose),
setup_masks: Some(setup_masks),
gpos_tag: None,
reorder_marks: None,
zero_width_marks: Some(ZeroWidthMarksMode::ByGdefEarly),
fallback_position: false,
};
pub type Category = u8;
pub mod category {
pub const O: u8 = 0;
pub const B: u8 = 1;
pub const IND: u8 = 3;
pub const N: u8 = 4;
pub const GB: u8 = 5;
pub const CGJ: u8 = 6;
pub const FM: u8 = 8;
pub const SUB: u8 = 11;
pub const H: u8 = 12;
pub const HN: u8 = 13;
pub const ZWNJ: u8 = 14;
pub const ZWJ: u8 = 15;
pub const WJ: u8 = 16;
pub const R: u8 = 18;
pub const S: u8 = 19;
pub const VS: u8 = 21;
pub const CS: u8 = 43;
pub const HVM: u8 = 44;
pub const SK: u8 = 48;
pub const FABV: u8 = 24;
pub const FBLW: u8 = 25;
pub const FPST: u8 = 26;
pub const MABV: u8 = 27;
pub const MBLW: u8 = 28;
pub const MPST: u8 = 29;
pub const MPRE: u8 = 30;
pub const CMABV: u8 = 31;
pub const CMBLW: u8 = 32;
pub const VABV: u8 = 33;
pub const VBLW: u8 = 34;
pub const VPST: u8 = 35;
pub const VPRE: u8 = 22;
pub const VMABV: u8 = 37;
pub const VMBLW: u8 = 38;
pub const VMPST: u8 = 39;
pub const VMPRE: u8 = 23;
pub const SMABV: u8 = 41;
pub const SMBLW: u8 = 42;
pub const FMABV: u8 = 45;
pub const FMBLW: u8 = 46;
pub const FMPST: u8 = 47;
}
const BASIC_FEATURES: &[Tag] = &[
feature::RAKAR_FORMS,
feature::ABOVE_BASE_FORMS,
feature::BELOW_BASE_FORMS,
feature::HALF_FORMS,
feature::POST_BASE_FORMS,
feature::VATTU_VARIANTS,
feature::CONJUNCT_FORMS,
];
const TOPOGRAPHICAL_FEATURES: &[Tag] = &[
feature::ISOLATED_FORMS,
feature::INITIAL_FORMS,
feature::MEDIAL_FORMS_1,
feature::TERMINAL_FORMS_1,
];
#[derive(Clone, Copy, PartialEq)]
enum JoiningForm {
Isolated = 0,
Initial,
Medial,
Terminal,
}
const OTHER_FEATURES: &[Tag] = &[
feature::ABOVE_BASE_SUBSTITUTIONS,
feature::BELOW_BASE_SUBSTITUTIONS,
feature::HALANT_FORMS,
feature::PRE_BASE_SUBSTITUTIONS,
feature::POST_BASE_SUBSTITUTIONS,
];
impl GlyphInfo {
fn use_category(&self) -> Category {
let v: &[u8; 4] = bytemuck::cast_ref(&self.var2);
v[2]
}
fn set_use_category(&mut self, c: Category) {
let v: &mut [u8; 4] = bytemuck::cast_mut(&mut self.var2);
v[2] = c;
}
fn is_halant_use(&self) -> bool {
matches!(self.use_category(), category::H | category::HVM) && !self.is_ligated()
}
}
struct UniversalShapePlan {
rphf_mask: Mask,
arabic_plan: Option<ArabicShapePlan>,
}
impl UniversalShapePlan {
fn new(plan: &ShapePlan) -> UniversalShapePlan {
let mut arabic_plan = None;
if plan.script.map_or(false, has_arabic_joining) {
arabic_plan = Some(super::arabic::ArabicShapePlan::new(plan));
}
UniversalShapePlan {
rphf_mask: plan.ot_map.one_mask(feature::REPH_FORMS),
arabic_plan,
}
}
}
fn collect_features(planner: &mut ShapePlanner) {
planner.ot_map.add_gsub_pause(Some(setup_syllables));
planner.ot_map.enable_feature(feature::LOCALIZED_FORMS, FeatureFlags::empty(), 1);
planner.ot_map.enable_feature(feature::GLYPH_COMPOSITION_DECOMPOSITION, FeatureFlags::empty(), 1);
planner.ot_map.enable_feature(feature::NUKTA_FORMS, FeatureFlags::empty(), 1);
planner.ot_map.enable_feature(feature::AKHANDS, FeatureFlags::MANUAL_ZWJ, 1);
planner.ot_map.add_gsub_pause(Some(crate::ot::clear_substitution_flags));
planner.ot_map.add_feature(feature::REPH_FORMS, FeatureFlags::MANUAL_ZWJ, 1);
planner.ot_map.add_gsub_pause(Some(record_rphf));
planner.ot_map.add_gsub_pause(Some(crate::ot::clear_substitution_flags));
planner.ot_map.enable_feature(feature::PRE_BASE_FORMS, FeatureFlags::MANUAL_ZWJ, 1);
planner.ot_map.add_gsub_pause(Some(record_pref));
for feature in BASIC_FEATURES {
planner.ot_map.enable_feature(*feature, FeatureFlags::MANUAL_ZWJ, 1);
}
planner.ot_map.add_gsub_pause(Some(reorder));
planner.ot_map.add_gsub_pause(Some(crate::ot::clear_syllables));
for feature in TOPOGRAPHICAL_FEATURES {
planner.ot_map.add_feature(*feature, FeatureFlags::empty(), 1);
}
planner.ot_map.add_gsub_pause(None);
for feature in OTHER_FEATURES {
planner.ot_map.enable_feature(*feature, FeatureFlags::empty(), 1);
}
}
fn setup_syllables(plan: &ShapePlan, _: &Face, buffer: &mut Buffer) {
super::universal_machine::find_syllables(buffer);
foreach_syllable!(buffer, start, end, {
buffer.unsafe_to_break(start, end);
});
setup_rphf_mask(plan, buffer);
setup_topographical_masks(plan, buffer);
}
fn setup_rphf_mask(plan: &ShapePlan, buffer: &mut Buffer) {
let universal_plan = plan.data::<UniversalShapePlan>();
let mask = universal_plan.rphf_mask;
if mask == 0 {
return;
}
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
let limit = if buffer.info[start].use_category() == category::R {
1
} else {
std::cmp::min(3, end - start)
};
for i in start..start+limit {
buffer.info[i].mask |= mask;
}
start = end;
end = buffer.next_syllable(start);
}
}
fn setup_topographical_masks(plan: &ShapePlan, buffer: &mut Buffer) {
use super::universal_machine::SyllableType;
let mut masks = [0; 4];
let mut all_masks = 0;
for i in 0..4 {
masks[i] = plan.ot_map.one_mask(TOPOGRAPHICAL_FEATURES[i]);
if masks[i] == plan.ot_map.global_mask() {
masks[i] = 0;
}
all_masks |= masks[i];
}
if all_masks == 0 {
return;
}
let other_masks = !all_masks;
let mut last_start = 0;
let mut last_form = None;
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
let syllable = buffer.info[start].syllable() & 0x0F;
if syllable == SyllableType::IndependentCluster as u8 ||
syllable == SyllableType::SymbolCluster as u8 ||
syllable == SyllableType::NonCluster as u8
{
last_form = None;
} else {
let join = last_form == Some(JoiningForm::Terminal) || last_form == Some(JoiningForm::Isolated);
if join {
let form = if last_form == Some(JoiningForm::Terminal) {
JoiningForm::Medial
} else {
JoiningForm::Initial
};
for i in last_start..start {
buffer.info[i].mask = (buffer.info[i].mask & other_masks) | masks[form as usize];
}
}
let form = if join { JoiningForm::Terminal } else { JoiningForm::Isolated };
last_form = Some(form);
for i in start..end {
buffer.info[i].mask = (buffer.info[i].mask & other_masks) | masks[form as usize];
}
}
last_start = start;
start = end;
end = buffer.next_syllable(start);
}
}
fn record_rphf(plan: &ShapePlan, _: &Face, buffer: &mut Buffer) {
let universal_plan = plan.data::<UniversalShapePlan>();
let mask = universal_plan.rphf_mask;
if mask == 0 {
return;
}
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
for i in start..end {
if buffer.info[i].mask & mask == 0 {
break;
}
if buffer.info[i].is_substituted() {
buffer.info[i].set_use_category(category::R);
break;
}
}
start = end;
end = buffer.next_syllable(start);
}
}
fn reorder(_: &ShapePlan, face: &Face, buffer: &mut Buffer) {
insert_dotted_circles(face, buffer);
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
reorder_syllable(start, end, buffer);
start = end;
end = buffer.next_syllable(start);
}
}
fn insert_dotted_circles(face: &Face, buffer: &mut Buffer) {
use super::universal_machine::SyllableType;
if buffer.flags.contains(BufferFlags::DO_NOT_INSERT_DOTTED_CIRCLE) {
return;
}
let has_broken_syllables = buffer.info_slice().iter()
.any(|info| info.syllable() & 0x0F == SyllableType::BrokenCluster as u8);
if !has_broken_syllables {
return;
}
let dottedcircle_glyph = match face.glyph_index(0x25CC) {
Some(g) => g.0 as u32,
None => return,
};
let mut dottedcircle = GlyphInfo {
codepoint: dottedcircle_glyph,
..GlyphInfo::default()
};
dottedcircle.set_use_category(super::universal_table::get_category(0x25CC));
buffer.clear_output();
buffer.idx = 0;
let mut last_syllable = 0;
while buffer.idx < buffer.len {
let syllable = buffer.cur(0).syllable();
let syllable_type = syllable & 0x0F;
if last_syllable != syllable && syllable_type == SyllableType::BrokenCluster as u8 {
last_syllable = syllable;
let mut ginfo = dottedcircle;
ginfo.cluster = buffer.cur(0).cluster;
ginfo.mask = buffer.cur(0).mask;
ginfo.set_syllable(buffer.cur(0).syllable());
while buffer.idx < buffer.len &&
last_syllable == buffer.cur(0).syllable() &&
buffer.cur(0).use_category() == category::R
{
buffer.next_glyph();
}
buffer.output_info(ginfo);
} else {
buffer.next_glyph();
}
}
buffer.swap_buffers();
}
const fn category_flag(c: Category) -> u32 {
rb_flag(c as u32)
}
const fn category_flag64(c: Category) -> u64 {
rb_flag64(c as u32)
}
const BASE_FLAGS: u64 =
category_flag64(category::FM) |
category_flag64(category::FABV) |
category_flag64(category::FBLW) |
category_flag64(category::FPST) |
category_flag64(category::MABV) |
category_flag64(category::MBLW) |
category_flag64(category::MPST) |
category_flag64(category::MPRE) |
category_flag64(category::VABV) |
category_flag64(category::VBLW) |
category_flag64(category::VPST) |
category_flag64(category::VPRE) |
category_flag64(category::VMABV) |
category_flag64(category::VMBLW) |
category_flag64(category::VMPST) |
category_flag64(category::VMPRE);
fn reorder_syllable(start: usize, end: usize, buffer: &mut Buffer) {
use super::universal_machine::SyllableType;
let syllable_type = (buffer.info[start].syllable() & 0x0F) as u32;
if (rb_flag_unsafe(syllable_type) &
(rb_flag(SyllableType::ViramaTerminatedCluster as u32) |
rb_flag(SyllableType::SakotTerminatedCluster as u32) |
rb_flag(SyllableType::StandardCluster as u32) |
rb_flag(SyllableType::BrokenCluster as u32) |
0)) == 0
{
return;
}
if buffer.info[start].use_category() == category::R && end - start > 1 {
for i in start+1..end {
let is_post_base_glyph =
(rb_flag64_unsafe(buffer.info[i].use_category() as u32) & BASE_FLAGS) != 0 ||
buffer.info[i].is_halant_use();
if is_post_base_glyph || i == end - 1 {
let mut i = i;
if is_post_base_glyph {
i -= 1;
}
buffer.merge_clusters(start, i + 1);
let t = buffer.info[start];
for k in 0..i-start {
buffer.info[k + start] = buffer.info[k + start + 1];
}
buffer.info[i] = t;
break;
}
}
}
let mut j = start;
for i in start..end {
let flag = rb_flag_unsafe(buffer.info[i].use_category() as u32);
if buffer.info[i].is_halant_use() {
j = i + 1;
} else if (flag & (category_flag(category::VPRE) | category_flag(category::VMPRE))) != 0 &&
buffer.info[i].lig_comp() == 0 && j < i
{
buffer.merge_clusters(j, i + 1);
let t = buffer.info[i];
for k in (0..i-j).rev() {
buffer.info[k + j + 1] = buffer.info[k + j];
}
buffer.info[j] = t;
}
}
}
fn record_pref(_: &ShapePlan, _: &Face, buffer: &mut Buffer) {
let mut start = 0;
let mut end = buffer.next_syllable(0);
while start < buffer.len {
for i in start..end {
if buffer.info[i].is_substituted() {
buffer.info[i].set_use_category(category::VPRE);
break;
}
}
start = end;
end = buffer.next_syllable(start);
}
}
fn has_arabic_joining(script: Script) -> bool {
match script {
script::ARABIC |
script::MONGOLIAN |
script::SYRIAC |
script::NKO |
script::PHAGS_PA |
script::MANDAIC |
script::MANICHAEAN |
script::PSALTER_PAHLAVI |
script::ADLAM => true,
_ => false,
}
}
fn preprocess_text(_: &ShapePlan, _: &Face, buffer: &mut Buffer) {
super::vowel_constraints::preprocess_text_vowel_constraints(buffer);
}
fn compose(_: &ShapeNormalizeContext, a: char, b: char) -> Option<char> {
if a.general_category().is_mark() {
return None;
}
crate::unicode::compose(a, b)
}
fn setup_masks(plan: &ShapePlan, _: &Face, buffer: &mut Buffer) {
let universal_plan = plan.data::<UniversalShapePlan>();
if let Some(ref arabic_plan) = universal_plan.arabic_plan {
super::arabic::setup_masks_inner(arabic_plan, plan.script, buffer);
}
for info in buffer.info_slice_mut() {
info.set_use_category(super::universal_table::get_category(info.codepoint));
}
}