use crate::tables::gpos::PosRecord;
use crate::Font;
const MAX_GSUB_BUFFER_GROWTH: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShapedGlyph {
pub glyph_id: u16,
pub cluster: u32,
pub x_offset: i32,
pub y_offset: i32,
pub x_advance: i32,
pub y_advance: i32,
}
#[derive(Debug, Clone, Copy)]
struct WorkGlyph {
gid: u16,
cluster: u32,
}
impl<'a> Font<'a> {
pub fn shape(
&self,
text: &str,
script: [u8; 4],
lang: Option<[u8; 4]>,
features: &[[u8; 4]],
) -> Vec<ShapedGlyph> {
let mut buf: Vec<WorkGlyph> = Vec::with_capacity(text.len());
for (byte_idx, ch) in text.char_indices() {
let gid = self.glyph_index(ch).unwrap_or(0);
buf.push(WorkGlyph {
gid,
cluster: byte_idx as u32,
});
}
self.run_gsub(&mut buf, script, lang, features);
self.run_gpos(buf, script, lang, features)
}
fn run_gsub(
&self,
buf: &mut Vec<WorkGlyph>,
script: [u8; 4],
lang: Option<[u8; 4]>,
features: &[[u8; 4]],
) {
if self.gsub.is_none() {
return;
}
let resolved = self.gsub_features_for_script_at_instance(script, lang);
let mut active: Vec<u16> = Vec::new();
for feat in &resolved {
if !features.contains(&feat.tag) {
continue;
}
for &li in &feat.lookup_indices {
if !active.contains(&li) {
active.push(li);
}
}
}
if active.is_empty() {
return;
}
active.sort_unstable();
let types = self.gsub_lookup_list();
for &li in &active {
let kind = types
.iter()
.find(|(idx, _, _)| *idx == li)
.map(|(_, k, _)| *k)
.unwrap_or(0);
let flags = self.gsub.as_ref().map(|g| g.lookup_flags(li)).unwrap_or(0);
self.apply_gsub_lookup(buf, li, kind, flags);
}
}
fn apply_gsub_lookup(&self, buf: &mut Vec<WorkGlyph>, li: u16, kind: u16, flags: u16) {
match kind {
1 => {
for w in buf.iter_mut() {
if let Some(g) = self.gsub_apply_lookup_type_1(li, w.gid) {
w.gid = g;
}
}
}
2 => {
let mut out: Vec<WorkGlyph> = Vec::with_capacity(buf.len());
let mut growth = 0usize;
for w in buf.iter() {
match self.gsub_apply_lookup_type_2(li, w.gid) {
Some(seq) => {
growth += seq.len();
for g in seq {
out.push(WorkGlyph {
gid: g,
cluster: w.cluster,
});
}
}
None => out.push(*w),
}
if growth > buf.len() + MAX_GSUB_BUFFER_GROWTH {
break;
}
}
if growth <= buf.len() + MAX_GSUB_BUFFER_GROWTH {
*buf = out;
}
}
3 => {
for w in buf.iter_mut() {
if let Some(g) = self.gsub_apply_lookup_type_3(li, w.gid, 0) {
w.gid = g;
}
}
}
4 => {
let mfs = self.gsub_lookup_mark_filtering_set(li);
let mut i = 0usize;
while i < buf.len() {
if self.lookup_skips_glyph(flags, mfs, buf[i].gid) {
i += 1;
continue;
}
let mut cand_gids: Vec<u16> = Vec::new();
let mut cand_idx: Vec<usize> = Vec::new();
for (off, w) in buf[i..].iter().enumerate() {
if self.lookup_skips_glyph(flags, mfs, w.gid) {
continue;
}
cand_gids.push(w.gid);
cand_idx.push(i + off);
}
if let Some((lig, consumed)) = self.gsub_apply_lookup_type_4(li, &cand_gids) {
if consumed >= 1 {
let cluster = buf[i].cluster;
buf[i] = WorkGlyph { gid: lig, cluster };
let to_remove: Vec<usize> =
cand_idx[1..consumed.min(cand_idx.len())].to_vec();
for &idx in to_remove.iter().rev() {
if idx < buf.len() {
buf.remove(idx);
}
}
i += 1;
continue;
}
}
i += 1;
}
}
5 => {
let mut pos = 0usize;
while pos < buf.len() {
let gids: Vec<u16> = buf.iter().map(|w| w.gid).collect();
if let Some(rewritten) = self.gsub_apply_lookup_type_5(li, &gids, pos) {
self.reconcile_context_rewrite(buf, &gids, rewritten, pos);
}
pos += 1;
}
}
6 => {
let mut pos = 0usize;
while pos < buf.len() {
let gids: Vec<u16> = buf.iter().map(|w| w.gid).collect();
if let Some(rewritten) = self.gsub_apply_lookup_type_6(li, &gids, pos) {
self.reconcile_context_rewrite(buf, &gids, rewritten, pos);
}
pos += 1;
}
}
8 => {
let gids: Vec<u16> = buf.iter().map(|w| w.gid).collect();
for pos in (0..buf.len()).rev() {
if let Some(g) = self.gsub_apply_lookup_type_8(li, &gids, pos) {
buf[pos].gid = g;
}
}
}
_ => {}
}
}
fn reconcile_context_rewrite(
&self,
buf: &mut Vec<WorkGlyph>,
old: &[u16],
new: Vec<u16>,
pos: usize,
) {
if new == old {
return;
}
let mut pre = 0usize;
while pre < old.len() && pre < new.len() && old[pre] == new[pre] {
pre += 1;
}
let mut suf = 0usize;
while suf < (old.len() - pre)
&& suf < (new.len() - pre)
&& old[old.len() - 1 - suf] == new[new.len() - 1 - suf]
{
suf += 1;
}
let cluster = buf.get(pos).map(|w| w.cluster).unwrap_or(0);
let mut rebuilt: Vec<WorkGlyph> = Vec::with_capacity(new.len());
for &g in &new[..pre] {
let c = buf.get(rebuilt.len()).map(|w| w.cluster).unwrap_or(cluster);
rebuilt.push(WorkGlyph { gid: g, cluster: c });
}
for &g in &new[pre..new.len() - suf] {
rebuilt.push(WorkGlyph { gid: g, cluster });
}
let suffix_start_old = old.len() - suf;
for (k, &g) in new[new.len() - suf..].iter().enumerate() {
let c = buf
.get(suffix_start_old + k)
.map(|w| w.cluster)
.unwrap_or(cluster);
rebuilt.push(WorkGlyph { gid: g, cluster: c });
}
*buf = rebuilt;
}
fn run_gpos(
&self,
buf: Vec<WorkGlyph>,
script: [u8; 4],
lang: Option<[u8; 4]>,
features: &[[u8; 4]],
) -> Vec<ShapedGlyph> {
let mut out: Vec<ShapedGlyph> = buf
.iter()
.map(|w| ShapedGlyph {
glyph_id: w.gid,
cluster: w.cluster,
x_offset: 0,
y_offset: 0,
x_advance: self.glyph_advance(w.gid) as i32,
y_advance: 0,
})
.collect();
if self.gpos.is_none() {
return out;
}
let resolved = self.gpos_features_for_script_at_instance(script, lang);
let mut active: Vec<u16> = Vec::new();
for feat in &resolved {
if !features.contains(&feat.tag) {
continue;
}
for &li in &feat.lookup_indices {
if !active.contains(&li) {
active.push(li);
}
}
}
if active.is_empty() {
return out;
}
active.sort_unstable();
let types = self.gpos_lookup_list();
for &li in &active {
let kind = types
.iter()
.find(|(idx, _, _)| *idx == li)
.map(|(_, k, _)| *k)
.unwrap_or(0);
self.apply_gpos_lookup(&mut out, li, kind);
}
out
}
fn apply_gpos_lookup(&self, out: &mut [ShapedGlyph], li: u16, kind: u16) {
match kind {
1 => {
for g in out.iter_mut() {
if let Some(v) = self.gpos_apply_lookup_type_1(li, g.glyph_id) {
g.x_offset += v.x_placement as i32;
g.y_offset += v.y_placement as i32;
g.x_advance += v.x_advance as i32;
g.y_advance += v.y_advance as i32;
}
}
}
2 => {
let gdef = self.gdef.as_ref();
let flags = self.gpos_lookup_flags(li);
let mfs = self.gpos_lookup_mark_filtering_set(li);
for i in 0..out.len() {
if self.lookup_skips_glyph(flags, mfs, out[i].glyph_id) {
continue;
}
let right_idx = ((i + 1)..out.len())
.find(|&k| !self.lookup_skips_glyph(flags, mfs, out[k].glyph_id));
let right_idx = match right_idx {
Some(k) => k,
None => break,
};
let left = out[i].glyph_id;
let right = out[right_idx].glyph_id;
let adj = self
.gpos
.as_ref()
.map(|g| g.lookup_kerning_at(li, left, right, gdef))
.unwrap_or(0);
out[i].x_advance += adj as i32;
}
}
3 => {
let flags = self.gpos_lookup_flags(li);
let mfs = self.gpos_lookup_mark_filtering_set(li);
let mut prev_exit: Option<(i16, i16)> = None;
for g in out.iter_mut() {
if self.lookup_skips_glyph(flags, mfs, g.glyph_id) {
continue;
}
if let Some(att) = self.gpos_apply_lookup_type_3(li, g.glyph_id) {
if let (Some((px, py)), Some((ex, ey))) = (prev_exit, att.entry) {
g.x_offset += (px - ex) as i32;
g.y_offset += (py - ey) as i32;
}
prev_exit = att.exit;
} else {
prev_exit = None;
}
}
}
4 => {
self.apply_mark_attach(out, li, false);
}
5 => {
self.apply_mark_to_ligature(out, li);
}
6 => {
self.apply_mark_attach(out, li, true);
}
7 => {
let gids: Vec<u16> = out.iter().map(|g| g.glyph_id).collect();
for pos in 0..out.len() {
if let Some(records) = self.gpos_apply_lookup_type_7(li, &gids, pos) {
apply_pos_records(out, &records);
}
}
}
8 => {
let gids: Vec<u16> = out.iter().map(|g| g.glyph_id).collect();
for pos in 0..out.len() {
if let Some(records) = self.gpos_apply_lookup_type_8(li, &gids, pos) {
apply_pos_records(out, &records);
}
}
}
_ => {}
}
}
fn apply_mark_attach(&self, out: &mut [ShapedGlyph], li: u16, to_mark: bool) {
let flags = self.gpos_lookup_flags(li);
let mfs = self.gpos_lookup_mark_filtering_set(li);
for i in 0..out.len() {
let mark = out[i].glyph_id;
if self.lookup_skips_glyph(flags, mfs, mark) {
continue;
}
for j in (0..i).rev() {
let base = out[j].glyph_id;
if self.lookup_skips_glyph(flags, mfs, base) {
continue;
}
let hit = if to_mark {
self.gpos
.as_ref()
.and_then(|g| g.apply_mark_to_mark_at(li, base, mark))
} else {
self.gpos
.as_ref()
.and_then(|g| g.apply_mark_to_base_at(li, base, mark))
};
if let Some((dx, dy)) = hit {
let between: i32 = out[j..i].iter().map(|g| g.x_advance).sum();
out[i].x_offset += dx as i32 - between;
out[i].y_offset += dy as i32;
}
break;
}
}
}
fn apply_mark_to_ligature(&self, out: &mut [ShapedGlyph], li: u16) {
let flags = self.gpos_lookup_flags(li);
let mfs = self.gpos_lookup_mark_filtering_set(li);
for i in 0..out.len() {
let mark = out[i].glyph_id;
if self.lookup_skips_glyph(flags, mfs, mark) {
continue;
}
for j in (0..i).rev() {
let lig = out[j].glyph_id;
if self.lookup_skips_glyph(flags, mfs, lig) {
continue;
}
if let Some((dx, dy)) = self
.gpos
.as_ref()
.and_then(|g| g.apply_lookup_type_5(li, lig, 0, mark))
{
let between: i32 = out[j..i].iter().map(|g| g.x_advance).sum();
out[i].x_offset += dx as i32 - between;
out[i].y_offset += dy as i32;
}
break;
}
}
}
}
fn apply_pos_records(out: &mut [ShapedGlyph], records: &[PosRecord]) {
for r in records {
if let Some(g) = out.get_mut(r.glyph_index) {
g.x_offset += r.value.x_placement as i32;
g.y_offset += r.value.y_placement as i32;
g.x_advance += r.value.x_advance as i32;
g.y_advance += r.value.y_advance as i32;
}
}
}