use crate::{
path::{IntegralCubicCurveSegment, IntegralQuadraticCurveSegment, LineSegment, Path},
safe_float::SafeFloat,
utils::{aabb_to_convex_polygon, do_convex_polygons_overlap, translate2d},
};
use geometric_algebra::ppga2d;
pub struct Font {
name: String,
_backing_store: std::pin::Pin<Box<[u8]>>,
parsed_face: ttf_parser::Face<'static>,
}
impl Font {
pub fn new(name: String, font_data: &[u8]) -> Self {
let backing_store = std::pin::Pin::new(font_data.to_vec().into_boxed_slice());
let backing_slice = unsafe { std::mem::transmute::<&[u8], &[u8]>(&backing_store) };
Self {
name,
_backing_store: backing_store,
parsed_face: ttf_parser::Face::from_slice(backing_slice, 0).unwrap(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn face(&self) -> &ttf_parser::Face {
&self.parsed_face
}
}
impl std::fmt::Debug for Font {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Font({:?})", self.name)
}
}
impl PartialEq for Font {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Font {}
impl std::hash::Hash for Font {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
#[derive(Default)]
struct OutlineBuilder {
path: Path,
paths: Vec<Path>,
}
impl ttf_parser::OutlineBuilder for OutlineBuilder {
fn move_to(&mut self, x: f32, y: f32) {
self.path.start = [x, y].into();
}
fn line_to(&mut self, x: f32, y: f32) {
self.path.push_line(LineSegment {
control_points: [[x, y].into()],
});
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.path.push_integral_quadratic_curve(IntegralQuadraticCurveSegment {
control_points: [[x1, y1].into(), [x, y].into()],
});
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.path.push_integral_cubic_curve(IntegralCubicCurveSegment {
control_points: [[x1, y1].into(), [x2, y2].into(), [x, y].into()],
});
}
fn close(&mut self) {
let mut path = Path::default();
std::mem::swap(&mut path, &mut self.path);
self.paths.push(path);
}
}
pub fn paths_of_glyph(face: &ttf_parser::Face, glyph_id: ttf_parser::GlyphId) -> Vec<Path> {
let mut outline_builder = OutlineBuilder::default();
if let Some(_bounding_box) = face.outline_glyph(glyph_id, &mut outline_builder) {
outline_builder.paths
} else {
Vec::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Orientation {
RightToLeft,
LeftToRight,
TopToBottom,
BottomToTop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Alignment {
Begin,
Baseline,
Center,
End,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Layout {
pub size: SafeFloat<f32, 1>,
pub orientation: Orientation,
pub major_alignment: Alignment,
pub minor_alignment: Alignment,
}
macro_rules! calculate_kerning {
($face:expr, $layout:expr, $text:expr, |$glyph_position:ident, $glyph_id:ident| $glyph_position_callback:tt) => {{
let kerning_table = $face.tables().kern.and_then(|table| table.subtables.into_iter().next());
let mut major_offset = 0.0;
let mut prev_glyph_id = None;
let replacement_glyph_id = $face.glyph_index('�');
for char in $text.chars() {
let $glyph_id = $face.glyph_index(char).or(replacement_glyph_id).unwrap();
if let Some(prev_glyph_id) = prev_glyph_id {
if let Some(kerning_table) = kerning_table {
if let Some(kerning) = kerning_table.glyphs_kerning(prev_glyph_id, $glyph_id) {
major_offset += kerning as f32;
}
}
}
let $glyph_position = match $layout.orientation {
Orientation::RightToLeft | Orientation::LeftToRight => {
let position = [major_offset, 0.0];
if let Some(advance_x) = $face.glyph_hor_advance($glyph_id) {
major_offset += advance_x as f32;
}
position
}
Orientation::TopToBottom | Orientation::BottomToTop => {
let position = [0.0, major_offset];
if let Some(advance_y) = $face.glyph_ver_advance($glyph_id) {
major_offset += advance_y as f32;
}
position
}
};
$glyph_position_callback
prev_glyph_id = Some($glyph_id);
}
major_offset
}};
}
macro_rules! calculate_aligned_positions {
($face:expr, $layout:expr, $text:expr) => {{
let mut glyph_positions = Vec::new();
let mut major_offset = calculate_kerning!($face, $layout, $text, |glyph_position, glyph_id| {
glyph_positions.push((glyph_position, glyph_id));
});
major_offset *= match $layout.major_alignment {
Alignment::Begin => 0.0,
Alignment::Baseline | Alignment::Center => -0.5,
Alignment::End => -1.0,
};
let mut minor_offset = -match $layout.minor_alignment {
Alignment::Begin => $face.descender() as f32,
Alignment::Baseline => 0.0,
Alignment::Center => $face.x_height().unwrap() as f32 * 0.5,
Alignment::End => $face.height() as f32,
};
let (swap, mut major_sign, mut minor_sign) = match $layout.orientation {
Orientation::RightToLeft => (false, -1.0, 1.0),
Orientation::LeftToRight => (false, 1.0, 1.0),
Orientation::TopToBottom => (true, 1.0, -1.0),
Orientation::BottomToTop => (true, 1.0, 1.0),
};
if swap {
std::mem::swap(&mut major_offset, &mut minor_offset);
std::mem::swap(&mut major_sign, &mut minor_sign);
}
for (offsets, _glyph_id) in &mut glyph_positions {
offsets[0] = major_sign * (offsets[0] + major_offset);
offsets[1] = minor_sign * (offsets[1] + minor_offset);
}
(major_offset, minor_offset, glyph_positions)
}};
}
pub fn paths_of_text(face: &ttf_parser::Face, layout: &Layout, text: &str, clipping_area: Option<&[ppga2d::Point]>) -> Vec<Path> {
let (_major_offset, _minor_offset, glyph_positions) = calculate_aligned_positions!(face, layout, text);
let scale = layout.size.unwrap() / face.height() as f32;
let mut result = Vec::new();
for ([x, y], glyph_id) in glyph_positions {
if let Some(glyph_bounding_box) = face.glyph_bounding_box(glyph_id) {
if let Some(clipping_area) = clipping_area {
let aabb = [
(glyph_bounding_box.x_min as f32 + x) * scale,
(glyph_bounding_box.y_min as f32 + y) * scale,
(glyph_bounding_box.x_max as f32 + x) * scale,
(glyph_bounding_box.y_max as f32 + y) * scale,
];
if !do_convex_polygons_overlap(&aabb_to_convex_polygon(&aabb), clipping_area) {
continue;
}
}
} else {
continue;
}
let scalator = ppga2d::Scalar { g0: scale };
let motor = translate2d([x * scale, y * scale]);
let mut paths = paths_of_glyph(face, glyph_id);
for path in &mut paths {
path.transform(&scalator, &motor);
}
result.append(&mut paths);
}
result
}
pub fn half_extent_of_text(face: &ttf_parser::Face, layout: &Layout, text: &str) -> SafeFloat<f32, 2> {
let scale = 0.5 * layout.size.unwrap() / face.height() as f32;
let major_size = calculate_kerning!(face, layout, text, |_glyph_position, _glyph_id| {}) * scale;
let minor_size = face.height() as f32 * scale;
match layout.orientation {
Orientation::RightToLeft | Orientation::LeftToRight => [major_size, minor_size].into(),
Orientation::TopToBottom | Orientation::BottomToTop => [minor_size, major_size].into(),
}
}
pub fn index_of_char_at(face: &ttf_parser::Face, layout: &Layout, text: &str, cursor: ppga2d::Point) -> usize {
let (_major_offset, _minor_offset, glyph_positions) = calculate_aligned_positions!(face, layout, text);
let scale = face.height() as f32 / (layout.size.unwrap() * cursor.g0[0]);
let cursor = [cursor.g0[1] * scale, cursor.g0[2] * scale];
match glyph_positions.binary_search_by(|([x, _y], glyph_id)| {
(x + face.glyph_hor_advance(*glyph_id).unwrap_or(0) as f32 * 0.5)
.partial_cmp(&cursor[0])
.unwrap()
}) {
Ok(index) => index,
Err(index) => index,
}
}
pub fn byte_offset_of_char_index(string: &str, char_index: usize) -> usize {
string.char_indices().nth(char_index).map(|(index, _char)| index).unwrap_or(string.len())
}