use crate::{
layout_input::LayoutInput,
shaped_glyphs::ShapedGlyph,
shaped_item::{ShapedItem, scale},
text_layout::font_units,
typeface::Typeface,
};
#[derive(Clone, Debug, Default)]
pub(crate) struct ClusterWidths {
clusters: Vec<(u32, f32)>,
total: ClusterTotal,
dense: bool,
}
#[derive(Clone, Debug, Default)]
struct ClusterTotal {
last: Option<u32>,
width: f32,
}
impl ClusterTotal {
fn add(&mut self, start: u32, advance: f32, spacing: f32) -> bool {
let starts = self.last != Some(start);
if starts {
self.last = Some(start);
self.width += spacing;
}
self.width += advance;
starts
}
}
impl ClusterWidths {
pub(crate) fn measure(
&mut self,
items: &[ShapedItem],
input: &LayoutInput<'_>,
faces: &[Typeface],
glyphs: &[ShapedGlyph],
) {
self.clusters.clear();
self.total = ClusterTotal::default();
each_cluster_glyph(items, input, faces, glyphs, |start, advance, spacing| {
let before = self.total.width;
if self.total.add(start, advance, spacing) {
self.clusters.push((start, before));
}
});
self.dense = input.text.len() == self.clusters.len();
self.clusters
.push((held(input.text.len()), self.total.width));
}
pub(crate) fn width(&self, start: usize, end: usize) -> f32 {
self.before(end) - self.before(start)
}
pub(crate) fn before(&self, offset: usize) -> f32 {
if self.dense
&& let Some(&(_, width)) = self.clusters.get(offset)
{
return width;
}
let offset = held(offset);
let at = self.clusters.partition_point(|&(start, _)| start < offset);
self.clusters
.get(at)
.map_or(self.total.width, |&(_, width)| width)
}
pub(crate) fn last_fitting(&self, start: usize, end: usize, edge: f32, width: f32) -> usize {
let before_start = self.before(start);
let last = held(end);
let fits = self.clusters.partition_point(|&(cluster, before)| {
cluster <= last && before - before_start + edge <= width
});
self.clusters[..fits]
.last()
.map_or(start, |&(cluster, _)| offset(cluster).max(start))
}
pub(crate) fn first_fitting(&self, start: usize, end: usize, width: f32) -> usize {
let limit = self.before(end) - width;
let first_start = held(start);
let first = self
.clusters
.partition_point(|&(cluster, before)| cluster < first_start || before < limit);
self.clusters
.get(first)
.map_or(end, |&(cluster, _)| offset(cluster).min(end))
}
pub(crate) fn next_boundary(&self, offset: usize) -> Option<usize> {
let after = held(offset);
let at = self.clusters.partition_point(|&(start, _)| start <= after);
self.clusters.get(at).map(|&(start, _)| self::offset(start))
}
}
fn held(offset: usize) -> u32 {
u32::try_from(offset).unwrap_or(u32::MAX)
}
fn offset(held: u32) -> usize {
usize::try_from(held).unwrap_or(usize::MAX)
}
fn each_cluster_glyph(
items: &[ShapedItem],
input: &LayoutInput<'_>,
faces: &[Typeface],
glyphs: &[ShapedGlyph],
mut add: impl FnMut(u32, f32, f32),
) {
for item in items {
let scale = scale(&input.spans[item.span], &faces[item.face]);
let glyphs = &glyphs[item.glyphs.clone()];
let mut add_glyph = |glyph: &ShapedGlyph| {
add(
glyph.cluster,
font_units(glyph.advance) * scale,
item.spacing,
);
};
if item.level.is_rtl() {
glyphs.iter().rev().for_each(&mut add_glyph);
} else {
glyphs.iter().for_each(&mut add_glyph);
}
}
}