use crate::buffer::*;
use crate::hint::Likely;
use crate::ot::layout::*;
use crate::plan::ShapePlan;
use crate::Direction;
use crate::ShaperFont;
pub fn position(plan: &ShapePlan, font: &ShaperFont<'_, '_>, buffer: &mut Buffer) {
#[cfg(feature = "tracing")]
let tag = crate::ot::map::MessageTag(plan.ot_map.chosen_script(LayoutTableKind::Gpos));
if !buffer_message!(buffer, font, "start table GPOS script tag '{tag:#}'") {
return;
}
let table = font.layout().ot.gpos();
apply_layout_table(plan, font, buffer, table.as_ref());
buffer_message!(buffer, font, "end table GPOS script tag '{tag:#}'");
}
pub mod attach_type {
pub const MARK: u8 = 1;
pub const CURSIVE: u8 = 2;
}
fn propagate_attachment_offsets(
pos: &mut [GlyphPosition],
len: usize,
i: usize,
direction: Direction,
nesting_level: usize,
) -> Option<()> {
let this = pos.get_mut(i).likely()?;
let chain = this.attach_chain();
let kind = this.attach_type();
this.set_attach_chain(0);
let j = (i as isize + isize::from(chain)) as usize;
if j >= len {
return Some(());
}
if nesting_level == 0 {
return Some(());
}
if pos.get(j).likely()?.attach_chain() != 0 {
propagate_attachment_offsets(pos, len, j, direction, nesting_level - 1);
}
let parent = *pos.get(j).likely()?;
let mut this = *pos.get(i).likely()?;
match kind {
attach_type::MARK => {
if direction.is_horizontal() {
this.x_offset = this.x_offset.saturating_add(parent.x_offset);
} else {
this.y_offset = this.y_offset.saturating_add(parent.y_offset);
}
if j < i {
if direction.is_forward() {
for k in pos.get(j..i).likely()? {
this.x_offset = this.x_offset.saturating_sub(k.x_advance);
this.y_offset = this.y_offset.saturating_sub(k.y_advance);
}
} else {
for k in pos.get(j + 1..i + 1).likely()? {
this.x_offset = this.x_offset.saturating_add(k.x_advance);
this.y_offset = this.y_offset.saturating_add(k.y_advance);
}
}
} else {
if direction.is_forward() {
for k in pos.get(i..j).likely()? {
this.x_offset = this.x_offset.saturating_add(k.x_advance);
this.y_offset = this.y_offset.saturating_add(k.y_advance);
}
} else {
for k in pos.get(i + 1..j + 1).likely()? {
this.x_offset = this.x_offset.saturating_sub(k.x_advance);
this.y_offset = this.y_offset.saturating_sub(k.y_advance);
}
}
}
}
attach_type::CURSIVE => {
if direction.is_horizontal() {
this.y_offset = this.y_offset.saturating_add(parent.y_offset);
} else {
this.x_offset = this.x_offset.saturating_add(parent.x_offset);
}
}
_ => {}
}
*pos.get_mut(i).likely()? = this;
Some(())
}
pub fn position_start(buffer: &mut Buffer) {
let len = buffer.len;
for pos in buffer.pos.get_mut(..len).unwrap_or_default() {
pos.set_attach_chain(0);
pos.set_attach_type(0);
}
}
pub fn position_finish_advances(_: &mut Buffer) {
}
pub fn position_finish_offsets(buffer: &mut Buffer) {
buffer.assert_gsubgpos_vars();
let len = buffer.len;
let direction = buffer.direction;
if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT != 0 {
let pos = &mut buffer.pos;
if direction.is_forward() {
for i in 0..len {
if pos.get(i).is_some_and(|p| p.attach_chain() != 0) {
propagate_attachment_offsets(pos, len, i, direction, MAX_NESTING_LEVEL);
}
}
} else {
for i in (0..len).rev() {
if pos.get(i).is_some_and(|p| p.attach_chain() != 0) {
propagate_attachment_offsets(pos, len, i, direction, MAX_NESTING_LEVEL);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn propagate_attachment_offsets_saturates_deep_mark_chains() {
let len = MAX_NESTING_LEVEL + 2;
let mut pos = vec![GlyphPosition::default(); len];
for i in 1..len {
pos[i].x_offset = 40_000_000;
pos[i].y_offset = 40_000_000;
pos[i].set_attach_type(attach_type::MARK);
pos[i].set_attach_chain(-1);
}
propagate_attachment_offsets(
&mut pos,
len,
len - 1,
Direction::LeftToRight,
MAX_NESTING_LEVEL,
);
assert_eq!(pos[len - 1].x_offset, i32::MAX);
assert_eq!(pos[len - 1].y_offset, 40_000_000);
}
}