use std::ops::Range;
use super::font::FaceEntry;
use super::sdf::{SdfCoverage, SDF_EM_PX};
pub(crate) struct ShapedGlyph {
pub font: usize,
pub glyph_id: u16,
pub x_advance: f32,
pub x_offset: f32,
pub y_offset: f32,
pub char_ix: usize,
pub scale: f32,
pub section: u16,
}
pub(crate) struct ShapeSection<'a> {
pub text: &'a str,
pub fonts: Range<usize>,
pub scale: f32,
}
pub(crate) struct ShapedText {
pub glyphs: Vec<ShapedGlyph>,
pub chars: Vec<char>,
pub covered: Vec<bool>,
pub dropped: usize,
pub missing_range: usize,
}
struct Run {
font: usize,
text: String,
char_start: usize,
}
pub(crate) fn shape_sections(
sections: &[ShapeSection<'_>],
fonts: &[FaceEntry<'_>],
letter_spacing_em: f32,
) -> ShapedText {
let mut glyphs = Vec::new();
let mut chars: Vec<char> = Vec::new();
let mut covered: Vec<bool> = Vec::new();
let mut dropped = 0usize;
let mut missing_range = 0usize;
for (sec_ix, sec) in sections.iter().enumerate() {
let base = sec.fonts.start;
let sub = &fonts[sec.fonts.clone()];
let it = itemize(sec.text, sub);
let char_base = chars.len();
chars.extend(it.chars);
covered.extend(it.covered);
dropped += it.dropped;
missing_range += it.missing_range;
let runs = it.runs;
for run in &runs {
shape_run(
run,
&fonts[base + run.font],
base,
sec.scale,
sec_ix as u16,
char_base,
letter_spacing_em,
&mut glyphs,
);
}
}
ShapedText {
glyphs,
chars,
covered,
dropped,
missing_range,
}
}
#[allow(clippy::too_many_arguments)]
fn shape_run(
run: &Run,
entry: &FaceEntry<'_>,
base: usize,
scale: f32,
section: u16,
char_base: usize,
letter_spacing_em: f32,
glyphs: &mut Vec<ShapedGlyph>,
) {
let font_ix = base + run.font;
match entry {
FaceEntry::Outline { font, face } => {
let units = 1.0 / font.units_per_em();
let char_of_byte: Vec<usize> = {
let mut map = vec![0usize; run.text.len() + 1];
for (char_ix, (byte_ix, _)) in run.text.char_indices().enumerate() {
map[byte_ix] = char_base + run.char_start + char_ix;
}
map
};
let mut buffer = rustybuzz::UnicodeBuffer::new();
buffer.push_str(&run.text);
let shaped = rustybuzz::shape(face, &[], buffer);
for (info, pos) in shaped
.glyph_infos()
.iter()
.zip(shaped.glyph_positions().iter())
{
glyphs.push(ShapedGlyph {
font: font_ix,
glyph_id: info.glyph_id as u16,
x_advance: pos.x_advance as f32 * units * scale + letter_spacing_em,
x_offset: pos.x_offset as f32 * units * scale,
y_offset: pos.y_offset as f32 * units * scale,
char_ix: char_of_byte[info.cluster as usize],
scale,
section,
});
}
}
FaceEntry::Sdf(stack) => {
for (char_ix, c) in run.text.chars().enumerate() {
let Some(glyph) = stack.glyph(c) else {
continue;
};
glyphs.push(ShapedGlyph {
font: font_ix,
glyph_id: c as u16,
x_advance: glyph.advance as f32 / SDF_EM_PX * scale + letter_spacing_em,
x_offset: 0.0,
y_offset: 0.0,
char_ix: char_base + run.char_start + char_ix,
scale,
section,
});
}
}
}
}
struct Itemized {
runs: Vec<Run>,
chars: Vec<char>,
covered: Vec<bool>,
dropped: usize,
missing_range: usize,
}
fn itemize(text: &str, fonts: &[FaceEntry<'_>]) -> Itemized {
let mut runs: Vec<Run> = Vec::new();
let mut chars: Vec<char> = Vec::new();
let mut covered: Vec<bool> = Vec::new();
let mut dropped = 0usize;
let mut missing_range = 0usize;
let mut prev_font: Option<usize> = None;
let mut run_end = 0usize;
for c in text.chars() {
let first_covering = fonts.iter().position(|f| f.covers(c));
let picked = if is_combining_mark(c) {
match prev_font {
Some(p) if fonts[p].covers(c) => Some(p),
_ => first_covering,
}
} else {
first_covering
};
let Some(font) = picked else {
dropped += 1;
if fonts.iter().any(|f| {
matches!(f, FaceEntry::Sdf(s) if s.coverage(c) == SdfCoverage::RangeUnavailable)
}) {
missing_range += 1;
}
chars.push(c);
covered.push(false);
prev_font = None;
continue;
};
match runs.last_mut() {
Some(run) if run.font == font && run_end == chars.len() => run.text.push(c),
_ => runs.push(Run {
font,
text: c.to_string(),
char_start: chars.len(),
}),
}
chars.push(c);
covered.push(true);
run_end = chars.len();
prev_font = Some(font);
}
Itemized {
runs,
chars,
covered,
dropped,
missing_range,
}
}
fn is_combining_mark(c: char) -> bool {
matches!(
c as u32,
0x0300..=0x036F | 0x1AB0..=0x1AFF | 0x1DC0..=0x1DFF | 0x20D0..=0x20FF | 0xFE20..=0xFE2F )
}