pub(crate) mod glyph;
pub(crate) mod shape;
pub(crate) mod tables;
use std::sync::Arc;
use crate::api::path::Path;
use crate::font::tables::ParsedTables;
#[derive(Debug)]
pub enum FontError {
Io(std::io::Error),
InvalidData(&'static str),
}
impl std::fmt::Display for FontError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FontError::Io(e) => write!(f, "I/O error: {e}"),
FontError::InvalidData(msg) => write!(f, "invalid font data: {msg}"),
}
}
}
impl std::error::Error for FontError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FontError::Io(e) => Some(e),
FontError::InvalidData(_) => None,
}
}
}
impl From<std::io::Error> for FontError {
fn from(e: std::io::Error) -> Self {
FontError::Io(e)
}
}
pub struct FontData {
data: Vec<u8>,
}
impl FontData {
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, FontError> {
let data = std::fs::read(path)?;
Ok(Self { data })
}
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self { data: bytes }
}
pub fn data(&self) -> &[u8] {
&self.data
}
}
pub struct FontFace {
pub(crate) data: Arc<Vec<u8>>,
pub(crate) tables: Arc<ParsedTables>,
}
impl FontFace {
pub fn from_data(font_data: &FontData, index: u32) -> Result<Self, FontError> {
let data = Arc::new(font_data.data.clone());
let tables = Arc::new(tables::parse_all(&data, index)?);
Ok(Self { data, tables })
}
pub fn units_per_em(&self) -> u16 {
self.tables.units_per_em
}
pub fn ascent(&self) -> i16 {
self.tables.ascent
}
pub fn descent(&self) -> i16 {
self.tables.descent
}
pub fn line_gap(&self) -> i16 {
self.tables.line_gap
}
pub fn cap_height(&self) -> Option<i16> {
self.tables.cap_height
}
pub fn x_height(&self) -> Option<i16> {
self.tables.x_height
}
pub fn glyph_bounds(&self, glyph_id: u16) -> Option<GlyphBounds> {
let glyph_data = glyph::glyph_entry_slice(glyph_id, &self.tables, &self.data)
.ok()
.flatten()?;
let raw = glyph::glyph_bbox_raw(glyph_data)?;
Some(GlyphBounds::from_raw_scaled(raw, 1.0))
}
}
#[derive(Clone)]
pub struct Font {
face: Arc<FontFace>,
size: f64,
scale: f64,
feature_settings: FontFeatureSettings,
}
impl Font {
pub fn with_features(face: &FontFace, size: f64, features: FontFeatureSettings) -> Self {
let scale = size / face.tables.units_per_em as f64;
let face = Arc::new(FontFace {
data: Arc::clone(&face.data),
tables: Arc::clone(&face.tables),
});
Self {
face,
size,
scale,
feature_settings: features,
}
}
pub fn from_face(face: &FontFace, size: f64) -> Self {
Self::with_features(face, size, FontFeatureSettings::default())
}
pub fn size(&self) -> f64 {
self.size
}
pub fn scale(&self) -> f64 {
self.scale
}
pub fn map_char_to_glyph(&self, ch: char) -> u16 {
self.face.tables.cmap.map(ch as u32)
}
pub fn glyph_advance(&self, glyph_id: u16) -> f64 {
let gid = glyph_id as usize;
let aw = if gid < self.face.tables.hmtx.advance_widths.len() {
self.face.tables.hmtx.advance_widths[gid]
} else {
0
};
aw as f64 * self.scale
}
pub fn append_glyph_outline(
&self,
glyph_id: u16,
offset_x: f64,
offset_y: f64,
path: &mut Path,
) -> Result<(), FontError> {
glyph::append_glyph_outline(
glyph_id,
offset_x,
offset_y,
self.scale,
&self.face.tables,
&self.face.data,
path,
)
}
pub fn ascent(&self) -> f64 {
self.face.tables.ascent as f64 * self.scale
}
pub fn descent(&self) -> f64 {
self.face.tables.descent as f64 * self.scale
}
pub fn line_gap(&self) -> f64 {
self.face.tables.line_gap as f64 * self.scale
}
pub fn cap_height(&self) -> Option<f64> {
self.face.tables.cap_height.map(|v| v as f64 * self.scale)
}
pub fn x_height(&self) -> Option<f64> {
self.face.tables.x_height.map(|v| v as f64 * self.scale)
}
pub fn glyph_bounds(&self, glyph_id: u16) -> Option<GlyphBounds> {
let glyph_data = glyph::glyph_entry_slice(glyph_id, &self.face.tables, &self.face.data)
.ok()
.flatten()?;
let raw = glyph::glyph_bbox_raw(glyph_data)?;
Some(GlyphBounds::from_raw_scaled(raw, self.scale))
}
pub fn set_feature_settings(&mut self, features: FontFeatureSettings) {
self.feature_settings = features;
}
pub fn feature_settings(&self) -> &FontFeatureSettings {
&self.feature_settings
}
pub fn clone_with_features(&self, features: FontFeatureSettings) -> Self {
let mut clone = self.clone();
clone.feature_settings = features;
clone
}
pub fn shape(&self, text: &str) -> GlyphBuffer {
let mut buf = GlyphBuffer::default();
self.shape_into(text, &mut buf);
buf
}
pub fn shape_into(&self, text: &str, buf: &mut GlyphBuffer) {
buf.clear();
for (byte_idx, ch) in text.char_indices() {
let gid = self.map_char_to_glyph(ch);
buf.push_glyph(
gid,
GlyphPlacement {
offset_x: 0.0,
offset_y: 0.0,
advance: self.glyph_advance(gid),
},
byte_idx as u32,
);
}
shape::apply_gsub(&self.face.tables.gsub, buf, &self.feature_settings, self);
if self.feature_settings.kern {
shape::apply_gpos_kern_feature_lookups(&self.face.tables.gpos, buf, self);
}
}
pub fn measure_text(&self, text: &str) -> TextMetrics {
let buffer = self.shape(text);
let advance: f64 = buffer.placements.iter().map(|p| p.advance).sum();
let bounding_box = compute_bounding_box(&buffer, self);
let leading_bearing = compute_leading_bearing(&buffer, self);
let trailing_bearing = compute_trailing_bearing(&buffer, self);
TextMetrics {
advance,
bounding_box,
leading_bearing,
trailing_bearing,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct GlyphBounds {
pub x_min: f64,
pub y_min: f64,
pub x_max: f64,
pub y_max: f64,
}
impl GlyphBounds {
pub(crate) fn from_raw_scaled(raw: (i16, i16, i16, i16), scale: f64) -> Self {
let (x_min, y_min, x_max, y_max) = raw;
Self {
x_min: x_min as f64 * scale,
y_min: y_min as f64 * scale,
x_max: x_max as f64 * scale,
y_max: y_max as f64 * scale,
}
}
pub(crate) fn translated_x(self, dx: f64) -> Self {
Self {
x_min: self.x_min + dx,
y_min: self.y_min,
x_max: self.x_max + dx,
y_max: self.y_max,
}
}
pub(crate) fn translated_y(self, dy: f64) -> Self {
Self {
x_min: self.x_min,
y_min: self.y_min + dy,
x_max: self.x_max,
y_max: self.y_max + dy,
}
}
pub(crate) fn union(self, other: Self) -> Self {
Self {
x_min: self.x_min.min(other.x_min),
y_min: self.y_min.min(other.y_min),
x_max: self.x_max.max(other.x_max),
y_max: self.y_max.max(other.y_max),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct TextMetrics {
pub advance: f64,
pub bounding_box: Option<GlyphBounds>,
pub leading_bearing: f64,
pub trailing_bearing: f64,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct FontFeatureSettings {
pub kern: bool,
pub liga: bool,
pub clig: bool,
}
impl Default for FontFeatureSettings {
fn default() -> Self {
Self {
kern: true,
liga: true,
clig: true,
}
}
}
impl FontFeatureSettings {
pub fn none() -> Self {
Self {
kern: false,
liga: false,
clig: false,
}
}
pub fn with_kern(mut self, enabled: bool) -> Self {
self.kern = enabled;
self
}
pub fn with_liga(mut self, enabled: bool) -> Self {
self.liga = enabled;
self
}
pub fn with_clig(mut self, enabled: bool) -> Self {
self.clig = enabled;
self
}
}
#[derive(Debug, Clone, Default, PartialEq)]
#[non_exhaustive]
pub struct GlyphBuffer {
pub(crate) glyph_ids: Vec<u16>,
pub(crate) placements: Vec<GlyphPlacement>,
pub(crate) clusters: Vec<u32>,
}
impl GlyphBuffer {
pub fn len(&self) -> usize {
self.glyph_ids.len()
}
pub fn is_empty(&self) -> bool {
self.glyph_ids.is_empty()
}
pub fn glyph_id(&self, index: usize) -> Option<u16> {
self.glyph_ids.get(index).copied()
}
pub fn placement(&self, index: usize) -> Option<GlyphPlacement> {
self.placements.get(index).copied()
}
pub fn cluster(&self, index: usize) -> Option<u32> {
self.clusters.get(index).copied()
}
pub fn iter(&self) -> GlyphBufferIter<'_> {
GlyphBufferIter {
buffer: self,
index: 0,
}
}
pub fn is_well_formed(&self) -> bool {
self.glyph_ids.len() == self.placements.len()
&& self.placements.len() == self.clusters.len()
}
pub(crate) fn clear(&mut self) {
self.glyph_ids.clear();
self.placements.clear();
self.clusters.clear();
}
pub(crate) fn push_glyph(&mut self, glyph_id: u16, placement: GlyphPlacement, cluster: u32) {
self.glyph_ids.push(glyph_id);
self.placements.push(placement);
self.clusters.push(cluster);
}
pub(crate) fn drain_range(&mut self, start: usize, end: usize) {
debug_assert!(start <= end, "drain_range: start ({start}) <= end ({end})");
debug_assert!(
end <= self.glyph_ids.len(),
"drain_range: end ({end}) <= len ({})",
self.glyph_ids.len()
);
self.glyph_ids.drain(start..end);
self.placements.drain(start..end);
self.clusters.drain(start..end);
}
}
#[derive(Debug, Clone)]
pub struct GlyphBufferIter<'a> {
buffer: &'a GlyphBuffer,
index: usize,
}
impl<'a> Iterator for GlyphBufferIter<'a> {
type Item = (u16, GlyphPlacement, u32);
fn next(&mut self) -> Option<Self::Item> {
let gid = self.buffer.glyph_ids.get(self.index).copied()?;
let placement = self.buffer.placements.get(self.index).copied()?;
let cluster = self.buffer.clusters.get(self.index).copied()?;
self.index += 1;
Some((gid, placement, cluster))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.buffer.glyph_ids.len().saturating_sub(self.index);
(len, Some(len))
}
}
impl<'a> ExactSizeIterator for GlyphBufferIter<'a> {}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
#[non_exhaustive]
pub struct GlyphPlacement {
pub offset_x: f64,
pub offset_y: f64,
pub advance: f64,
}
fn compute_bounding_box(buffer: &GlyphBuffer, font: &Font) -> Option<GlyphBounds> {
debug_assert_eq!(buffer.glyph_ids.len(), buffer.placements.len());
debug_assert_eq!(buffer.placements.len(), buffer.clusters.len());
let mut cursor_x = 0.0;
let mut bbox: Option<GlyphBounds> = None;
for (gid, placement, _cluster) in buffer.iter() {
if let Some(gb) = font.glyph_bounds(gid) {
let translated = gb
.translated_x(cursor_x + placement.offset_x)
.translated_y(placement.offset_y);
bbox = Some(match bbox {
None => translated,
Some(b) => b.union(translated),
});
}
cursor_x += placement.advance;
}
bbox
}
fn compute_leading_bearing(buffer: &GlyphBuffer, font: &Font) -> f64 {
let Some((gid, placement)) = buffer.glyph_ids.first().zip(buffer.placements.first()) else {
return 0.0;
};
font.glyph_bounds(*gid)
.map(|gb| gb.x_min + placement.offset_x)
.unwrap_or(0.0)
}
fn compute_trailing_bearing(buffer: &GlyphBuffer, font: &Font) -> f64 {
let Some(last_idx) = buffer.glyph_ids.len().checked_sub(1) else {
return 0.0;
};
let gid = buffer.glyph_ids[last_idx];
let placement = buffer.placements[last_idx];
font.glyph_bounds(gid)
.map(|gb| placement.advance - placement.offset_x - gb.x_max)
.unwrap_or(0.0)
}