use crate::context::{ContextLookupHelper, Glyph, MatchType};
use crate::error::ParseError;
use crate::gdef::gdef_is_mark;
use crate::gsub::RawGlyph;
use crate::indic;
use crate::layout::{
chain_context_lookup_info, context_lookup_info, Adjust, Anchor, ChainContextLookup,
ContextLookup, CursivePos, GDEFTable, LangSys, LayoutCache, LayoutTable, LookupList,
MarkBasePos, MarkLigPos, PairPos, PosLookup, SinglePos, ValueRecord, GPOS,
};
use crate::opentype;
use crate::tag;
type PosContext<'a> = ContextLookupHelper<'a, GPOS>;
pub fn gpos_apply_lookup(
gpos_cache: &LayoutCache<GPOS>,
gpos_table: &LayoutTable<GPOS>,
opt_gdef_table: Option<&GDEFTable>,
lookup_index: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
if let Some(ref lookup_list) = gpos_table.opt_lookup_list {
let lookup = lookup_list.lookup_cache_gpos(gpos_cache, lookup_index)?;
let match_type = MatchType::from_lookup_flag(lookup.lookup_flag);
match lookup.lookup_subtables {
PosLookup::SinglePos(ref subtables) => {
forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
singlepos(&subtables, &mut infos[i])
})
}
PosLookup::PairPos(ref subtables) => {
forall_glyph_pairs_match(match_type, opt_gdef_table, infos, |i1, i2, infos| {
pairpos(&subtables, i1, i2, infos)
})
}
PosLookup::CursivePos(ref subtables) => forall_glyph_pairs_match(
MatchType::ignore_marks(),
opt_gdef_table,
infos,
|i1, i2, infos| cursivepos(&subtables, i1, i2, infos),
),
PosLookup::MarkBasePos(ref subtables) => {
forall_base_mark_glyph_pairs(infos, |i1, i2, infos| {
markbasepos(&subtables, i1, i2, infos)
})
}
PosLookup::MarkLigPos(ref subtables) => {
forall_base_mark_glyph_pairs(infos, |i1, i2, infos| {
markligpos(&subtables, i1, i2, infos)
})
}
PosLookup::MarkMarkPos(ref subtables) => {
forall_mark_mark_glyph_pairs(infos, |i1, i2, infos| {
markmarkpos(&subtables, i1, i2, infos)
})
}
PosLookup::ContextPos(ref subtables) => {
forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
contextpos(
gpos_cache,
&lookup_list,
opt_gdef_table,
match_type,
&subtables,
i,
infos,
)
})
}
PosLookup::ChainContextPos(ref subtables) => {
forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
chaincontextpos(
gpos_cache,
&lookup_list,
opt_gdef_table,
match_type,
&subtables,
i,
infos,
)
})
}
}
} else {
Ok(())
}
}
fn gpos_lookup_singlepos(
subtables: &[SinglePos],
glyph_index: u16,
) -> Result<ValueRecord, ParseError> {
for singlepos in subtables {
if let Some(val) = singlepos.apply(glyph_index)? {
return Ok(Some(val));
}
}
Ok(None)
}
fn gpos_lookup_pairpos(
subtables: &[PairPos],
glyph_index1: u16,
glyph_index2: u16,
) -> Result<Option<(ValueRecord, ValueRecord)>, ParseError> {
for pairpos in subtables {
if let Some((val1, val2)) = pairpos.apply(glyph_index1, glyph_index2)? {
return Ok(Some((val1, val2)));
}
}
Ok(None)
}
fn gpos_lookup_cursivepos(
subtables: &[CursivePos],
glyph_index1: u16,
glyph_index2: u16,
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
for cursivepos in subtables {
if let Some((an1, an2)) = cursivepos.apply(glyph_index1, glyph_index2)? {
return Ok(Some((an1, an2)));
}
}
Ok(None)
}
fn gpos_lookup_markbasepos(
subtables: &[MarkBasePos],
glyph_index1: u16,
glyph_index2: u16,
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
for markbasepos in subtables {
if let Some((an1, an2)) = markbasepos.apply(glyph_index1, glyph_index2)? {
return Ok(Some((an1, an2)));
}
}
Ok(None)
}
fn gpos_lookup_markligpos(
subtables: &[MarkLigPos],
glyph_index1: u16,
glyph_index2: u16,
liga_component_index: u16,
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
for markligpos in subtables {
if let Some((an1, an2)) = markligpos.apply(
glyph_index1,
glyph_index2,
usize::from(liga_component_index),
)? {
return Ok(Some((an1, an2)));
}
}
Ok(None)
}
fn gpos_lookup_markmarkpos(
subtables: &[MarkBasePos],
glyph_index1: u16,
glyph_index2: u16,
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
for markmarkpos in subtables {
if let Some((an1, an2)) = markmarkpos.apply(glyph_index1, glyph_index2)? {
return Ok(Some((an1, an2)));
}
}
Ok(None)
}
fn gpos_lookup_contextpos<'a>(
opt_gdef_table: Option<&GDEFTable>,
match_type: MatchType,
subtables: &'a [ContextLookup<GPOS>],
glyph_index: u16,
i: usize,
infos: &mut [Info],
) -> Result<Option<Box<PosContext<'a>>>, ParseError> {
for context_lookup in subtables {
if let Some(context) = context_lookup_info(&context_lookup, glyph_index, |context| {
context.matches(opt_gdef_table, match_type, infos, i)
})? {
return Ok(Some(context));
}
}
Ok(None)
}
fn gpos_lookup_chaincontextpos<'a>(
opt_gdef_table: Option<&GDEFTable>,
match_type: MatchType,
subtables: &'a [ChainContextLookup<GPOS>],
glyph_index: u16,
i: usize,
infos: &mut [Info],
) -> Result<Option<Box<PosContext<'a>>>, ParseError> {
for chain_context_lookup in subtables {
if let Some(context) =
chain_context_lookup_info(&chain_context_lookup, glyph_index, |context| {
context.matches(opt_gdef_table, match_type, infos, i)
})?
{
return Ok(Some(context));
}
}
Ok(None)
}
#[derive(Debug)]
pub enum Placement {
None,
Distance(i32, i32),
Anchor(Anchor, Anchor),
}
#[derive(Debug)]
pub enum MarkPlacement {
None,
MarkAnchor(usize, Anchor, Anchor),
}
impl Placement {
fn combine_distance(&mut self, x2: i32, y2: i32) {
*self = match *self {
Placement::None => Placement::Distance(x2, y2),
Placement::Distance(x1, y1) => Placement::Distance(x1 + x2, y1 + y2),
Placement::Anchor(_, _) => {
return;
}
}
}
fn combine_anchor(&mut self, an1: Anchor, an2: Anchor) {
*self = match *self {
Placement::None => Placement::Anchor(an1, an2),
Placement::Distance(_, _) => {
return;
}
Placement::Anchor(_, _) => {
return;
}
}
}
}
#[derive(Debug)]
pub struct Info {
pub glyph: RawGlyph<()>,
pub kerning: i16,
pub placement: Placement,
pub mark_placement: MarkPlacement,
pub is_mark: bool,
}
impl Glyph for Info {
fn get_glyph_index(&self) -> Option<u16> {
self.glyph.glyph_index
}
}
impl Info {
pub fn init_from_glyphs(
opt_gdef_table: Option<&GDEFTable>,
glyphs: Vec<RawGlyph<()>>,
) -> Result<Vec<Info>, ParseError> {
let mut infos = Vec::with_capacity(glyphs.len());
for glyph in glyphs {
match glyph.glyph_index {
Some(glyph_index) => {
let is_mark = gdef_is_mark(opt_gdef_table, glyph_index);
let info = Info {
glyph,
kerning: 0,
placement: Placement::None,
mark_placement: MarkPlacement::None,
is_mark,
};
infos.push(info);
}
None => {}
}
}
Ok(infos)
}
}
impl Adjust {
fn apply(&self, info: &mut Info) {
if self.x_placement == 0 && self.y_placement == 0 {
if self.x_advance != 0 && self.y_advance == 0 {
info.kerning += self.x_advance;
} else if self.y_advance != 0 {
} else {
}
} else {
if self.y_advance == 0 {
info.placement
.combine_distance(i32::from(self.x_placement), i32::from(self.y_placement));
if self.x_advance != 0 {
info.kerning += self.x_advance;
}
} else {
}
}
}
}
fn forall_glyphs_match(
match_type: MatchType,
opt_gdef_table: Option<&GDEFTable>,
infos: &mut [Info],
f: impl Fn(usize, &mut [Info]) -> Result<(), ParseError>,
) -> Result<(), ParseError> {
for i in 0..infos.len() {
if match_type.match_glyph(opt_gdef_table, &infos[i]) {
f(i, infos)?;
}
}
Ok(())
}
fn forall_glyph_pairs_match(
match_type: MatchType,
opt_gdef_table: Option<&GDEFTable>,
infos: &mut [Info],
f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
) -> Result<(), ParseError> {
if let Some(mut i1) = match_type.find_first(opt_gdef_table, infos) {
while let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
f(i1, i2, infos)?;
i1 = i2;
}
}
Ok(())
}
fn forall_base_mark_glyph_pairs(
infos: &mut [Info],
f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
) -> Result<(), ParseError> {
let mut i = 0;
'outer: while i + 1 < infos.len() {
if !infos[i].is_mark {
for j in i + 1..infos.len() {
f(i, j, infos)?;
if !infos[j].is_mark {
i = j;
continue 'outer;
}
}
}
i += 1;
}
Ok(())
}
fn forall_mark_mark_glyph_pairs(
infos: &mut [Info],
f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
) -> Result<(), ParseError> {
let mut start = 0;
'outer: loop {
let mut i = start;
while i + 1 < infos.len() {
if infos[i].is_mark {
for j in i + 1..infos.len() {
f(i, j, infos)?;
if !infos[j].is_mark {
start = i + 1;
continue 'outer;
}
}
}
i += 1;
}
break;
}
Ok(())
}
fn singlepos(subtables: &[SinglePos], i: &mut Info) -> Result<(), ParseError> {
let glyph_index = i.glyph.glyph_index.unwrap();
if let Some(adj) = gpos_lookup_singlepos(subtables, glyph_index)? {
adj.apply(i);
}
Ok(())
}
fn pairpos(
subtables: &[PairPos],
i1: usize,
i2: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
match gpos_lookup_pairpos(
subtables,
infos[i1].glyph.glyph_index.unwrap(),
infos[i2].glyph.glyph_index.unwrap(),
)? {
Some((opt_adj1, opt_adj2)) => {
if let Some(adj1) = opt_adj1 {
adj1.apply(&mut infos[i1]);
}
if let Some(adj2) = opt_adj2 {
adj2.apply(&mut infos[i2]);
}
Ok(())
}
None => Ok(()),
}
}
fn cursivepos(
subtables: &[CursivePos],
i1: usize,
i2: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
match gpos_lookup_cursivepos(
subtables,
infos[i1].glyph.glyph_index.unwrap(),
infos[i2].glyph.glyph_index.unwrap(),
)? {
Some((anchor1, anchor2)) => {
infos[i1].placement.combine_anchor(anchor2, anchor1);
Ok(())
}
None => Ok(()),
}
}
fn markbasepos(
subtables: &[MarkBasePos],
i1: usize,
i2: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
match gpos_lookup_markbasepos(
subtables,
infos[i1].glyph.glyph_index.unwrap(),
infos[i2].glyph.glyph_index.unwrap(),
)? {
Some((anchor1, anchor2)) => {
infos[i2].mark_placement = MarkPlacement::MarkAnchor(i1, anchor1, anchor2);
infos[i2].is_mark = true;
Ok(())
}
None => Ok(()),
}
}
fn markligpos(
subtables: &[MarkLigPos],
i1: usize,
i2: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
match gpos_lookup_markligpos(
subtables,
infos[i1].glyph.glyph_index.unwrap(),
infos[i2].glyph.glyph_index.unwrap(),
infos[i2].glyph.liga_component_pos,
)? {
Some((anchor1, anchor2)) => {
infos[i2].mark_placement = MarkPlacement::MarkAnchor(i1, anchor1, anchor2);
infos[i2].is_mark = true;
Ok(())
}
None => Ok(()),
}
}
fn markmarkpos(
subtables: &[MarkBasePos],
i1: usize,
i2: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
match gpos_lookup_markmarkpos(
subtables,
infos[i1].glyph.glyph_index.unwrap(),
infos[i2].glyph.glyph_index.unwrap(),
)? {
Some((anchor1, anchor2)) => {
infos[i2].mark_placement = MarkPlacement::MarkAnchor(i1, anchor1, anchor2);
infos[i2].is_mark = true;
Ok(())
}
None => Ok(()),
}
}
fn contextpos<'a>(
gpos_cache: &LayoutCache<GPOS>,
lookup_list: &LookupList<GPOS>,
opt_gdef_table: Option<&GDEFTable>,
match_type: MatchType,
subtables: &[ContextLookup<GPOS>],
i: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
let glyph_index = infos[i].glyph.glyph_index.unwrap();
match gpos_lookup_contextpos(opt_gdef_table, match_type, subtables, glyph_index, i, infos)? {
Some(pos) => apply_pos_context(
gpos_cache,
lookup_list,
opt_gdef_table,
match_type,
&pos,
i,
infos,
),
None => Ok(()),
}
}
fn chaincontextpos<'a>(
gpos_cache: &LayoutCache<GPOS>,
lookup_list: &LookupList<GPOS>,
opt_gdef_table: Option<&GDEFTable>,
match_type: MatchType,
subtables: &[ChainContextLookup<GPOS>],
i: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
let glyph_index = infos[i].glyph.glyph_index.unwrap();
match gpos_lookup_chaincontextpos(opt_gdef_table, match_type, subtables, glyph_index, i, infos)?
{
Some(pos) => apply_pos_context(
gpos_cache,
lookup_list,
opt_gdef_table,
match_type,
&pos,
i,
infos,
),
None => Ok(()),
}
}
fn apply_pos_context<'a>(
gpos_cache: &LayoutCache<GPOS>,
lookup_list: &LookupList<GPOS>,
opt_gdef_table: Option<&GDEFTable>,
_match_type: MatchType,
pos: &PosContext<'_>,
i: usize,
infos: &mut [Info],
) -> Result<(), ParseError> {
for (pos_index, pos_lookup_index) in pos.lookup_array {
apply_pos(
gpos_cache,
lookup_list,
opt_gdef_table,
usize::from(*pos_index),
usize::from(*pos_lookup_index),
infos,
i,
)?;
}
Ok(())
}
fn apply_pos<'a>(
gpos_cache: &LayoutCache<GPOS>,
lookup_list: &LookupList<GPOS>,
opt_gdef_table: Option<&GDEFTable>,
pos_index: usize,
lookup_index: usize,
infos: &mut [Info],
index: usize,
) -> Result<(), ParseError> {
let lookup = lookup_list.lookup_cache_gpos(gpos_cache, lookup_index)?;
let match_type = MatchType::from_lookup_flag(lookup.lookup_flag);
let i1;
match match_type.find_nth(opt_gdef_table, infos, index, pos_index) {
Some(index1) => i1 = index1,
None => return Ok(()),
}
match lookup.lookup_subtables {
PosLookup::SinglePos(ref subtables) => singlepos(&subtables, &mut infos[i1]),
PosLookup::PairPos(ref subtables) => {
if let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
pairpos(&subtables, i1, i2, infos)
} else {
Ok(())
}
}
PosLookup::CursivePos(ref subtables) => {
if let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
cursivepos(&subtables, i1, i2, infos)
} else {
Ok(())
}
}
PosLookup::MarkBasePos(ref subtables) => {
if let Some(base_index) = MatchType::ignore_marks().find_prev(opt_gdef_table, infos, i1)
{
markbasepos(&subtables, base_index, i1, infos)
} else {
Ok(())
}
}
PosLookup::MarkLigPos(ref subtables) => {
if let Some(base_index) = MatchType::ignore_marks().find_prev(opt_gdef_table, infos, i1)
{
markligpos(&subtables, base_index, i1, infos)
} else {
Ok(())
}
}
PosLookup::MarkMarkPos(ref subtables) => {
if let Some(base_index) = match_type.find_prev(opt_gdef_table, infos, i1) {
markmarkpos(&subtables, base_index, i1, infos)
} else {
Ok(())
}
}
PosLookup::ContextPos(ref _subtables) => Ok(()),
PosLookup::ChainContextPos(ref _subtables) => Ok(()),
}
}
pub fn gpos_apply(
gpos_cache: &LayoutCache<GPOS>,
opt_gdef_table: Option<&GDEFTable>,
kerning: bool,
script_tag: u32,
lang_tag: u32,
infos: &mut [Info],
) -> Result<(), ParseError> {
let gpos_table = &gpos_cache.layout_table;
if opentype::is_indic_script_tag(script_tag) {
indic::gpos_apply_indic(
gpos_cache,
&gpos_table,
opt_gdef_table,
script_tag,
lang_tag,
infos,
)
} else {
if let Some(script) = gpos_table.find_script_or_default(script_tag)? {
if let Some(langsys) = script.find_langsys_or_default(lang_tag)? {
if script_tag == tag::ARAB || script_tag == tag::SYRC {
gpos_apply0(
&gpos_cache,
&gpos_table,
opt_gdef_table,
&langsys,
&[tag::KERN, tag::CURS, tag::MARK, tag::MKMK],
infos,
)
} else if script_tag == tag::LATN
|| script_tag == tag::CYRL
|| script_tag == tag::GREK
{
if kerning {
gpos_apply0(
&gpos_cache,
&gpos_table,
opt_gdef_table,
&langsys,
&[tag::DIST, tag::KERN, tag::MARK, tag::MKMK],
infos,
)
} else {
gpos_apply0(
&gpos_cache,
&gpos_table,
opt_gdef_table,
&langsys,
&[tag::DIST, tag::MARK, tag::MKMK],
infos,
)
}
} else {
if kerning {
gpos_apply0(
&gpos_cache,
&gpos_table,
opt_gdef_table,
&langsys,
&[tag::KERN, tag::MARK, tag::MKMK],
infos,
)
} else {
gpos_apply0(
&gpos_cache,
&gpos_table,
opt_gdef_table,
&langsys,
&[tag::MARK, tag::MKMK],
infos,
)
}
}
} else {
Ok(())
}
} else {
Ok(())
}
}
}
pub fn gpos_apply0(
gpos_cache: &LayoutCache<GPOS>,
gpos_table: &LayoutTable<GPOS>,
opt_gdef_table: Option<&GDEFTable>,
langsys: &LangSys,
feature_tags: &[u32],
infos: &mut [Info],
) -> Result<(), ParseError> {
for feature_tag in feature_tags {
if let Some(feature_table) = gpos_table.find_langsys_feature(&langsys, *feature_tag)? {
for lookup_index in &feature_table.lookup_indices {
gpos_apply_lookup(
gpos_cache,
gpos_table,
opt_gdef_table,
usize::from(*lookup_index),
infos,
)?;
}
}
}
Ok(())
}