use std::ops::Range;
use read_fonts::{
FontData, FontRead, TableProvider,
model::{Font, Source, glyph::outline::PathSink, metrics::GlyphExtents},
tables::{
glyf::{
Glyph, PointFlags,
outline::{PathContourStart, outline_to_path},
},
loca::Loca,
},
types::{BigEndian, F26Dot6, GlyphId, Point},
};
use crate::glyph_outline::PathCollector;
const ON_STACK: usize = 64;
pub(crate) struct GlyfTables {
glyf: Range<usize>,
loca: Range<usize>,
long_loca: bool,
metrics: Range<usize>,
bearings: Range<usize>,
}
impl GlyfTables {
pub(crate) fn of(font: &Font) -> Option<Self> {
if font
.normalized_coords()
.iter()
.any(|coord| coord.to_bits() != 0)
{
return None;
}
let bytes = font_bytes(font)?;
let tables = font.tables();
if tables.varc().is_ok() {
return None;
}
let glyf = range_in(bytes, tables.glyf().ok()?.offset_data().as_bytes())?;
let (loca, long_loca) = match tables.loca(None).ok()? {
Loca::Short(offsets) => (range_in(bytes, offsets)?, false),
Loca::Long(offsets) => (range_in(bytes, offsets)?, true),
};
let (metrics, bearings) = tables
.hmtx()
.ok()
.and_then(|hmtx| {
Some((
range_in(bytes, hmtx.h_metrics())?,
range_in(bytes, hmtx.left_side_bearings())?,
))
})
.unwrap_or_default();
Some(Self {
glyf,
loca,
long_loca,
metrics,
bearings,
})
}
fn offset(&self, bytes: &[u8], index: usize) -> Option<usize> {
let loca = &bytes[self.loca.clone()];
if self.long_loca {
usize::try_from(u32::from_be_bytes(entry(loca, index)?)).ok()
} else {
Some(usize::from(u16::from_be_bytes(entry(loca, index)?)) * 2)
}
}
fn side_bearing(&self, bytes: &[u8], index: usize) -> Option<i16> {
let long = self.metrics.len() / 4;
let bearing = match index.checked_sub(long) {
None => {
let [_, _, high, low] = entry(&bytes[self.metrics.clone()], index)?;
[high, low]
}
Some(after) => entry(&bytes[self.bearings.clone()], after)?,
};
Some(i16::from_be_bytes(bearing))
}
}
#[inline(never)]
pub(crate) fn draw(
font: &Font,
tables: &GlyfTables,
glyph: GlyphId,
path: &mut PathCollector,
) -> Option<()> {
let bytes = font_bytes(font)?;
let index = usize::try_from(glyph.to_u32()).ok()?;
let (start, end) = (
tables.offset(bytes, index)?,
tables.offset(bytes, index.checked_add(1)?)?,
);
if start == end {
return Some(());
}
let data = bytes[tables.glyf.clone()].get(start..end)?;
let simple = match Glyph::read(FontData::new(data)).ok()? {
Glyph::Simple(simple) => simple,
Glyph::Composite(_) => return None,
};
let count = simple.num_points();
let mut stack_points = [Point::<F26Dot6>::default(); ON_STACK];
let mut stack_flags = [PointFlags::default(); ON_STACK];
let (mut heap_points, mut heap_flags);
let (points, flags) = if count <= ON_STACK {
(&mut stack_points[..count], &mut stack_flags[..count])
} else {
heap_points = vec![Point::default(); count];
heap_flags = vec![PointFlags::default(); count];
(&mut heap_points[..], &mut heap_flags[..])
};
simple.read_points_fast(points, flags).ok()?;
let bearing = tables.side_bearing(bytes, index).unwrap_or(0);
let shift = F26Dot6::from_i32(i32::from(simple.x_min()) - i32::from(bearing));
if shift != F26Dot6::ZERO {
for point in points.iter_mut() {
point.x -= shift;
}
}
let ends = simple.end_pts_of_contours();
let mut stack_contours = [0u16; ON_STACK];
let heap_contours: Vec<u16>;
let contours = if ends.len() <= ON_STACK {
let contours = &mut stack_contours[..ends.len()];
for (contour, end) in contours.iter_mut().zip(ends) {
*contour = end.get();
}
&*contours
} else {
heap_contours = ends.iter().map(BigEndian::get).collect();
&heap_contours
};
path.reserve(count + contours.len());
let mut sink: &mut dyn PathSink = path;
outline_to_path(
points,
flags,
contours,
PathContourStart::default(),
&mut sink,
)
.then_some(())
}
pub(crate) fn extents(
font: &Font,
tables: &GlyfTables,
glyph: GlyphId,
) -> Option<GlyphExtents<f32>> {
let bytes = font_bytes(font)?;
let index = usize::try_from(glyph.to_u32()).ok()?;
let (start, end) = (
tables.offset(bytes, index)?,
tables.offset(bytes, index.checked_add(1)?)?,
);
if start == end {
return Some(GlyphExtents::default());
}
let header: [u8; 10] = bytes[tables.glyf.clone()]
.get(start..start.checked_add(10)?)?
.try_into()
.ok()?;
let at = |offset: usize| f32::from(i16::from_be_bytes([header[offset], header[offset + 1]]));
let (x_min, y_min, x_max, y_max) = (at(2), at(4), at(6), at(8));
Some(GlyphExtents {
x_bearing: tables.side_bearing(bytes, index).map_or(x_min, f32::from),
y_bearing: y_max,
width: x_max - x_min,
height: y_max - y_min,
})
}
fn entry<const N: usize>(table: &[u8], index: usize) -> Option<[u8; N]> {
let at = index.checked_mul(N)?;
table.get(at..at.checked_add(N)?)?.try_into().ok()
}
fn font_bytes(font: &Font) -> Option<&[u8]> {
let Source::Blob(bytes) = font.source() else {
return None;
};
Some(bytes.as_ref())
}
fn range_in<T>(bytes: &[u8], part: &[T]) -> Option<Range<usize>> {
let start = part.as_ptr().addr().checked_sub(bytes.as_ptr().addr())?;
let end = start.checked_add(size_of_val(part))?;
bytes.get(start..end).map(|_| start..end)
}