mod glyf;
use std::sync::Arc;
use write_fonts::read::tables::cmap::PlatformId;
use write_fonts::read::tables::os2::{Os2, SelectionFlags};
use write_fonts::read::TableProvider;
pub(crate) use glyf::{OutlineBuilder, Rect};
pub(crate) use harfrust::{Buffer, Direction, Feature, Tag};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct GlyphId(pub(crate) u16);
pub(crate) struct Face {
font: harfrust::Font,
}
impl Face {
pub(crate) fn from_slice(data: &[u8], index: u32) -> Option<Self> {
Self::new(data.to_vec().into(), index)
}
pub(crate) fn from_shared(data: &Arc<[u8]>, index: u32) -> Option<Self> {
let shared: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(Arc::clone(data));
Self::new(shared.into(), index)
}
fn new(blob: harfrust::font::Blob, index: u32) -> Option<Self> {
let font = harfrust::Font::new(blob, index)?;
let tables = font.tables();
let head = tables.head().ok()?;
if !(16..=16384).contains(&head.units_per_em())
|| !matches!(head.index_to_loc_format(), 0 | 1)
{
return None;
}
tables.hhea().ok()?;
let maxp = tables.maxp().ok()?;
let version = u32::from_be_bytes(maxp.version().to_be_bytes());
if !matches!(version, 0x0000_5000 | 0x0001_0000) || maxp.num_glyphs() == 0 {
return None;
}
Some(Self { font })
}
pub(crate) fn tables(&self) -> &harfrust::font::Tables {
self.font.tables()
}
pub(crate) fn units_per_em(&self) -> u16 {
self.font.units_per_em()
}
#[cfg(test)]
pub(crate) fn number_of_glyphs(&self) -> u16 {
self.tables().maxp().map_or(0, |maxp| maxp.num_glyphs())
}
fn typographic(&self) -> Option<Os2<'_>> {
self.tables().os2().ok().filter(|os2| {
os2.version() >= 4
&& os2
.fs_selection()
.contains(SelectionFlags::USE_TYPO_METRICS)
})
}
pub(crate) fn ascender(&self) -> i16 {
if let Some(os2) = self.typographic() {
return os2.s_typo_ascender();
}
let value = self.hhea_line().0;
if value != 0 {
return value;
}
match self.tables().os2() {
Ok(os2) if os2.s_typo_ascender() != 0 => os2.s_typo_ascender(),
Ok(os2) => os2.us_win_ascent() as i16,
Err(_) => value,
}
}
pub(crate) fn descender(&self) -> i16 {
if let Some(os2) = self.typographic() {
return os2.s_typo_descender();
}
let value = self.hhea_line().1;
if value != 0 {
return value;
}
match self.tables().os2() {
Ok(os2) if os2.s_typo_descender() != 0 => os2.s_typo_descender(),
Ok(os2) => (os2.us_win_descent() as i16).wrapping_neg(),
Err(_) => value,
}
}
pub(crate) fn line_gap(&self) -> i16 {
if let Some(os2) = self.typographic() {
return os2.s_typo_line_gap();
}
let (ascender, descender, value) = self.hhea_line();
if ascender != 0 && descender != 0 {
return value;
}
match self.tables().os2() {
Ok(os2) if os2.s_typo_ascender() != 0 || os2.s_typo_descender() != 0 => {
os2.s_typo_line_gap()
}
Ok(_) => 0,
Err(_) => value,
}
}
fn hhea_line(&self) -> (i16, i16, i16) {
self.tables().hhea().map_or((0, 0, 0), |hhea| {
(
hhea.ascender().to_i16(),
hhea.descender().to_i16(),
hhea.line_gap().to_i16(),
)
})
}
pub(crate) fn glyph_index(&self, c: char) -> Option<GlyphId> {
let cmap = self.tables().cmap().ok()?;
let data = cmap.offset_data();
for record in cmap.encoding_records() {
let Ok(subtable) = record.subtable(data) else {
continue;
};
let unicode = match record.platform_id() {
PlatformId::Unicode => true,
PlatformId::Windows => {
record.encoding_id() == 1
|| (record.encoding_id() == 10 && matches!(subtable.format(), 12 | 13))
}
_ => false,
};
if !unicode {
continue;
}
let glyph = subtable
.map_codepoint(c)
.filter(|glyph| subtable.format() != 0 || glyph.to_u32() != 0)
.and_then(|glyph| u16::try_from(glyph.to_u32()).ok());
if let Some(glyph) = glyph {
return Some(GlyphId(glyph));
}
}
None
}
pub(crate) fn glyph_hor_advance(&self, glyph: GlyphId) -> Option<u16> {
let tables = self.tables();
let metrics = tables.hhea().ok()?.number_of_h_metrics();
if metrics == 0 {
return None;
}
let glyphs = tables.maxp().ok()?.num_glyphs();
if glyph.0 >= metrics.max(glyphs) {
return None;
}
let long = tables.hmtx().ok()?.h_metrics();
if long.len() < usize::from(metrics) {
return None;
}
long.get(usize::from(glyph.0))
.or(long.last())
.map(|metric| metric.advance())
}
#[cfg(test)]
pub(crate) fn glyph_hor_side_bearing(&self, glyph: GlyphId) -> Option<i16> {
let tables = self.tables();
let metrics = tables.hhea().ok()?.number_of_h_metrics();
let hmtx = tables.hmtx().ok()?;
let long = hmtx.h_metrics();
if metrics == 0 || long.len() < usize::from(metrics) {
return None;
}
match long.get(usize::from(glyph.0)) {
Some(metric) => Some(metric.side_bearing()),
None => hmtx
.left_side_bearings()
.get(usize::from(glyph.0) - long.len())
.map(|bearing| bearing.get()),
}
}
pub(crate) fn outline_glyph(
&self,
glyph: GlyphId,
builder: &mut dyn OutlineBuilder,
) -> Option<Rect> {
let tables = self.tables();
let glyf = tables.glyf().ok()?;
let loca = tables.loca(None).ok()?;
glyf::outline(&loca, &glyf, glyph.0, builder)
}
}
pub(crate) fn shape(
face: &Face,
features: &[Feature],
text: &str,
direction: Option<Direction>,
) -> Buffer {
let mut buffer = Buffer::new();
buffer.push_str(text);
if let Some(direction) = direction {
buffer.set_direction(direction);
}
buffer.guess_segment_properties();
harfrust::shape(
&harfrust::ShaperFont::new(&face.font),
&mut buffer,
harfrust::ShapeOptions::new().features(features),
)
.expect("a guessed text buffer shapes");
buffer
}
pub(crate) fn reverses_cluster_glyphs(buffer: &Buffer) -> bool {
buffer.direction() == Direction::RightToLeft && buffer.script().is_some_and(backward_script)
}
fn backward_script(script: harfrust::Script) -> bool {
BACKWARD_SCRIPTS.contains(&script)
}
pub(crate) const BACKWARD_SCRIPTS: [harfrust::Script; 40] = {
use harfrust::Script;
[
Script::ARABIC,
Script::HEBREW,
Script::SYRIAC,
Script::THAANA,
Script::CYPRIOT,
Script::KHAROSHTHI,
Script::PHOENICIAN,
Script::NKO,
Script::LYDIAN,
Script::AVESTAN,
Script::IMPERIAL_ARAMAIC,
Script::INSCRIPTIONAL_PAHLAVI,
Script::INSCRIPTIONAL_PARTHIAN,
Script::OLD_SOUTH_ARABIAN,
Script::OLD_TURKIC,
Script::SAMARITAN,
Script::MANDAIC,
Script::MEROITIC_CURSIVE,
Script::MEROITIC_HIEROGLYPHS,
Script::MANICHAEAN,
Script::MENDE_KIKAKUI,
Script::NABATAEAN,
Script::OLD_NORTH_ARABIAN,
Script::PALMYRENE,
Script::PSALTER_PAHLAVI,
Script::HATRAN,
Script::ADLAM,
Script::HANIFI_ROHINGYA,
Script::OLD_SOGDIAN,
Script::SOGDIAN,
Script::ELYMAIC,
Script::CHORASMIAN,
Script::YEZIDI,
Script::OLD_UYGHUR,
Script::GARAY,
Script::SIDETIC,
Script::OLD_HUNGARIAN,
Script::OLD_ITALIC,
Script::RUNIC,
Script::TIFINAGH,
]
};
#[cfg(test)]
pub(crate) fn shape_in_script(
face: &Face,
text: &str,
direction: Direction,
script: Option<harfrust::Script>,
) -> Buffer {
let mut buffer = Buffer::new();
buffer.push_str(text);
buffer.set_direction(direction);
buffer.set_script(script);
harfrust::shape(
&harfrust::ShaperFont::new(&face.font),
&mut buffer,
harfrust::ShapeOptions::new(),
)
.expect("a buffer with a direction shapes");
buffer
}