use ttf_parser::gpos::*;
use ttf_parser::opentype_layout::LookupIndex;
use ttf_parser::GlyphId;
use super::buffer::*;
use super::hb_font_t;
use super::ot_layout::*;
use super::ot_layout_common::{lookup_flags, PositioningLookup, PositioningTable};
use super::ot_layout_gsubgpos::{skipping_iterator_t, Apply, OT::hb_ot_apply_context_t};
use super::ot_shape_plan::hb_ot_shape_plan_t;
use crate::Direction;
pub fn position(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
apply_layout_table(plan, face, buffer, face.gpos.as_ref());
}
trait ValueRecordExt {
fn is_empty(&self) -> bool;
fn apply(&self, ctx: &mut hb_ot_apply_context_t, idx: usize) -> bool;
fn apply_to_pos(&self, ctx: &mut hb_ot_apply_context_t, pos: &mut GlyphPosition) -> bool;
}
impl ValueRecordExt for ValueRecord<'_> {
fn is_empty(&self) -> bool {
self.x_placement == 0
&& self.y_placement == 0
&& self.x_advance == 0
&& self.y_advance == 0
&& self.x_placement_device.is_none()
&& self.y_placement_device.is_none()
&& self.x_advance_device.is_none()
&& self.y_advance_device.is_none()
}
fn apply(&self, ctx: &mut hb_ot_apply_context_t, idx: usize) -> bool {
let mut pos = ctx.buffer.pos[idx];
let worked = self.apply_to_pos(ctx, &mut pos);
ctx.buffer.pos[idx] = pos;
worked
}
fn apply_to_pos(&self, ctx: &mut hb_ot_apply_context_t, pos: &mut GlyphPosition) -> bool {
let horizontal = ctx.buffer.direction.is_horizontal();
let mut worked = false;
if self.x_placement != 0 {
pos.x_offset += i32::from(self.x_placement);
worked = true;
}
if self.y_placement != 0 {
pos.y_offset += i32::from(self.y_placement);
worked = true;
}
if self.x_advance != 0 && horizontal {
pos.x_advance += i32::from(self.x_advance);
worked = true;
}
if self.y_advance != 0 && !horizontal {
pos.y_advance -= i32::from(self.y_advance);
worked = true;
}
{
let (ppem_x, ppem_y) = ctx.face.pixels_per_em().unwrap_or((0, 0));
let coords = ctx.face.ttfp_face.variation_coordinates().len();
let use_x_device = ppem_x != 0 || coords != 0;
let use_y_device = ppem_y != 0 || coords != 0;
if use_x_device {
if let Some(device) = self.x_placement_device {
pos.x_offset += device.get_x_delta(ctx.face).unwrap_or(0);
worked = true; }
}
if use_y_device {
if let Some(device) = self.y_placement_device {
pos.y_offset += device.get_y_delta(ctx.face).unwrap_or(0);
worked = true;
}
}
if horizontal && use_x_device {
if let Some(device) = self.x_advance_device {
pos.x_advance += device.get_x_delta(ctx.face).unwrap_or(0);
worked = true;
}
}
if !horizontal && use_y_device {
if let Some(device) = self.y_advance_device {
pos.y_advance -= device.get_y_delta(ctx.face).unwrap_or(0);
worked = true;
}
}
}
worked
}
}
trait AnchorExt {
fn get(&self, face: &hb_font_t) -> (i32, i32);
}
impl AnchorExt for Anchor<'_> {
fn get(&self, face: &hb_font_t) -> (i32, i32) {
let mut x = i32::from(self.x);
let mut y = i32::from(self.y);
if self.x_device.is_some() || self.y_device.is_some() {
let (ppem_x, ppem_y) = face.pixels_per_em().unwrap_or((0, 0));
let coords = face.ttfp_face.variation_coordinates().len();
if let Some(device) = self.x_device {
if ppem_x != 0 || coords != 0 {
x += device.get_x_delta(face).unwrap_or(0);
}
}
if let Some(device) = self.y_device {
if ppem_y != 0 || coords != 0 {
y += device.get_y_delta(face).unwrap_or(0);
}
}
}
(x, y)
}
}
impl<'a> LayoutTable for PositioningTable<'a> {
const INDEX: TableIndex = TableIndex::GPOS;
const IN_PLACE: bool = true;
type Lookup = PositioningLookup<'a>;
fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> {
self.lookups.get(usize::from(index))
}
}
impl LayoutLookup for PositioningLookup<'_> {
fn props(&self) -> u32 {
self.props
}
fn is_reverse(&self) -> bool {
false
}
fn covers(&self, glyph: GlyphId) -> bool {
self.coverage.contains(glyph)
}
}
impl Apply for PositioningLookup<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
if self.covers(ctx.buffer.cur(0).as_glyph()) {
for subtable in &self.subtables {
if subtable.apply(ctx).is_some() {
return Some(());
}
}
}
None
}
}
impl Apply for SingleAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let glyph = ctx.buffer.cur(0).as_glyph();
let record = match self {
Self::Format1 { coverage, value } => {
coverage.get(glyph)?;
*value
}
Self::Format2 { coverage, values } => {
let index = coverage.get(glyph)?;
values.get(index)?
}
};
record.apply(ctx, ctx.buffer.idx);
ctx.buffer.idx += 1;
Some(())
}
}
impl Apply for PairAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let first_glyph = ctx.buffer.cur(0).as_glyph();
let first_glyph_coverage_index = self.coverage().get(first_glyph)?;
let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, 1, false);
let mut unsafe_to = 0;
if !iter.next(Some(&mut unsafe_to)) {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
return None;
}
let second_glyph_index = iter.index();
let second_glyph = ctx.buffer.info[second_glyph_index].as_glyph();
let finish = |ctx: &mut hb_ot_apply_context_t, has_record2| {
ctx.buffer.idx = second_glyph_index;
if has_record2 {
ctx.buffer.idx += 1;
}
Some(())
};
let boring = |ctx: &mut hb_ot_apply_context_t, has_record2| {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
finish(ctx, has_record2)
};
let success = |ctx: &mut hb_ot_apply_context_t, flag1, flag2, has_record2| {
if flag1 || flag2 {
ctx.buffer
.unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
finish(ctx, has_record2)
} else {
boring(ctx, has_record2)
}
};
let bail = |ctx: &mut hb_ot_apply_context_t, records: (ValueRecord, ValueRecord)| {
let flag1 = records.0.apply(ctx, ctx.buffer.idx);
let flag2 = records.1.apply(ctx, second_glyph_index);
let has_record2 = !records.1.is_empty();
success(ctx, flag1, flag2, has_record2)
};
let records = match self {
Self::Format1 { sets, .. } => {
sets.get(first_glyph_coverage_index)?.get(second_glyph)?
}
Self::Format2 {
classes, matrix, ..
} => {
let classes = (classes.0.get(first_glyph), classes.1.get(second_glyph));
let records = match matrix.get(classes) {
Some(v) => v,
None => {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(iter.index() + 1));
return None;
}
};
return bail(ctx, records);
}
};
bail(ctx, records)
}
}
impl Apply for CursiveAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let this = ctx.buffer.cur(0).as_glyph();
let index_this = self.coverage.get(this)?;
let entry_this = self.sets.entry(index_this)?;
let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, 1, false);
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
let i = iter.index();
let prev = ctx.buffer.info[i].as_glyph();
let index_prev = self.coverage.get(prev)?;
let Some(exit_prev) = self.sets.exit(index_prev) else {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter.index()), Some(ctx.buffer.idx + 1));
return None;
};
let (exit_x, exit_y) = exit_prev.get(ctx.face);
let (entry_x, entry_y) = entry_this.get(ctx.face);
let direction = ctx.buffer.direction;
let j = ctx.buffer.idx;
ctx.buffer.unsafe_to_break(Some(i), Some(j));
let pos = &mut ctx.buffer.pos;
match direction {
Direction::LeftToRight => {
pos[i].x_advance = exit_x + pos[i].x_offset;
let d = entry_x + pos[j].x_offset;
pos[j].x_advance -= d;
pos[j].x_offset -= d;
}
Direction::RightToLeft => {
let d = exit_x + pos[i].x_offset;
pos[i].x_advance -= d;
pos[i].x_offset -= d;
pos[j].x_advance = entry_x + pos[j].x_offset;
}
Direction::TopToBottom => {
pos[i].y_advance = exit_y + pos[i].y_offset;
let d = entry_y + pos[j].y_offset;
pos[j].y_advance -= d;
pos[j].y_offset -= d;
}
Direction::BottomToTop => {
let d = exit_y + pos[i].y_offset;
pos[i].y_advance -= d;
pos[i].y_offset -= d;
pos[j].y_advance = entry_y;
}
Direction::Invalid => {}
}
let mut child = i;
let mut parent = j;
let mut x_offset = entry_x - exit_x;
let mut y_offset = entry_y - exit_y;
if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 {
core::mem::swap(&mut child, &mut parent);
x_offset = -x_offset;
y_offset = -y_offset;
}
reverse_cursive_minor_offset(pos, child, direction, parent);
pos[child].set_attach_type(attach_type::CURSIVE);
pos[child].set_attach_chain((parent as isize - child as isize) as i16);
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
if direction.is_horizontal() {
pos[child].y_offset = y_offset;
} else {
pos[child].x_offset = x_offset;
}
if pos[parent].attach_chain() == -pos[child].attach_chain() {
pos[parent].set_attach_chain(0);
}
ctx.buffer.idx += 1;
Some(())
}
}
impl Apply for MarkToBaseAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let buffer = &ctx.buffer;
let mark_glyph = ctx.buffer.cur(0).as_glyph();
let mark_index = self.mark_coverage.get(mark_glyph)?;
let mut iter = skipping_iterator_t::new(ctx, buffer.idx, 1, false);
iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
let info = &buffer.info;
loop {
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
let idx = iter.index();
if !_hb_glyph_info_multiplied(&info[idx])
|| _hb_glyph_info_get_lig_comp(&info[idx]) == 0
|| idx == 0
|| _hb_glyph_info_is_mark(&info[idx - 1])
|| _hb_glyph_info_get_lig_id(&info[idx])
!= _hb_glyph_info_get_lig_id(&info[idx - 1])
|| _hb_glyph_info_get_lig_comp(&info[idx])
!= _hb_glyph_info_get_lig_comp(&info[idx - 1]) + 1
{
break;
}
iter.reject();
}
let iter_idx = iter.index();
let base_glyph = info[iter_idx].as_glyph();
let Some(base_index) = self.base_coverage.get(base_glyph) else {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
};
self.marks
.apply(ctx, self.anchors, mark_index, base_index, iter_idx)
}
}
impl Apply for MarkToLigatureAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let buffer = &ctx.buffer;
let mark_glyph = ctx.buffer.cur(0).as_glyph();
let mark_index = self.mark_coverage.get(mark_glyph)?;
let mut iter = skipping_iterator_t::new(ctx, buffer.idx, 1, false);
iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
let iter_idx = iter.index();
let lig_glyph = buffer.info[iter_idx].as_glyph();
let Some(lig_index) = self.ligature_coverage.get(lig_glyph) else {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
};
let lig_attach = self.ligature_array.get(lig_index)?;
let comp_count = lig_attach.rows;
if comp_count == 0 {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
}
let lig_id = _hb_glyph_info_get_lig_id(&buffer.info[iter_idx]);
let mark_id = _hb_glyph_info_get_lig_id(&buffer.cur(0));
let mark_comp = u16::from(_hb_glyph_info_get_lig_comp(buffer.cur(0)));
let matches = lig_id != 0 && lig_id == mark_id && mark_comp > 0;
let comp_index = if matches {
mark_comp.min(comp_count)
} else {
comp_count
} - 1;
self.marks
.apply(ctx, lig_attach, mark_index, comp_index, iter_idx)
}
}
impl Apply for MarkToMarkAdjustment<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
let buffer = &ctx.buffer;
let mark1_glyph = ctx.buffer.cur(0).as_glyph();
let mark1_index = self.mark1_coverage.get(mark1_glyph)?;
let mut iter = skipping_iterator_t::new(ctx, buffer.idx, 1, false);
iter.set_lookup_props(ctx.lookup_props & !u32::from(lookup_flags::IGNORE_FLAGS));
let mut unsafe_from = 0;
if !iter.prev(Some(&mut unsafe_from)) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
return None;
}
let iter_idx = iter.index();
if !_hb_glyph_info_is_mark(&buffer.info[iter_idx]) {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
}
let id1 = _hb_glyph_info_get_lig_id(buffer.cur(0));
let id2 = _hb_glyph_info_get_lig_id(&buffer.info[iter_idx]);
let comp1 = _hb_glyph_info_get_lig_comp(buffer.cur(0));
let comp2 = _hb_glyph_info_get_lig_comp(&buffer.info[iter_idx]);
let matches = if id1 == id2 {
id1 == 0 || comp1 == comp2
} else {
(id1 > 0 && comp1 == 0) || (id2 > 0 && comp2 == 0)
};
if !matches {
ctx.buffer
.unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1));
return None;
}
let mark2_glyph = buffer.info[iter_idx].as_glyph();
let mark2_index = self.mark2_coverage.get(mark2_glyph)?;
self.marks
.apply(ctx, self.mark2_matrix, mark1_index, mark2_index, iter_idx)
}
}
trait MarkArrayExt {
fn apply(
&self,
ctx: &mut hb_ot_apply_context_t,
anchors: AnchorMatrix,
mark_index: u16,
glyph_index: u16,
glyph_pos: usize,
) -> Option<()>;
}
impl MarkArrayExt for MarkArray<'_> {
fn apply(
&self,
ctx: &mut hb_ot_apply_context_t,
anchors: AnchorMatrix,
mark_index: u16,
glyph_index: u16,
glyph_pos: usize,
) -> Option<()> {
let (mark_class, mark_anchor) = self.get(mark_index)?;
let base_anchor = anchors.get(glyph_index, mark_class)?;
let (mark_x, mark_y) = mark_anchor.get(ctx.face);
let (base_x, base_y) = base_anchor.get(ctx.face);
ctx.buffer
.unsafe_to_break(Some(glyph_pos), Some(ctx.buffer.idx + 1));
let idx = ctx.buffer.idx;
let pos = ctx.buffer.cur_pos_mut();
pos.x_offset = base_x - mark_x;
pos.y_offset = base_y - mark_y;
pos.set_attach_type(attach_type::MARK);
pos.set_attach_chain((glyph_pos as isize - idx as isize) as i16);
ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
ctx.buffer.idx += 1;
Some(())
}
}
pub mod attach_type {
pub const MARK: u8 = 1;
pub const CURSIVE: u8 = 2;
}
pub trait TryNumFrom<T>: Sized {
fn try_num_from(_: T) -> Option<Self>;
}
impl TryNumFrom<f32> for i32 {
#[inline]
fn try_num_from(v: f32) -> Option<Self> {
const MIN: f32 = core::i32::MIN as f32;
const MAX_P1: f32 = core::i32::MAX as f32;
if v >= MIN && v < MAX_P1 {
Some(v as i32)
} else {
None
}
}
}
trait DeviceExt {
fn get_x_delta(&self, face: &hb_font_t) -> Option<i32>;
fn get_y_delta(&self, face: &hb_font_t) -> Option<i32>;
}
impl DeviceExt for Device<'_> {
fn get_x_delta(&self, face: &hb_font_t) -> Option<i32> {
match self {
Device::Hinting(hinting) => hinting.x_delta(face.units_per_em, face.pixels_per_em()),
Device::Variation(variation) => face
.tables()
.gdef?
.glyph_variation_delta(
variation.outer_index,
variation.inner_index,
face.variation_coordinates(),
)
.and_then(|float| i32::try_num_from(super::round(float))),
}
}
fn get_y_delta(&self, face: &hb_font_t) -> Option<i32> {
match self {
Device::Hinting(hinting) => hinting.y_delta(face.units_per_em, face.pixels_per_em()),
Device::Variation(variation) => face
.tables()
.gdef?
.glyph_variation_delta(
variation.outer_index,
variation.inner_index,
face.variation_coordinates(),
)
.and_then(|float| i32::try_num_from(super::round(float))),
}
}
}
impl Apply for PositioningSubtable<'_> {
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
match self {
Self::Single(t) => t.apply(ctx),
Self::Pair(t) => t.apply(ctx),
Self::Cursive(t) => t.apply(ctx),
Self::MarkToBase(t) => t.apply(ctx),
Self::MarkToLigature(t) => t.apply(ctx),
Self::MarkToMark(t) => t.apply(ctx),
Self::Context(t) => t.apply(ctx),
Self::ChainContext(t) => t.apply(ctx),
}
}
}
fn reverse_cursive_minor_offset(
pos: &mut [GlyphPosition],
i: usize,
direction: Direction,
new_parent: usize,
) {
let chain = pos[i].attach_chain();
let attach_type = pos[i].attach_type();
if chain == 0 || attach_type & attach_type::CURSIVE == 0 {
return;
}
pos[i].set_attach_chain(0);
let j = (i as isize + isize::from(chain)) as _;
if j == new_parent {
return;
}
reverse_cursive_minor_offset(pos, j, direction, new_parent);
if direction.is_horizontal() {
pos[j].y_offset = -pos[i].y_offset;
} else {
pos[j].x_offset = -pos[i].x_offset;
}
pos[j].set_attach_chain(-chain);
pos[j].set_attach_type(attach_type);
}
fn propagate_attachment_offsets(
pos: &mut [GlyphPosition],
len: usize,
i: usize,
direction: Direction,
) {
let chain = pos[i].attach_chain();
let kind = pos[i].attach_type();
if chain == 0 {
return;
}
pos[i].set_attach_chain(0);
let j = (i as isize + isize::from(chain)) as _;
if j >= len {
return;
}
propagate_attachment_offsets(pos, len, j, direction);
match kind {
attach_type::MARK => {
pos[i].x_offset += pos[j].x_offset;
pos[i].y_offset += pos[j].y_offset;
assert!(j < i);
if direction.is_forward() {
for k in j..i {
pos[i].x_offset -= pos[k].x_advance;
pos[i].y_offset -= pos[k].y_advance;
}
} else {
for k in j + 1..i + 1 {
pos[i].x_offset += pos[k].x_advance;
pos[i].y_offset += pos[k].y_advance;
}
}
}
attach_type::CURSIVE => {
if direction.is_horizontal() {
pos[i].y_offset += pos[j].y_offset;
} else {
pos[i].x_offset += pos[j].x_offset;
}
}
_ => {}
}
}
pub mod GPOS {
use super::*;
pub fn position_start(_: &hb_font_t, buffer: &mut hb_buffer_t) {
let len = buffer.len;
for pos in &mut buffer.pos[..len] {
pos.set_attach_chain(0);
pos.set_attach_type(0);
}
}
pub fn position_finish_advances(_: &hb_font_t, _: &mut hb_buffer_t) {}
pub fn position_finish_offsets(_: &hb_font_t, buffer: &mut hb_buffer_t) {
let len = buffer.len;
let direction = buffer.direction;
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT != 0 {
for i in 0..len {
propagate_attachment_offsets(&mut buffer.pos, len, i, direction);
}
}
}
}