use oxitext_core::ShapedGlyph;
use std::sync::Arc;
pub trait ShapeBackend: Send + Sync {
fn shape(&self, face_data: &Arc<[u8]>, text: &str, px_size: f32) -> Vec<ShapedGlyph>;
fn shape_with_direction(
&self,
face_data: &Arc<[u8]>,
text: &str,
px_size: f32,
rtl: bool,
) -> Vec<ShapedGlyph> {
if !rtl {
return self.shape(face_data, text, px_size);
}
let mut glyphs = self.shape(face_data, text, px_size);
glyphs.sort_by_key(|g| g.cluster);
glyphs
}
fn shape_with_features(
&self,
face_data: &Arc<[u8]>,
text: &str,
px_size: f32,
features: &[crate::ShapeFeature],
) -> Vec<ShapedGlyph> {
let _ = features;
self.shape(face_data, text, px_size)
}
fn shape_with_options(
&self,
face_data: &Arc<[u8]>,
text: &str,
px_size: f32,
rtl: bool,
features: &[crate::ShapeFeature],
_options: &crate::ShapeRequest<'_>,
) -> Vec<ShapedGlyph> {
if features.is_empty() {
self.shape_with_direction(face_data, text, px_size, rtl)
} else {
let mut glyphs = self.shape_with_features(face_data, text, px_size, features);
if rtl {
glyphs.sort_by_key(|g| g.cluster);
}
glyphs
}
}
fn supports_script(&self, font_data: &Arc<[u8]>, script: [u8; 4]) -> bool {
fn sentinel_char(script: [u8; 4]) -> Option<char> {
match &script {
b"latn" => Some('A'),
b"arab" => Some('\u{0627}'), b"hani" => Some('\u{4E00}'), b"cyrl" => Some('\u{0410}'), b"grek" => Some('\u{0391}'), b"hebr" => Some('\u{05D0}'), b"deva" => Some('\u{0905}'), b"thai" => Some('\u{0E01}'), _ => None,
}
}
let Some(ch) = sentinel_char(script) else {
return true;
};
ttf_parser::Face::parse(font_data.as_ref(), 0)
.map(|face| face.glyph_index(ch).is_some())
.unwrap_or(true) }
}
pub struct SwashShaperBackend {
inner: std::sync::Arc<std::sync::RwLock<crate::SwashShaper>>,
}
impl SwashShaperBackend {
pub fn new() -> Self {
Self {
inner: std::sync::Arc::new(std::sync::RwLock::new(crate::SwashShaper::new())),
}
}
}
impl Default for SwashShaperBackend {
fn default() -> Self {
Self::new()
}
}
impl ShapeBackend for SwashShaperBackend {
fn shape(&self, face_data: &Arc<[u8]>, text: &str, px_size: f32) -> Vec<ShapedGlyph> {
let mut guard = match self.inner.write() {
Ok(g) => g,
Err(_) => return Vec::new(),
};
match guard.shape(text, Arc::clone(face_data), px_size) {
Ok(run) => run.glyphs.into_vec(),
Err(_) => Vec::new(),
}
}
fn shape_with_direction(
&self,
face_data: &Arc<[u8]>,
text: &str,
px_size: f32,
rtl: bool,
) -> Vec<ShapedGlyph> {
let mut guard = match self.inner.write() {
Ok(g) => g,
Err(_) => return Vec::new(),
};
match guard.shape_with_direction(text, Arc::clone(face_data), px_size, rtl) {
Ok(run) => run.glyphs.into_vec(),
Err(_) => Vec::new(),
}
}
}
#[cfg(feature = "rustybuzz-backend")]
pub struct RustybuzzShaper;
#[cfg(feature = "rustybuzz-backend")]
impl Default for RustybuzzShaper {
fn default() -> Self {
Self
}
}
#[cfg(feature = "rustybuzz-backend")]
impl RustybuzzShaper {
fn shape_internal(
&self,
face_data: &Arc<[u8]>,
text: &str,
px_size: f32,
direction: rustybuzz::Direction,
) -> Vec<ShapedGlyph> {
use rustybuzz::{Face, UnicodeBuffer};
let face = match Face::from_slice(face_data.as_ref(), 0) {
Some(f) => f,
None => return Vec::new(),
};
let upem = face.units_per_em() as f32;
let scale = if upem > 0.0 { px_size / upem } else { 1.0 };
let mut buf = UnicodeBuffer::new();
buf.push_str(text);
buf.set_direction(direction);
let shaped = rustybuzz::shape(&face, &[], buf);
let infos = shaped.glyph_infos();
let positions = shaped.glyph_positions();
let mut glyphs: Vec<ShapedGlyph> = infos
.iter()
.zip(positions.iter())
.map(|(info, pos)| {
let is_ws = text
.get(info.cluster as usize..)
.and_then(|s| s.chars().next())
.map(|c| c.is_whitespace())
.unwrap_or(false);
ShapedGlyph {
gid: info.glyph_id as u16,
cluster: info.cluster,
x_advance: pos.x_advance as f32 * scale,
y_advance: pos.y_advance as f32 * scale,
x_offset: pos.x_offset as f32 * scale,
y_offset: pos.y_offset as f32 * scale,
is_whitespace: is_ws,
unsafe_to_break: false,
}
})
.collect();
glyphs.sort_by_key(|g| g.cluster);
glyphs
}
}
#[cfg(feature = "rustybuzz-backend")]
impl ShapeBackend for RustybuzzShaper {
fn shape(&self, face_data: &Arc<[u8]>, text: &str, px_size: f32) -> Vec<ShapedGlyph> {
self.shape_internal(face_data, text, px_size, rustybuzz::Direction::LeftToRight)
}
fn shape_with_direction(
&self,
face_data: &Arc<[u8]>,
text: &str,
px_size: f32,
rtl: bool,
) -> Vec<ShapedGlyph> {
let direction = if rtl {
rustybuzz::Direction::RightToLeft
} else {
rustybuzz::Direction::LeftToRight
};
self.shape_internal(face_data, text, px_size, direction)
}
}