use crate::face::Face;
use crate::face_chain::FaceChain;
use crate::Error;
use oxideav_core::{Node, Transform2D};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PositionedGlyph {
pub glyph_id: u16,
pub x_offset: f32,
pub y_offset: f32,
pub x_advance: f32,
pub face_idx: u16,
}
#[derive(Debug)]
pub struct Shaper;
impl Shaper {
pub fn shape(face: &Face, text: &str, size_px: f32) -> Result<Vec<PositionedGlyph>, Error> {
if text.is_empty() || size_px <= 0.0 {
return Ok(Vec::new());
}
face.with_font(|font| shape_with_font(font, text, size_px))
}
pub fn shape_to_paths(
face_chain: &FaceChain,
text: &str,
size_px: f32,
) -> Vec<(usize, Node, Transform2D)> {
if text.is_empty() || size_px <= 0.0 || !size_px.is_finite() {
return Vec::new();
}
let glyphs = match face_chain.shape(text, size_px) {
Ok(g) => g,
Err(_) => return Vec::new(),
};
let mut out: Vec<(usize, Node, Transform2D)> = Vec::with_capacity(glyphs.len());
let mut pen_x = 0.0_f32;
for g in &glyphs {
let face_idx = g.face_idx as usize;
let face = face_chain.face(g.face_idx);
let target_x = pen_x + g.x_offset;
pen_x += g.x_advance;
let node = match face.glyph_node(g.glyph_id, size_px) {
Some(n) => n,
None => continue,
};
let ty = g.y_offset;
out.push((face_idx, node, Transform2D::translate(target_x, ty)));
}
out
}
}
fn shape_with_font(font: &oxideav_ttf::Font<'_>, text: &str, size_px: f32) -> Vec<PositionedGlyph> {
let raw_glyphs: Vec<u16> = text
.chars()
.map(|ch| font.glyph_index(ch).unwrap_or(0))
.collect();
shape_run_with_font(font, &raw_glyphs, size_px, 0)
}
pub fn shape_run_with_font(
font: &oxideav_ttf::Font<'_>,
raw_glyphs: &[u16],
size_px: f32,
face_idx: u16,
) -> Vec<PositionedGlyph> {
let upem = font.units_per_em().max(1) as f32;
let scale = size_px / upem;
let mut shaped_gids: Vec<u16> = Vec::with_capacity(raw_glyphs.len());
let mut i = 0;
while i < raw_glyphs.len() {
if let Some((replacement, count)) = font.lookup_ligature(&raw_glyphs[i..]) {
if count >= 2 {
shaped_gids.push(replacement);
i += count;
continue;
}
}
shaped_gids.push(raw_glyphs[i]);
i += 1;
}
let mut out: Vec<PositionedGlyph> = Vec::with_capacity(shaped_gids.len());
for (idx, &gid) in shaped_gids.iter().enumerate() {
let advance_units = font.glyph_advance(gid) as f32;
let x_advance = advance_units * scale;
let mut x_offset = 0.0_f32;
if idx > 0 {
let prev = shaped_gids[idx - 1];
let kern_units = font.lookup_kerning(prev, gid) as f32;
x_offset = kern_units * scale;
}
out.push(PositionedGlyph {
glyph_id: gid,
x_offset,
y_offset: 0.0,
x_advance,
face_idx,
});
}
for i in 1..out.len() {
let mark_gid = out[i].glyph_id;
if !font.is_mark_glyph(mark_gid) {
continue;
}
let prev = i - 1;
let prev_gid = out[prev].glyph_id;
if font.is_mark_glyph(prev_gid) {
if let Some((dx_units, dy_units)) = font.lookup_mark_to_mark(prev_gid, mark_gid) {
let dx = dx_units as f32 * scale;
let dy = dy_units as f32 * scale;
out[i].x_offset = out[prev].x_offset + dx - out[i].x_advance;
out[i].y_offset = out[prev].y_offset - dy;
out[i].x_advance = 0.0;
continue;
}
}
let mut j = i;
while j > 0 {
j -= 1;
if !font.is_mark_glyph(out[j].glyph_id) {
break;
}
}
if font.is_mark_glyph(out[j].glyph_id) {
continue;
}
let base_gid = out[j].glyph_id;
if let Some((dx_units, dy_units)) = font.lookup_mark_to_base(base_gid, mark_gid) {
let dx = dx_units as f32 * scale;
let dy = dy_units as f32 * scale;
let mut intervening_advance = 0.0_f32;
for k in (j + 1)..=i {
intervening_advance += out[k].x_advance;
}
out[i].x_offset += dx - intervening_advance;
out[i].y_offset -= dy;
out[i].x_advance = 0.0;
}
}
out
}