use crate::Face;
use crate::buffer::{Buffer, BufferScratchFlags, GlyphInfo};
use crate::complex::MAX_COMBINING_MARKS;
use crate::plan::ShapePlan;
use crate::unicode::{CharExt, GeneralCategory};
pub struct ShapeNormalizeContext<'a> {
pub plan: &'a ShapePlan,
pub buffer: &'a mut Buffer,
pub face: &'a Face<'a>,
pub decompose: fn(&ShapeNormalizeContext, char) -> Option<(char, char)>,
pub compose: fn(&ShapeNormalizeContext, char, char) -> Option<char>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ShapeNormalizationMode {
#[allow(dead_code)]
Decomposed,
ComposedDiacritics,
ComposedDiacriticsNoShortCircuit,
Auto,
}
impl Default for ShapeNormalizationMode {
fn default() -> Self {
Self::Auto
}
}
pub fn normalize(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) {
if buffer.is_empty() {
return;
}
let mut mode = plan.shaper.normalization_mode;
if mode == Some(ShapeNormalizationMode::Auto) {
mode = Some(ShapeNormalizationMode::ComposedDiacritics);
}
let decompose = plan.shaper.decompose.unwrap_or(|_, ab| crate::unicode::decompose(ab));
let compose = plan.shaper.compose.unwrap_or(|_, a, b| crate::unicode::compose(a, b));
let mut ctx = ShapeNormalizeContext { plan, buffer, face, decompose, compose };
let mut buffer = &mut ctx.buffer;
let always_short_circuit = mode.is_none();
let might_short_circuit = always_short_circuit || !matches!(
mode,
Some(ShapeNormalizationMode::Decomposed) |
Some(ShapeNormalizationMode::ComposedDiacriticsNoShortCircuit)
);
let mut all_simple = true;
{
let count = buffer.len;
buffer.idx = 0;
buffer.clear_output();
loop {
let mut end = buffer.idx + 1;
while end < count && !buffer.info[end].is_unicode_mark() {
end += 1;
}
if end < count {
end -= 1;
}
if might_short_circuit {
let len = end - buffer.idx;
let mut done = 0;
while done < len {
let cur = buffer.cur_mut(done);
cur.set_glyph_index(match face.glyph_index(cur.glyph_id) {
Some(glyph_id) => u32::from(glyph_id.0),
None => break,
});
done += 1;
}
buffer.next_glyphs(done);
}
while buffer.idx < end && buffer.successful {
decompose_current_character(&mut ctx, might_short_circuit);
buffer = &mut ctx.buffer;
}
if buffer.idx == count || !buffer.successful {
break;
}
all_simple = false;
end = buffer.idx + 1;
while end < count && buffer.info[end].is_unicode_mark() {
end += 1;
}
decompose_multi_char_cluster(&mut ctx, end, always_short_circuit);
buffer = &mut ctx.buffer;
if buffer.idx >= count || !buffer.successful {
break;
}
}
buffer.swap_buffers();
}
if !all_simple {
let count = buffer.len;
let mut i = 0;
while i < count {
if buffer.info[i].modified_combining_class() == 0 {
i += 1;
continue;
}
let mut end = i + 1;
while end < count && buffer.info[end].modified_combining_class() != 0 {
end += 1;
}
if end - i <= MAX_COMBINING_MARKS {
buffer.sort(i, end, |a, b| a.modified_combining_class() > b.modified_combining_class());
if let Some(reorder_marks) = ctx.plan.shaper.reorder_marks {
reorder_marks(ctx.plan, buffer, i, end);
}
}
i = end + 1;
}
}
if buffer.scratch_flags.contains(BufferScratchFlags::HAS_CGJ) {
for i in 1..buffer.len.saturating_sub(1) {
if buffer.info[i].glyph_id == 0x034F {
let last = buffer.info[i - 1].modified_combining_class();
let next = buffer.info[i + 1].modified_combining_class();
if next == 0 || last <= next {
buffer.info[i].unhide();
}
}
}
}
if !all_simple && matches!(
mode,
Some(ShapeNormalizationMode::ComposedDiacritics) |
Some(ShapeNormalizationMode::ComposedDiacriticsNoShortCircuit)
) {
let count = buffer.len;
let mut starter = 0;
buffer.clear_output();
buffer.next_glyph();
while buffer.idx < count && buffer.successful {
let cur = buffer.cur(0);
if cur.is_unicode_mark() &&
(starter == buffer.out_len - 1
|| buffer.prev().modified_combining_class() < cur.modified_combining_class())
{
let a = buffer.out_info()[starter].as_char();
let b = cur.as_char();
if let Some(composed) = (ctx.compose)(&ctx, a, b) {
if let Some(glyph_id) = face.glyph_index(u32::from(composed)) {
buffer = &mut ctx.buffer;
buffer.next_glyph();
if !buffer.successful {
return;
}
buffer.merge_out_clusters(starter, buffer.out_len);
buffer.out_len -= 1;
let mut flags = buffer.scratch_flags;
let info = &mut buffer.out_info_mut()[starter];
info.glyph_id = u32::from(composed);
info.set_glyph_index(u32::from(glyph_id.0));
info.init_unicode_props(&mut flags);
buffer.scratch_flags = flags;
continue;
}
}
}
buffer = &mut ctx.buffer;
buffer.next_glyph();
if buffer.prev().modified_combining_class() == 0 {
starter = buffer.out_len - 1;
}
}
buffer.swap_buffers();
}
}
fn decompose_multi_char_cluster(ctx: &mut ShapeNormalizeContext, end: usize, short_circuit: bool) {
let mut i = ctx.buffer.idx;
while i < end && ctx.buffer.successful {
if ctx.buffer.info[i].as_char().is_variation_selector() {
handle_variation_selector_cluster(ctx, end, short_circuit);
return;
}
i += 1;
}
while ctx.buffer.idx < end && ctx.buffer.successful {
decompose_current_character(ctx, short_circuit);
}
}
fn handle_variation_selector_cluster(ctx: &mut ShapeNormalizeContext, end: usize, _: bool) {
let face = ctx.face;
let set_glyph = |info: &mut GlyphInfo| {
if let Some(glyph_id) = face.glyph_index(info.glyph_id) {
info.set_glyph_index(u32::from(glyph_id.0));
}
};
let buffer = &mut ctx.buffer;
while buffer.idx < end - 1 && buffer.successful {
if buffer.cur(1).as_char().is_variation_selector() {
if let Some(glyph_id) = face.glyph_variation_index(
buffer.cur(0).as_char(),
buffer.cur(1).as_char(),
) {
buffer.cur_mut(0).set_glyph_index(u32::from(glyph_id.0));
let unicode = buffer.cur(0).glyph_id;
buffer.replace_glyphs(2, 1, &[unicode]);
} else {
set_glyph(buffer.cur_mut(0));
buffer.next_glyph();
set_glyph(buffer.cur_mut(0));
buffer.next_glyph();
}
while buffer.idx < end && buffer.cur(0).as_char().is_variation_selector() {
set_glyph(buffer.cur_mut(0));
buffer.next_glyph();
}
} else {
set_glyph(buffer.cur_mut(0));
buffer.next_glyph();
}
}
if ctx.buffer.idx < end {
set_glyph(ctx.buffer.cur_mut(0));
ctx.buffer.next_glyph();
}
}
fn decompose_current_character(ctx: &mut ShapeNormalizeContext, shortest: bool) {
let u = ctx.buffer.cur(0).as_char();
let glyph = ctx.face.glyph_index(u32::from(u));
if !shortest || glyph.is_none() {
if decompose(ctx, shortest, u) > 0 {
ctx.buffer.skip_glyph();
return;
}
}
if let Some(glyph) = glyph {
ctx.buffer.next_char(u32::from(glyph.0));
return;
}
if ctx.buffer.cur(0).general_category() == GeneralCategory::SpaceSeparator {
if let Some(space_type) = u.space_fallback() {
if let Some(space_glyph) = ctx.face.glyph_index(u32::from(' ')) {
ctx.buffer.cur_mut(0).set_space_fallback(space_type);
ctx.buffer.next_char(u32::from(space_glyph.0));
ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_SPACE_FALLBACK;
return;
}
}
}
if u == '\u{2011}' {
if let Some(other_glyph) = ctx.face.glyph_index(0x2010) {
ctx.buffer.next_char(u32::from(other_glyph.0));
return;
}
}
ctx.buffer.next_char(0);
}
fn decompose(ctx: &mut ShapeNormalizeContext, shortest: bool, ab: char) -> u32 {
let (a, b) = match (ctx.decompose)(ctx, ab) {
Some(decomposed) => decomposed,
_ => return 0,
};
let a_glyph = ctx.face.glyph_index(u32::from(a));
let b_glyph = if b != '\0' {
match ctx.face.glyph_index(u32::from(b)) {
Some(glyph_id) => Some(glyph_id),
None => return 0,
}
} else {
None
};
if !shortest || a_glyph.is_none() {
let ret = decompose(ctx, shortest, a);
if ret != 0 {
if let Some(b_glyph) = b_glyph {
ctx.buffer.output_char(u32::from(b), u32::from(b_glyph.0));
return ret + 1;
}
return ret;
}
}
if let Some(a_glyph) = a_glyph {
ctx.buffer.output_char(u32::from(a), u32::from(a_glyph.0));
if let Some(b_glyph) = b_glyph {
ctx.buffer.output_char(u32::from(b), u32::from(b_glyph.0));
return 2;
}
return 1;
}
0
}