mod blues;
mod scale;
mod widths;
use super::{
super::Target,
shape::{Shaper, ShaperMode},
style::{GlyphStyleMap, ScriptGroup, StyleClass},
topo::Dimension,
};
use crate::{attribute::Style, collections::SmallVec, FontRef};
use alloc::vec::Vec;
use raw::types::{F2Dot14, Fixed, GlyphId};
#[cfg(feature = "std")]
use std::sync::{Arc, RwLock};
pub(crate) use blues::{BlueZones, ScaledBlue, ScaledBlues, UnscaledBlue, UnscaledBlues};
pub(crate) use scale::{compute_unscaled_style_metrics, scale_style_metrics};
pub(crate) const MAX_WIDTHS: usize = 16;
#[derive(Clone, Default, Debug)]
pub(crate) struct UnscaledAxisMetrics {
pub dim: Dimension,
pub widths: UnscaledWidths,
pub width_metrics: WidthMetrics,
pub blues: UnscaledBlues,
}
impl UnscaledAxisMetrics {
pub fn max_width(&self) -> Option<i32> {
self.widths.last().copied()
}
}
#[derive(Clone, Default, Debug)]
pub(crate) struct ScaledAxisMetrics {
pub dim: Dimension,
pub scale: i32,
pub delta: i32,
pub widths: ScaledWidths,
pub width_metrics: WidthMetrics,
pub blues: ScaledBlues,
}
#[derive(Clone, Default, Debug)]
pub(crate) struct UnscaledStyleMetrics {
pub class_ix: u16,
pub digits_have_same_width: bool,
pub axes: [UnscaledAxisMetrics; 2],
}
impl UnscaledStyleMetrics {
pub fn style_class(&self) -> &'static StyleClass {
&super::style::STYLE_CLASSES[self.class_ix as usize]
}
}
#[derive(Clone, Debug)]
pub(crate) enum UnscaledStyleMetricsSet {
Precomputed(Vec<UnscaledStyleMetrics>),
#[cfg(feature = "std")]
Lazy(Arc<RwLock<Vec<Option<UnscaledStyleMetrics>>>>),
}
impl UnscaledStyleMetricsSet {
pub fn precomputed(
font: &FontRef,
coords: &[F2Dot14],
shaper_mode: ShaperMode,
style_map: &GlyphStyleMap,
) -> Self {
let shaper = Shaper::new(font, shaper_mode);
let mut vec = Vec::with_capacity(style_map.metrics_count());
vec.extend(
style_map
.metrics_styles()
.map(|style| compute_unscaled_style_metrics(&shaper, coords, style)),
);
Self::Precomputed(vec)
}
#[cfg(feature = "std")]
pub fn lazy(style_map: &GlyphStyleMap) -> Self {
let vec = vec![None; style_map.metrics_count()];
Self::Lazy(Arc::new(RwLock::new(vec)))
}
pub fn get(
&self,
font: &FontRef,
coords: &[F2Dot14],
shaper_mode: ShaperMode,
style_map: &GlyphStyleMap,
glyph_id: GlyphId,
) -> Option<UnscaledStyleMetrics> {
let style = style_map.style(glyph_id)?;
let index = style_map.metrics_index(style)?;
match self {
Self::Precomputed(metrics) => metrics.get(index).cloned(),
#[cfg(feature = "std")]
Self::Lazy(lazy) => {
let read = lazy.read().unwrap();
let entry = read.get(index)?;
if let Some(metrics) = &entry {
return Some(metrics.clone());
}
core::mem::drop(read);
let shaper = Shaper::new(font, shaper_mode);
let style_class = style.style_class()?;
let metrics = compute_unscaled_style_metrics(&shaper, coords, style_class);
let mut entry = lazy.write().unwrap();
*entry.get_mut(index)? = Some(metrics.clone());
Some(metrics)
}
}
}
}
#[derive(Clone, Default, Debug)]
pub(crate) struct ScaledStyleMetrics {
pub scale: Scale,
pub axes: [ScaledAxisMetrics; 2],
}
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub(crate) struct WidthMetrics {
pub edge_distance_threshold: i32,
pub standard_width: i32,
pub is_extra_light: bool,
}
pub(crate) type UnscaledWidths = SmallVec<i32, MAX_WIDTHS>;
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub(crate) struct ScaledWidth {
pub scaled: i32,
pub fitted: i32,
}
pub(crate) type ScaledWidths = SmallVec<ScaledWidth, MAX_WIDTHS>;
#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct Scale {
pub x_scale: i32,
pub y_scale: i32,
pub x_delta: i32,
pub y_delta: i32,
pub size: f32,
pub units_per_em: i32,
pub flags: u32,
}
impl Scale {
pub fn new(
size: f32,
units_per_em: i32,
font_style: Style,
target: Target,
group: ScriptGroup,
) -> Self {
let scale =
(Fixed::from_bits((size * 64.0) as i32) / Fixed::from_bits(units_per_em)).to_bits();
let mut flags = 0;
let is_italic = font_style != Style::Normal;
let is_mono = target == Target::Mono;
let is_light = target.is_light() || target.preserve_linear_metrics();
if is_mono || target.is_lcd() {
flags |= Self::HORIZONTAL_SNAP;
}
if is_mono || target.is_vertical_lcd() {
flags |= Self::VERTICAL_SNAP;
}
if !(target.is_lcd() || is_light) {
flags |= Self::STEM_ADJUST;
}
if is_mono {
flags |= Self::MONO;
}
if group == ScriptGroup::Default {
if target.is_lcd() || is_light || is_italic {
flags |= Self::NO_HORIZONTAL;
}
} else {
flags |= Self::NO_ADVANCE;
}
if group != ScriptGroup::Default {
flags |= Self::NO_ADVANCE;
}
Self {
x_scale: scale,
y_scale: scale,
x_delta: 0,
y_delta: 0,
size,
units_per_em,
flags,
}
}
}
impl Scale {
pub const HORIZONTAL_SNAP: u32 = 1 << 0;
pub const VERTICAL_SNAP: u32 = 1 << 1;
pub const STEM_ADJUST: u32 = 1 << 2;
pub const MONO: u32 = 1 << 3;
pub const NO_HORIZONTAL: u32 = 1 << 4;
pub const NO_VERTICAL: u32 = 1 << 5;
pub const NO_ADVANCE: u32 = 1 << 6;
}
pub(crate) fn sort_and_quantize_widths(widths: &mut UnscaledWidths, threshold: i32) {
if widths.len() <= 1 {
return;
}
widths.sort_unstable();
let table = widths.as_mut_slice();
let mut cur_ix = 0;
let mut cur_val = table[cur_ix];
let last_ix = table.len() - 1;
let mut ix = 1;
while ix < table.len() {
if (table[ix] - cur_val) > threshold || ix == last_ix {
let mut sum = 0;
if (table[ix] - cur_val <= threshold) && ix == last_ix {
ix += 1;
}
for val in &mut table[cur_ix..ix] {
sum += *val;
*val = 0;
}
table[cur_ix] = sum / ix as i32;
if ix < last_ix {
cur_ix = ix + 1;
cur_val = table[cur_ix];
}
}
ix += 1;
}
cur_ix = 1;
for ix in 1..table.len() {
if table[ix] != 0 {
table[cur_ix] = table[ix];
cur_ix += 1;
}
}
widths.truncate(cur_ix);
}
pub(crate) fn fixed_mul(a: i32, b: i32) -> i32 {
(Fixed::from_bits(a) * Fixed::from_bits(b)).to_bits()
}
pub(crate) fn fixed_div(a: i32, b: i32) -> i32 {
(Fixed::from_bits(a) / Fixed::from_bits(b)).to_bits()
}
pub(crate) fn fixed_mul_div(a: i32, b: i32, c: i32) -> i32 {
Fixed::from_bits(a)
.mul_div(Fixed::from_bits(b), Fixed::from_bits(c))
.to_bits()
}
pub(crate) fn pix_round(a: i32) -> i32 {
(a + 32) & !63
}
pub(crate) fn pix_floor(a: i32) -> i32 {
a & !63
}
#[cfg(test)]
mod tests {
use super::{
super::{
shape::{Shaper, ShaperMode},
style::STYLE_CLASSES,
},
*,
};
use raw::TableProvider;
#[test]
fn sort_widths() {
assert_eq!(sort_widths_helper(&[1], 10), &[1]);
assert_eq!(sort_widths_helper(&[1], 20), &[1]);
assert_eq!(sort_widths_helper(&[60, 20, 40, 35], 10), &[20, 35, 13, 60]);
assert_eq!(sort_widths_helper(&[60, 20, 40, 35], 20), &[31, 60]);
}
fn sort_widths_helper(widths: &[i32], threshold: i32) -> Vec<i32> {
let mut widths2 = UnscaledWidths::new();
for width in widths {
widths2.push(*width);
}
sort_and_quantize_widths(&mut widths2, threshold);
widths2.into_iter().collect()
}
#[test]
fn precomputed_style_set() {
let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
let coords = &[];
let shaper = Shaper::new(&font, ShaperMode::Nominal);
let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
let style_map = GlyphStyleMap::new(glyph_count, &shaper);
let style_set =
UnscaledStyleMetricsSet::precomputed(&font, coords, ShaperMode::Nominal, &style_map);
let UnscaledStyleMetricsSet::Precomputed(set) = &style_set else {
panic!("we definitely made a precomputed style set");
};
assert_eq!(STYLE_CLASSES[set[0].class_ix as usize].name, "Latin");
assert_eq!(STYLE_CLASSES[set[1].class_ix as usize].name, "Hebrew");
assert_eq!(
STYLE_CLASSES[set[2].class_ix as usize].name,
"CJKV ideographs"
);
assert_eq!(set.len(), 3);
}
#[test]
fn lazy_style_set() {
let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
let coords = &[];
let shaper = Shaper::new(&font, ShaperMode::Nominal);
let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
let style_map = GlyphStyleMap::new(glyph_count, &shaper);
let style_set = UnscaledStyleMetricsSet::lazy(&style_map);
let all_empty = lazy_set_presence(&style_set);
assert_eq!(all_empty, [false; 3]);
let metrics2 = style_set
.get(
&font,
coords,
ShaperMode::Nominal,
&style_map,
GlyphId::new(0),
)
.unwrap();
assert_eq!(
STYLE_CLASSES[metrics2.class_ix as usize].name,
"CJKV ideographs"
);
let only_cjk = lazy_set_presence(&style_set);
assert_eq!(only_cjk, [false, false, true]);
let metrics1 = style_set
.get(
&font,
coords,
ShaperMode::Nominal,
&style_map,
GlyphId::new(1),
)
.unwrap();
assert_eq!(STYLE_CLASSES[metrics1.class_ix as usize].name, "Hebrew");
let hebrew_and_cjk = lazy_set_presence(&style_set);
assert_eq!(hebrew_and_cjk, [false, true, true]);
let metrics0 = style_set
.get(
&font,
coords,
ShaperMode::Nominal,
&style_map,
GlyphId::new(15),
)
.unwrap();
assert_eq!(STYLE_CLASSES[metrics0.class_ix as usize].name, "Latin");
let all_present = lazy_set_presence(&style_set);
assert_eq!(all_present, [true; 3]);
}
fn lazy_set_presence(style_set: &UnscaledStyleMetricsSet) -> Vec<bool> {
let UnscaledStyleMetricsSet::Lazy(set) = &style_set else {
panic!("we definitely made a lazy style set");
};
set.read()
.unwrap()
.iter()
.map(|opt| opt.is_some())
.collect()
}
}