use super::aat_layout::*;
use super::aat_map::{hb_aat_map_builder_t, hb_aat_map_t, range_flags_t};
use super::buffer::{hb_buffer_t, UnicodeProps};
use super::{hb_font_t, hb_glyph_info_t};
use crate::hb::aat_layout_common::hb_aat_apply_context_t;
use crate::hb::ot_layout::MAX_CONTEXT_LENGTH;
use alloc::vec;
use ttf_parser::{apple_layout, morx, FromData, GlyphId, LazyArray32};
pub fn compile_flags(
face: &hb_font_t,
builder: &hb_aat_map_builder_t,
map: &mut hb_aat_map_t,
) -> Option<()> {
let has_feature = |kind: u16, setting: u16| {
builder
.current_features
.binary_search_by(|probe| {
if probe.kind != kind {
probe.kind.cmp(&kind)
} else {
probe.setting.cmp(&setting)
}
})
.is_ok()
};
let chains = face.tables().morx.as_ref()?.chains;
let chain_len = chains.into_iter().count();
map.chain_flags.resize(chain_len, vec![]);
for (chain, chain_flags) in chains.into_iter().zip(map.chain_flags.iter_mut()) {
let mut flags = chain.default_flags;
for feature in chain.features {
if has_feature(feature.kind, feature.setting) {
flags &= feature.disable_flags;
flags |= feature.enable_flags;
} else if feature.kind == HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE as u16
&& feature.setting == u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS)
{
let ok = has_feature(
HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE as u16,
u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS),
);
if ok {
flags &= feature.disable_flags;
flags |= feature.enable_flags;
}
}
}
chain_flags.push(range_flags_t {
flags,
cluster_first: builder.range_first as u32,
cluster_last: builder.range_last as u32,
});
}
Some(())
}
pub fn apply<'a>(c: &mut hb_aat_apply_context_t<'a>, map: &'a mut hb_aat_map_t) -> Option<()> {
c.buffer.unsafe_to_concat(None, None);
let chains = c.face.tables().morx.as_ref()?.chains;
let chain_len = chains.into_iter().count();
map.chain_flags.resize(chain_len, vec![]);
for (chain, chain_flags) in chains.into_iter().zip(map.chain_flags.iter_mut()) {
c.range_flags = Some(chain_flags.as_mut_slice());
for subtable in chain.subtables {
if let Some(range_flags) = c.range_flags.as_ref() {
if range_flags.len() == 1 && (subtable.feature_flags & range_flags[0].flags == 0) {
continue;
}
}
c.subtable_flags = subtable.feature_flags;
if !subtable.coverage.is_all_directions()
&& c.buffer.direction.is_vertical() != subtable.coverage.is_vertical()
{
continue;
}
let reverse = if subtable.coverage.is_logical() {
subtable.coverage.is_backwards()
} else {
subtable.coverage.is_backwards() != c.buffer.direction.is_backward()
};
if reverse {
c.buffer.reverse();
}
apply_subtable(&subtable.kind, c);
if reverse {
c.buffer.reverse();
}
}
}
Some(())
}
trait driver_context_t<T: FromData> {
fn in_place(&self) -> bool;
fn can_advance(&self, entry: &apple_layout::GenericStateEntry<T>) -> bool;
fn is_actionable(
&self,
entry: &apple_layout::GenericStateEntry<T>,
buffer: &hb_buffer_t,
) -> bool;
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<T>,
buffer: &mut hb_buffer_t,
) -> Option<()>;
}
const START_OF_TEXT: u16 = 0;
fn drive<T: FromData>(
machine: &apple_layout::ExtendedStateTable<T>,
c: &mut dyn driver_context_t<T>,
ac: &mut hb_aat_apply_context_t,
) {
if !c.in_place() {
ac.buffer.clear_output();
}
let mut state = START_OF_TEXT;
let mut last_range = ac.range_flags.as_ref().and_then(|rf| {
if rf.len() > 1 {
rf.first().map(|_| 0usize)
} else {
None
}
});
ac.buffer.idx = 0;
loop {
if let Some(range_flags) = ac.range_flags.as_ref() {
if let Some(last_range) = last_range.as_mut() {
let mut range = *last_range;
if ac.buffer.idx < ac.buffer.len {
let cluster = ac.buffer.cur(0).cluster;
while cluster < range_flags[range].cluster_first {
range -= 1;
}
while cluster > range_flags[range].cluster_last {
range += 1;
}
*last_range = range;
}
if range_flags[range].flags & ac.subtable_flags == 0 {
if ac.buffer.idx == ac.buffer.len || !ac.buffer.successful {
break;
}
state = START_OF_TEXT;
ac.buffer.next_glyph();
continue;
}
}
}
let class = if ac.buffer.idx < ac.buffer.len {
machine.class(ac.buffer.cur(0).as_glyph()).unwrap_or(1)
} else {
u16::from(apple_layout::class::END_OF_TEXT)
};
let entry: apple_layout::GenericStateEntry<T> = match machine.entry(state, class) {
Some(v) => v,
None => break,
};
let next_state = entry.new_state;
let is_safe_to_break_extra = || {
let wouldbe_entry = match machine.entry(START_OF_TEXT, class) {
Some(v) => v,
None => return false,
};
if c.is_actionable(&wouldbe_entry, ac.buffer) {
return false;
}
next_state == wouldbe_entry.new_state
&& c.can_advance(&entry) == c.can_advance(&wouldbe_entry)
};
let is_safe_to_break = || {
if c.is_actionable(&entry, ac.buffer) {
return false;
}
let ok = state == START_OF_TEXT
|| (!c.can_advance(&entry) && next_state == START_OF_TEXT)
|| is_safe_to_break_extra();
if !ok {
return false;
}
let end_entry = match machine.entry(state, u16::from(apple_layout::class::END_OF_TEXT))
{
Some(v) => v,
None => return false,
};
!c.is_actionable(&end_entry, ac.buffer)
};
if !is_safe_to_break() && ac.buffer.backtrack_len() > 0 && ac.buffer.idx < ac.buffer.len {
ac.buffer.unsafe_to_break_from_outbuffer(
Some(ac.buffer.backtrack_len() - 1),
Some(ac.buffer.idx + 1),
);
}
c.transition(&entry, ac.buffer);
state = next_state;
if ac.buffer.idx >= ac.buffer.len || !ac.buffer.successful {
break;
}
if c.can_advance(&entry) {
ac.buffer.next_glyph();
} else {
if ac.buffer.max_ops <= 0 {
ac.buffer.next_glyph();
}
ac.buffer.max_ops -= 1;
}
}
if !c.in_place() {
ac.buffer.sync();
}
}
fn apply_subtable(kind: &morx::SubtableKind, ac: &mut hb_aat_apply_context_t) {
match kind {
morx::SubtableKind::Rearrangement(ref table) => {
let mut c = RearrangementCtx { start: 0, end: 0 };
drive::<()>(table, &mut c, ac);
}
morx::SubtableKind::Contextual(ref table) => {
let mut c = ContextualCtx {
mark_set: false,
face_if_has_glyph_classes:
matches!(ac.face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
.then_some(ac.face),
mark: 0,
table,
};
drive::<morx::ContextualEntryData>(&table.state, &mut c, ac);
}
morx::SubtableKind::Ligature(ref table) => {
let mut c = LigatureCtx {
table,
match_length: 0,
match_positions: [0; LIGATURE_MAX_MATCHES],
};
drive::<u16>(&table.state, &mut c, ac);
}
morx::SubtableKind::NonContextual(ref lookup) => {
let face_if_has_glyph_classes =
matches!(ac.face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
.then_some(ac.face);
let mut last_range = ac.range_flags.as_ref().and_then(|rf| {
if rf.len() > 1 {
rf.first().map(|_| 0usize)
} else {
None
}
});
for info in 0..ac.buffer.len {
if let Some(range_flags) = ac.range_flags.as_ref() {
if let Some(last_range) = last_range.as_mut() {
let mut range = *last_range;
if ac.buffer.idx < ac.buffer.len {
let cluster = ac.buffer.cur(0).cluster;
while cluster < range_flags[range].cluster_first {
range -= 1;
}
while cluster > range_flags[range].cluster_last {
range += 1;
}
*last_range = range;
}
if range_flags[range].flags & ac.subtable_flags == 0 {
continue;
}
}
}
let info = &mut ac.buffer.info[info];
if let Some(replacement) = lookup.value(info.as_glyph()) {
info.glyph_id = u32::from(replacement);
if let Some(face) = face_if_has_glyph_classes {
info.set_glyph_props(face.glyph_props(GlyphId(replacement)));
}
}
}
}
morx::SubtableKind::Insertion(ref table) => {
let mut c = InsertionCtx {
mark: 0,
glyphs: table.glyphs,
};
drive::<morx::InsertionEntryData>(&table.state, &mut c, ac);
}
}
}
struct RearrangementCtx {
start: usize,
end: usize,
}
impl RearrangementCtx {
const MARK_FIRST: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
const MARK_LAST: u16 = 0x2000;
const VERB: u16 = 0x000F;
}
impl driver_context_t<()> for RearrangementCtx {
fn in_place(&self) -> bool {
true
}
fn can_advance(&self, entry: &apple_layout::GenericStateEntry<()>) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<()>, _: &hb_buffer_t) -> bool {
entry.flags & Self::VERB != 0 && self.start < self.end
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<()>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
let flags = entry.flags;
if flags & Self::MARK_FIRST != 0 {
self.start = buffer.idx;
}
if flags & Self::MARK_LAST != 0 {
self.end = (buffer.idx + 1).min(buffer.len);
}
if flags & Self::VERB != 0 && self.start < self.end {
const MAP: [u8; 16] = [
0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x02, 0x03, 0x12, 0x13, 0x21, 0x31, 0x22, 0x32, 0x23, 0x33, ];
let m = MAP[usize::from(flags & Self::VERB)];
let l = 2.min(m >> 4) as usize;
let r = 2.min(m & 0x0F) as usize;
let reverse_l = 3 == (m >> 4);
let reverse_r = 3 == (m & 0x0F);
if (self.end - self.start >= l + r) && (self.end - self.start <= MAX_CONTEXT_LENGTH) {
buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len));
buffer.merge_clusters(self.start, self.end);
let mut buf = [hb_glyph_info_t::default(); 4];
for (i, glyph_info) in buf[..l].iter_mut().enumerate() {
*glyph_info = buffer.info[self.start + i];
}
for i in 0..r {
buf[i + 2] = buffer.info[self.end - r + i];
}
if l > r {
for i in 0..(self.end - self.start - l - r) {
buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
}
} else if l < r {
for i in (0..(self.end - self.start - l - r)).rev() {
buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
}
}
for i in 0..r {
buffer.info[self.start + i] = buf[2 + i];
}
for i in 0..l {
buffer.info[self.end - l + i] = buf[i];
}
if reverse_l {
buffer.info.swap(self.end - 1, self.end - 2);
}
if reverse_r {
buffer.info.swap(self.start, self.start + 1);
}
}
}
Some(())
}
}
struct ContextualCtx<'a> {
mark_set: bool,
face_if_has_glyph_classes: Option<&'a hb_font_t<'a>>,
mark: usize,
table: &'a morx::ContextualSubtable<'a>,
}
impl ContextualCtx<'_> {
const SET_MARK: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
}
impl driver_context_t<morx::ContextualEntryData> for ContextualCtx<'_> {
fn in_place(&self) -> bool {
true
}
fn can_advance(
&self,
entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(
&self,
entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
buffer: &hb_buffer_t,
) -> bool {
if buffer.idx == buffer.len && !self.mark_set {
return false;
}
entry.extra.mark_index != 0xFFFF || entry.extra.current_index != 0xFFFF
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
if buffer.idx == buffer.len && !self.mark_set {
return Some(());
}
let mut replacement = None;
if entry.extra.mark_index != 0xFFFF {
let lookup = self.table.lookup(u32::from(entry.extra.mark_index))?;
replacement = lookup.value(buffer.info[self.mark].as_glyph());
}
if let Some(replacement) = replacement {
buffer.unsafe_to_break(Some(self.mark), Some((buffer.idx + 1).min(buffer.len)));
buffer.info[self.mark].glyph_id = u32::from(replacement);
if let Some(face) = self.face_if_has_glyph_classes {
buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
}
}
replacement = None;
let idx = buffer.idx.min(buffer.len - 1);
if entry.extra.current_index != 0xFFFF {
let lookup = self.table.lookup(u32::from(entry.extra.current_index))?;
replacement = lookup.value(buffer.info[idx].as_glyph());
}
if let Some(replacement) = replacement {
buffer.info[idx].glyph_id = u32::from(replacement);
if let Some(face) = self.face_if_has_glyph_classes {
buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
}
}
if entry.flags & Self::SET_MARK != 0 {
self.mark_set = true;
self.mark = buffer.idx;
}
Some(())
}
}
struct InsertionCtx<'a> {
mark: u32,
glyphs: LazyArray32<'a, GlyphId>,
}
impl InsertionCtx<'_> {
const SET_MARK: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
const CURRENT_INSERT_BEFORE: u16 = 0x0800;
const MARKED_INSERT_BEFORE: u16 = 0x0400;
const CURRENT_INSERT_COUNT: u16 = 0x03E0;
const MARKED_INSERT_COUNT: u16 = 0x001F;
}
impl driver_context_t<morx::InsertionEntryData> for InsertionCtx<'_> {
fn in_place(&self) -> bool {
false
}
fn can_advance(
&self,
entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(
&self,
entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
_: &hb_buffer_t,
) -> bool {
(entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0)
&& (entry.extra.current_insert_index != 0xFFFF
|| entry.extra.marked_insert_index != 0xFFFF)
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
let flags = entry.flags;
let mark_loc = buffer.out_len;
if entry.extra.marked_insert_index != 0xFFFF {
let count = flags & Self::MARKED_INSERT_COUNT;
buffer.max_ops -= i32::from(count);
if buffer.max_ops <= 0 {
return Some(());
}
let start = entry.extra.marked_insert_index;
let before = flags & Self::MARKED_INSERT_BEFORE != 0;
let end = buffer.out_len;
buffer.move_to(self.mark as usize);
if buffer.idx < buffer.len && !before {
buffer.copy_glyph();
}
for i in 0..count {
let i = u32::from(start + i);
buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
}
if buffer.idx < buffer.len && !before {
buffer.skip_glyph();
}
buffer.move_to(end + usize::from(count));
buffer.unsafe_to_break_from_outbuffer(
Some(self.mark as usize),
Some((buffer.idx + 1).min(buffer.len)),
);
}
if flags & Self::SET_MARK != 0 {
self.mark = mark_loc as u32;
}
if entry.extra.current_insert_index != 0xFFFF {
let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5;
buffer.max_ops -= i32::from(count);
if buffer.max_ops < 0 {
return Some(());
}
let start = entry.extra.current_insert_index;
let before = flags & Self::CURRENT_INSERT_BEFORE != 0;
let end = buffer.out_len;
if buffer.idx < buffer.len && !before {
buffer.copy_glyph();
}
for i in 0..count {
let i = u32::from(start + i);
buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
}
if buffer.idx < buffer.len && !before {
buffer.skip_glyph();
}
buffer.move_to(if flags & Self::DONT_ADVANCE != 0 {
end
} else {
end + usize::from(count)
});
}
Some(())
}
}
const LIGATURE_MAX_MATCHES: usize = 64;
struct LigatureCtx<'a> {
table: &'a morx::LigatureSubtable<'a>,
match_length: usize,
match_positions: [usize; LIGATURE_MAX_MATCHES],
}
impl LigatureCtx<'_> {
const SET_COMPONENT: u16 = 0x8000;
const DONT_ADVANCE: u16 = 0x4000;
const PERFORM_ACTION: u16 = 0x2000;
const LIG_ACTION_LAST: u32 = 0x80000000;
const LIG_ACTION_STORE: u32 = 0x40000000;
const LIG_ACTION_OFFSET: u32 = 0x3FFFFFFF;
}
impl driver_context_t<u16> for LigatureCtx<'_> {
fn in_place(&self) -> bool {
false
}
fn can_advance(&self, entry: &apple_layout::GenericStateEntry<u16>) -> bool {
entry.flags & Self::DONT_ADVANCE == 0
}
fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<u16>, _: &hb_buffer_t) -> bool {
entry.flags & Self::PERFORM_ACTION != 0
}
fn transition(
&mut self,
entry: &apple_layout::GenericStateEntry<u16>,
buffer: &mut hb_buffer_t,
) -> Option<()> {
if entry.flags & Self::SET_COMPONENT != 0 {
if self.match_length != 0
&& self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES]
== buffer.out_len
{
self.match_length -= 1;
}
self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = buffer.out_len;
self.match_length += 1;
}
if entry.flags & Self::PERFORM_ACTION != 0 {
let end = buffer.out_len;
if self.match_length == 0 {
return Some(());
}
if buffer.idx >= buffer.len {
return Some(()); }
let mut cursor = self.match_length;
let mut ligature_actions_index = entry.extra;
let mut ligature_idx = 0;
loop {
if cursor == 0 {
self.match_length = 0;
break;
}
cursor -= 1;
buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]);
let action = match self
.table
.ligature_actions
.get(u32::from(ligature_actions_index))
{
Some(v) => v,
None => break,
};
let mut uoffset = action & Self::LIG_ACTION_OFFSET;
if uoffset & 0x20000000 != 0 {
uoffset |= 0xC0000000; }
let offset = uoffset as i32;
let component_idx = (buffer.cur(0).glyph_id as i32 + offset) as u32;
ligature_idx += match self.table.components.get(component_idx) {
Some(v) => v,
None => break,
};
if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 {
let lig = match self.table.ligatures.get(u32::from(ligature_idx)) {
Some(v) => v,
None => break,
};
buffer.replace_glyph(u32::from(lig.0));
let lig_end =
self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1;
while self.match_length - 1 > cursor {
self.match_length -= 1;
buffer.move_to(
self.match_positions[self.match_length % LIGATURE_MAX_MATCHES],
);
let cur_unicode = buffer.cur(0).unicode_props();
buffer
.cur_mut(0)
.set_unicode_props(cur_unicode | UnicodeProps::IGNORABLE.bits());
buffer.replace_glyph(0xFFFF);
}
buffer.move_to(lig_end);
buffer.merge_out_clusters(
self.match_positions[cursor % LIGATURE_MAX_MATCHES],
buffer.out_len,
);
}
ligature_actions_index += 1;
if action & Self::LIG_ACTION_LAST != 0 {
break;
}
}
buffer.move_to(end);
}
Some(())
}
}