use core::ops::{Index, IndexMut};
use super::buffer::*;
use super::common::TagExt;
use super::ot_layout_gsubgpos::{Apply, OT};
use super::ot_shape_plan::hb_ot_shape_plan_t;
use super::unicode::{hb_unicode_funcs_t, hb_unicode_general_category_t, GeneralCategoryExt};
use super::{hb_font_t, hb_glyph_info_t, hb_tag_t};
use crate::hb::set_digest::{hb_set_digest_ext, hb_set_digest_t};
use ttf_parser::opentype_layout::{FeatureIndex, LanguageIndex, LookupIndex, ScriptIndex};
pub const MAX_NESTING_LEVEL: usize = 64;
pub const MAX_CONTEXT_LENGTH: usize = 64;
pub fn hb_ot_layout_has_kerning(face: &hb_font_t) -> bool {
face.tables().kern.is_some()
}
pub fn hb_ot_layout_has_machine_kerning(face: &hb_font_t) -> bool {
match face.tables().kern {
Some(ref kern) => kern.subtables.into_iter().any(|s| s.has_state_machine),
None => false,
}
}
pub fn hb_ot_layout_has_cross_kerning(face: &hb_font_t) -> bool {
match face.tables().kern {
Some(ref kern) => kern.subtables.into_iter().any(|s| s.has_cross_stream),
None => false,
}
}
pub fn _hb_ot_layout_set_glyph_props(face: &hb_font_t, buffer: &mut hb_buffer_t) {
let len = buffer.len;
for info in &mut buffer.info[..len] {
info.set_glyph_props(face.glyph_props(info.as_glyph()));
info.set_lig_props(0);
}
}
pub fn hb_ot_layout_has_glyph_classes(face: &hb_font_t) -> bool {
face.tables()
.gdef
.map_or(false, |table| table.has_glyph_classes())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TableIndex {
GSUB = 0,
GPOS = 1,
}
impl TableIndex {
pub fn iter() -> impl Iterator<Item = TableIndex> {
[Self::GSUB, Self::GPOS].iter().copied()
}
}
impl<T> Index<TableIndex> for [T] {
type Output = T;
fn index(&self, table_index: TableIndex) -> &Self::Output {
&self[table_index as usize]
}
}
impl<T> IndexMut<TableIndex> for [T] {
fn index_mut(&mut self, table_index: TableIndex) -> &mut Self::Output {
&mut self[table_index as usize]
}
}
pub trait LayoutTable {
const INDEX: TableIndex;
const IN_PLACE: bool;
type Lookup: LayoutLookup;
fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup>;
}
pub trait LayoutLookup: Apply {
fn props(&self) -> u32;
fn is_reverse(&self) -> bool;
fn digest(&self) -> &hb_set_digest_t;
}
pub trait LayoutTableExt {
fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, ScriptIndex, hb_tag_t)>;
fn select_script_language(
&self,
script_index: ScriptIndex,
lang_tags: &[hb_tag_t],
) -> Option<LanguageIndex>;
fn get_required_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
) -> Option<(FeatureIndex, hb_tag_t)>;
fn find_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
feature_tag: hb_tag_t,
) -> Option<FeatureIndex>;
}
impl LayoutTableExt for ttf_parser::opentype_layout::LayoutTable<'_> {
fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, ScriptIndex, hb_tag_t)> {
for &tag in script_tags {
if let Some(index) = self.scripts.index(tag) {
return Some((true, index, tag));
}
}
for &tag in &[
hb_tag_t::default_script(),
hb_tag_t::default_language(),
hb_tag_t::from_bytes(b"latn"),
] {
if let Some(index) = self.scripts.index(tag) {
return Some((false, index, tag));
}
}
None
}
fn select_script_language(
&self,
script_index: ScriptIndex,
lang_tags: &[hb_tag_t],
) -> Option<LanguageIndex> {
let script = self.scripts.get(script_index)?;
for &tag in lang_tags {
if let Some(index) = script.languages.index(tag) {
return Some(index);
}
}
if let Some(index) = script.languages.index(hb_tag_t::default_language()) {
return Some(index);
}
None
}
fn get_required_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
) -> Option<(FeatureIndex, hb_tag_t)> {
let script = self.scripts.get(script_index)?;
let sys = match lang_index {
Some(index) => script.languages.get(index)?,
None => script.default_language?,
};
let idx = sys.required_feature?;
let tag = self.features.get(idx)?.tag;
Some((idx, tag))
}
fn find_language_feature(
&self,
script_index: ScriptIndex,
lang_index: Option<LanguageIndex>,
feature_tag: hb_tag_t,
) -> Option<FeatureIndex> {
let script = self.scripts.get(script_index)?;
let sys = match lang_index {
Some(index) => script.languages.get(index)?,
None => script.default_language?,
};
for i in 0..sys.feature_indices.len() {
if let Some(index) = sys.feature_indices.get(i) {
if self.features.get(index).map(|v| v.tag) == Some(feature_tag) {
return Some(index);
}
}
}
None
}
}
pub fn hb_ot_layout_substitute_start(face: &hb_font_t, buffer: &mut hb_buffer_t) {
_hb_ot_layout_set_glyph_props(face, buffer)
}
pub fn apply_layout_table<T: LayoutTable>(
plan: &hb_ot_shape_plan_t,
face: &hb_font_t,
buffer: &mut hb_buffer_t,
table: Option<&T>,
) {
let mut ctx = OT::hb_ot_apply_context_t::new(T::INDEX, face, buffer);
for (stage_index, stage) in plan.ot_map.stages(T::INDEX).iter().enumerate() {
if let Some(table) = table {
for lookup_map in plan.ot_map.stage_lookups(T::INDEX, stage_index) {
let Some(lookup) = table.get_lookup(lookup_map.index) else {
continue;
};
if lookup.digest().may_have(&ctx.digest) {
ctx.lookup_index = lookup_map.index;
ctx.set_lookup_mask(lookup_map.mask);
ctx.auto_zwj = lookup_map.auto_zwj;
ctx.auto_zwnj = lookup_map.auto_zwnj;
ctx.random = lookup_map.random;
ctx.per_syllable = lookup_map.per_syllable;
apply_string::<T>(&mut ctx, lookup);
}
}
}
if let Some(func) = stage.pause_func {
if func(plan, face, ctx.buffer) {
ctx.digest = ctx.buffer.digest();
}
}
}
}
fn apply_string<T: LayoutTable>(ctx: &mut OT::hb_ot_apply_context_t, lookup: &T::Lookup) {
if ctx.buffer.is_empty() || ctx.lookup_mask() == 0 {
return;
}
ctx.lookup_props = lookup.props();
if !lookup.is_reverse() {
if !T::IN_PLACE {
ctx.buffer.clear_output();
}
ctx.buffer.idx = 0;
apply_forward(ctx, lookup);
if !T::IN_PLACE {
ctx.buffer.sync();
}
} else {
assert!(!ctx.buffer.have_output);
ctx.buffer.idx = ctx.buffer.len - 1;
apply_backward(ctx, lookup);
}
}
fn apply_forward(ctx: &mut OT::hb_ot_apply_context_t, lookup: &impl Apply) -> bool {
let mut ret = false;
while ctx.buffer.idx < ctx.buffer.len && ctx.buffer.successful {
let cur = ctx.buffer.cur(0);
if (cur.mask & ctx.lookup_mask()) != 0
&& ctx.check_glyph_property(cur, ctx.lookup_props)
&& lookup.apply(ctx).is_some()
{
ret = true;
} else {
ctx.buffer.next_glyph();
}
}
ret
}
fn apply_backward(ctx: &mut OT::hb_ot_apply_context_t, lookup: &impl Apply) -> bool {
let mut ret = false;
loop {
let cur = ctx.buffer.cur(0);
ret |= (cur.mask & ctx.lookup_mask()) != 0
&& ctx.check_glyph_property(cur, ctx.lookup_props)
&& lookup.apply(ctx).is_some();
if ctx.buffer.idx == 0 {
break;
}
ctx.buffer.idx -= 1;
}
ret
}
#[inline]
pub fn _hb_glyph_info_set_general_category(
info: &mut hb_glyph_info_t,
gen_cat: hb_unicode_general_category_t,
) {
let gen_cat = gen_cat.to_rb();
let n =
(gen_cat as u16) | (info.unicode_props() & (0xFF & !UnicodeProps::GENERAL_CATEGORY.bits()));
info.set_unicode_props(n);
}
#[inline]
pub fn _hb_glyph_info_get_general_category(
info: &hb_glyph_info_t,
) -> hb_unicode_general_category_t {
let n = info.unicode_props() & UnicodeProps::GENERAL_CATEGORY.bits();
hb_unicode_general_category_t::from_rb(n as u32)
}
#[inline]
pub fn _hb_glyph_info_is_unicode_mark(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_get_general_category(info).is_mark()
}
#[inline]
pub(crate) fn _hb_glyph_info_set_modified_combining_class(
info: &mut hb_glyph_info_t,
modified_class: u8,
) {
if !_hb_glyph_info_is_unicode_mark(info) {
return;
}
let n = ((modified_class as u16) << 8) | (info.unicode_props() & 0xFF);
info.set_unicode_props(n);
}
#[inline]
pub fn _hb_glyph_info_get_modified_combining_class(info: &hb_glyph_info_t) -> u8 {
if _hb_glyph_info_is_unicode_mark(info) {
(info.unicode_props() >> 8) as u8
} else {
0
}
}
#[inline]
pub(crate) fn _hb_glyph_info_is_unicode_space(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::SpaceSeparator
}
#[inline]
pub(crate) fn _hb_glyph_info_set_unicode_space_fallback_type(
info: &mut hb_glyph_info_t,
s: hb_unicode_funcs_t::space_t,
) {
if !_hb_glyph_info_is_unicode_space(info) {
return;
}
let n = ((s as u16) << 8) | (info.unicode_props() & 0xFF);
info.set_unicode_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_get_unicode_space_fallback_type(
info: &hb_glyph_info_t,
) -> hb_unicode_funcs_t::space_t {
if _hb_glyph_info_is_unicode_space(info) {
(info.unicode_props() >> 8) as u8
} else {
hb_unicode_funcs_t::NOT_SPACE
}
}
#[inline]
pub(crate) fn _hb_glyph_info_is_variation_selector(info: &hb_glyph_info_t) -> bool {
let a = _hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::Format;
let b = (info.unicode_props() & UnicodeProps::CF_VS.bits()) != 0;
a && b
}
#[inline]
pub(crate) fn _hb_glyph_info_set_variation_selector(info: &mut hb_glyph_info_t, customize: bool) {
if customize {
_hb_glyph_info_set_general_category(info, hb_unicode_general_category_t::Format);
info.set_unicode_props(info.unicode_props() | UnicodeProps::CF_VS.bits())
} else {
_hb_glyph_info_set_general_category(info, hb_unicode_general_category_t::NonspacingMark);
}
}
#[inline]
pub(crate) fn _hb_glyph_info_is_default_ignorable(info: &hb_glyph_info_t) -> bool {
let n = info.unicode_props() & UnicodeProps::IGNORABLE.bits();
n != 0 && !_hb_glyph_info_substituted(info)
}
#[inline]
pub(crate) fn _hb_glyph_info_clear_default_ignorable(info: &mut hb_glyph_info_t) {
let mut n = info.unicode_props();
n &= !UnicodeProps::IGNORABLE.bits();
info.set_unicode_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_is_hidden(info: &hb_glyph_info_t) -> bool {
(info.unicode_props() & UnicodeProps::HIDDEN.bits()) != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_set_continuation(info: &mut hb_glyph_info_t) {
let mut n = info.unicode_props();
n |= UnicodeProps::CONTINUATION.bits();
info.set_unicode_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_reset_continuation(info: &mut hb_glyph_info_t) {
let mut n = info.unicode_props();
n &= !UnicodeProps::CONTINUATION.bits();
info.set_unicode_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_is_continuation(info: &hb_glyph_info_t) -> bool {
info.unicode_props() & UnicodeProps::CONTINUATION.bits() != 0
}
pub(crate) fn _hb_grapheme_group_func(_: &hb_glyph_info_t, b: &hb_glyph_info_t) -> bool {
_hb_glyph_info_is_continuation(b)
}
pub fn _hb_ot_layout_reverse_graphemes(buffer: &mut hb_buffer_t) {
buffer.reverse_groups(
_hb_grapheme_group_func,
buffer.cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS,
)
}
#[inline]
pub(crate) fn _hb_glyph_info_is_unicode_format(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_get_general_category(info) == hb_unicode_general_category_t::Format
}
#[inline]
pub(crate) fn _hb_glyph_info_is_zwnj(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_is_unicode_format(info)
&& (info.unicode_props() & UnicodeProps::CF_ZWNJ.bits() != 0)
}
#[inline]
pub(crate) fn _hb_glyph_info_is_zwj(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_is_unicode_format(info)
&& (info.unicode_props() & UnicodeProps::CF_ZWJ.bits() != 0)
}
const IS_LIG_BASE: u8 = 0x10;
#[inline]
pub(crate) fn _hb_glyph_info_set_lig_props_for_ligature(
info: &mut hb_glyph_info_t,
lig_id: u8,
lig_num_comps: u8,
) {
info.set_lig_props((lig_id << 5) | IS_LIG_BASE | (lig_num_comps & 0x0F));
}
#[inline]
pub(crate) fn _hb_glyph_info_set_lig_props_for_mark(
info: &mut hb_glyph_info_t,
lig_id: u8,
lig_comp: u8,
) {
info.set_lig_props((lig_id << 5) | (lig_comp & 0x0F));
}
#[inline]
pub(crate) fn _hb_glyph_info_set_lig_props_for_component(info: &mut hb_glyph_info_t, comp: u8) {
_hb_glyph_info_set_lig_props_for_mark(info, 0, comp);
}
#[inline]
pub(crate) fn _hb_glyph_info_get_lig_id(info: &hb_glyph_info_t) -> u8 {
info.lig_props() >> 5
}
#[inline]
pub(crate) fn _hb_glyph_info_ligated_internal(info: &hb_glyph_info_t) -> bool {
info.lig_props() & IS_LIG_BASE != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_get_lig_comp(info: &hb_glyph_info_t) -> u8 {
if _hb_glyph_info_ligated_internal(info) {
0
} else {
info.lig_props() & 0x0F
}
}
#[inline]
pub(crate) fn _hb_glyph_info_get_lig_num_comps(info: &hb_glyph_info_t) -> u8 {
if info.glyph_props() & GlyphPropsFlags::LIGATURE.bits() != 0
&& _hb_glyph_info_ligated_internal(info)
{
info.lig_props() & 0x0F
} else {
1
}
}
#[inline]
pub(crate) fn _hb_glyph_info_is_base_glyph(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::BASE_GLYPH.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_is_ligature(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::LIGATURE.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_is_mark(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::MARK.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_substituted(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::SUBSTITUTED.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_ligated(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::LIGATED.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_multiplied(info: &hb_glyph_info_t) -> bool {
info.glyph_props() & GlyphPropsFlags::MULTIPLIED.bits() != 0
}
#[inline]
pub(crate) fn _hb_glyph_info_ligated_and_didnt_multiply(info: &hb_glyph_info_t) -> bool {
_hb_glyph_info_ligated(info) && !_hb_glyph_info_multiplied(info)
}
#[inline]
pub(crate) fn _hb_glyph_info_clear_ligated_and_multiplied(info: &mut hb_glyph_info_t) {
let mut n = info.glyph_props();
n &= !(GlyphPropsFlags::LIGATED | GlyphPropsFlags::MULTIPLIED).bits();
info.set_glyph_props(n);
}
#[inline]
pub(crate) fn _hb_glyph_info_clear_substituted(info: &mut hb_glyph_info_t) {
let mut n = info.glyph_props();
n &= !GlyphPropsFlags::SUBSTITUTED.bits();
info.set_glyph_props(n);
}
pub fn _hb_clear_substitution_flags(
_: &hb_ot_shape_plan_t,
_: &hb_font_t,
buffer: &mut hb_buffer_t,
) -> bool {
let len = buffer.len;
for info in &mut buffer.info[..len] {
_hb_glyph_info_clear_substituted(info);
}
false
}