use crate::conv::{DPU, to_u32, to_usize};
use crate::display::RunSpecial;
use crate::fonts::{self, FaceId};
use crate::{Range, Vec2};
use icu_properties::props::Script;
use tinyvec::TinyVec;
use unicode_bidi::Level;
#[repr(transparent)]
#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default, Debug)]
pub struct GlyphId(pub u16);
#[derive(Clone, Copy, Debug)]
pub struct Glyph {
pub index: u32,
pub id: GlyphId,
pub position: Vec2,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct GlyphBreak {
pub index: u32,
pub gi: u32,
pub no_space_end: f32,
}
impl GlyphBreak {
pub(crate) fn new(index: u32) -> Self {
GlyphBreak {
index,
gi: u32::MAX,
no_space_end: f32::NAN,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct PartMetrics {
pub offset: f32,
pub len_no_space: f32,
pub len: f32,
}
#[derive(Clone, Debug)]
pub(crate) struct GlyphRun {
pub range: Range,
pub dpem: f32,
pub dpu: DPU,
pub face_id: FaceId,
pub special: RunSpecial,
pub level: Level,
pub script: Script,
pub glyphs: Vec<Glyph>,
pub breaks: TinyVec<[GlyphBreak; 4]>,
pub no_space_end: f32,
pub caret: f32,
}
impl GlyphRun {
pub fn num_parts(&self) -> usize {
self.breaks.len() + 1
}
pub fn part_lengths(&self, range: std::ops::Range<usize>) -> PartMetrics {
assert!(range.start <= range.end);
let mut part = PartMetrics::default();
if self.level.is_ltr() {
if range.end > 0 {
part.len_no_space = self.no_space_end;
part.len = self.caret;
if range.end <= self.breaks.len() {
let b = self.breaks[range.end - 1];
part.len_no_space = b.no_space_end;
if to_usize(b.gi) < self.glyphs.len() {
part.len = self.glyphs[to_usize(b.gi)].position.0
}
}
}
if range.start > 0 {
let glyph = to_usize(self.breaks[range.start - 1].gi);
part.offset = self.glyphs[glyph].position.0;
part.len_no_space -= part.offset;
part.len -= part.offset;
}
} else {
if range.start <= self.breaks.len() {
part.len = self.caret;
if range.start > 0 {
let b = self.breaks.len() - range.start;
let gi = to_usize(self.breaks[b].gi);
if gi < self.glyphs.len() {
part.len = self.glyphs[gi].position.0;
}
}
part.len_no_space = part.len;
}
if range.end <= self.breaks.len() {
part.offset = self.caret;
if range.end == 0 {
part.len_no_space = 0.0;
} else {
let b = self.breaks.len() - range.end;
let b = self.breaks[b];
part.len_no_space -= b.no_space_end;
if to_usize(b.gi) < self.glyphs.len() {
part.offset = self.glyphs[to_usize(b.gi)].position.0;
}
}
part.len -= part.offset;
}
}
part
}
pub fn to_glyph_range(&self, range: std::ops::Range<usize>) -> Range {
let mut start = range.start;
let mut end = range.end;
let rtl = self.level.is_rtl();
if rtl {
let num_parts = self.num_parts();
start = num_parts - start;
end = num_parts - end;
}
let map = |part: usize| {
if part == 0 {
0
} else if part <= self.breaks.len() {
to_usize(self.breaks[part - 1].gi)
} else {
debug_assert_eq!(part, self.breaks.len() + 1);
self.glyphs.len()
}
};
let mut start = map(start);
let mut end = map(end);
if rtl {
std::mem::swap(&mut start, &mut end);
}
Range::from(start..end)
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Input<'a> {
pub text: &'a str,
pub dpem: f32,
pub level: Level,
pub script: Script,
}
pub(crate) fn shape(
input: Input,
range: Range, face_id: FaceId,
mut breaks: TinyVec<[GlyphBreak; 4]>,
special: RunSpecial,
) -> GlyphRun {
if input.level.is_rtl() {
breaks.reverse();
}
let mut glyphs = vec![];
let mut no_space_end = 0.0;
let mut caret = 0.0;
let face = fonts::library().get_face(face_id);
let dpu = face.dpu(input.dpem);
let sf = face.scale_by_dpu(dpu);
if input.dpem >= 0.0 {
#[cfg(feature = "rustybuzz")]
let r = shape_rustybuzz(input, range, face_id, &mut breaks);
#[cfg(not(feature = "rustybuzz"))]
let r = shape_simple(sf, input, range, &mut breaks);
glyphs = r.0;
no_space_end = r.1;
caret = r.2;
}
if input.level.is_rtl() {
let mut break_i = breaks.len().wrapping_sub(1);
let mut start_no_space = caret;
let mut last_id = None;
let side_bearing = |id: Option<GlyphId>| id.map(|id| sf.h_side_bearing(id)).unwrap_or(0.0);
for (gi, glyph) in glyphs.iter().enumerate().rev() {
if let Some(b) = breaks.get_mut(break_i)
&& to_usize(b.gi) == gi
{
assert!(gi < glyphs.len());
b.gi = to_u32(gi) + 1;
b.no_space_end = start_no_space - side_bearing(last_id);
break_i = break_i.wrapping_sub(1);
}
if !input.text[to_usize(glyph.index)..]
.chars()
.next()
.map(|c| c.is_whitespace())
.unwrap_or(true)
{
last_id = Some(glyph.id);
start_no_space = glyph.position.0;
}
}
no_space_end = start_no_space - side_bearing(last_id);
}
GlyphRun {
range,
dpem: input.dpem,
dpu,
face_id,
special,
level: input.level,
script: input.script.into(),
glyphs,
breaks,
no_space_end,
caret,
}
}
#[cfg(feature = "rustybuzz")]
fn shape_rustybuzz(
input: Input<'_>,
range: Range,
face_id: FaceId,
breaks: &mut [GlyphBreak],
) -> (Vec<Glyph>, f32, f32) {
let Input {
text,
dpem,
level,
script,
} = input;
let fonts = fonts::library();
let store = fonts.get_face_store(face_id);
let dpu = store.face_ref().dpu(dpem);
let face = store.rustybuzz();
let slice = &text[range];
let idx_offset = range.start;
let rtl = level.is_rtl();
let mut buffer = rustybuzz::UnicodeBuffer::new();
buffer.set_direction(match rtl {
false => rustybuzz::Direction::LeftToRight,
true => rustybuzz::Direction::RightToLeft,
});
buffer.push_str(slice);
let script: fontique::Script = script.into();
let tag = ttf_parser::Tag(u32::from_be_bytes(script.0));
if let Some(script) = rustybuzz::Script::from_iso15924_tag(tag) {
buffer.set_script(script);
}
let features = [];
let output = rustybuzz::shape(face, &features, buffer);
let mut caret = 0.0;
let mut no_space_end = caret;
let mut break_i = 0;
let mut glyphs = Vec::with_capacity(output.len());
for (info, pos) in output
.glyph_infos()
.iter()
.zip(output.glyph_positions().iter())
{
let index = idx_offset + info.cluster;
assert!(info.glyph_id <= u16::MAX as u32, "failed to map glyph id");
let id = GlyphId(info.glyph_id as u16);
if breaks
.get(break_i)
.map(|b| b.index == index)
.unwrap_or(false)
{
breaks[break_i].gi = to_u32(glyphs.len());
breaks[break_i].no_space_end = no_space_end;
break_i += 1;
}
let position = Vec2(
caret + dpu.i32_to_px(pos.x_offset),
dpu.i32_to_px(pos.y_offset),
);
glyphs.push(Glyph {
index,
id,
position,
});
debug_assert_eq!(pos.y_advance, 0);
caret += dpu.i32_to_px(pos.x_advance);
if text[to_usize(index)..]
.chars()
.next()
.map(|c| !c.is_whitespace())
.unwrap()
{
no_space_end = caret;
}
}
(glyphs, no_space_end, caret)
}
#[cfg(not(feature = "rustybuzz"))]
fn shape_simple(
sf: crate::fonts::ScaledFaceRef,
input: Input<'_>,
range: Range,
breaks: &mut [GlyphBreak],
) -> (Vec<Glyph>, f32, f32) {
let Input { text, level, .. } = input;
use unicode_bidi_mirroring::get_mirrored;
let slice = &text[range];
let idx_offset = range.start;
let rtl = level.is_rtl();
let mut caret = 0.0;
let mut no_space_end = caret;
let mut prev_glyph_id: Option<GlyphId> = None;
let mut break_i = 0;
let mut glyphs = Vec::with_capacity(slice.len());
let mut iter = slice.char_indices();
let mut next_char_index = || match rtl {
false => iter.next(),
true => iter.next_back(),
};
while let Some((index, mut c)) = next_char_index() {
let index = idx_offset + to_u32(index);
if rtl && let Some(m) = get_mirrored(c) {
c = m;
}
let id = sf.face().glyph_index(c);
if breaks
.get(break_i)
.map(|b| b.index == index)
.unwrap_or(false)
{
breaks[break_i].gi = to_u32(glyphs.len());
breaks[break_i].no_space_end = no_space_end;
break_i += 1;
no_space_end = caret;
}
if let Some(prev) = prev_glyph_id
&& let Some(kern) = sf.face().0.tables().kern
&& let Some(adv) = kern
.subtables
.into_iter()
.filter(|st| st.horizontal && !st.variable)
.find_map(|st| st.glyphs_kerning(prev.into(), id.into()))
{
caret += sf.dpu().i16_to_px(adv);
}
prev_glyph_id = Some(id);
let position = Vec2(caret, 0.0);
let glyph = Glyph {
index,
id,
position,
};
glyphs.push(glyph);
caret += sf.h_advance(id);
if !c.is_whitespace() {
no_space_end = caret;
}
}
glyphs.shrink_to_fit();
(glyphs, no_space_end, caret)
}