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_aligned_positions {
($face:expr, $layout:expr, $text:expr) => {{
let replacement_glyph_id = $face.glyph_index('�');
let kerning_table = $face.tables().kern.and_then(|table| table.subtables.into_iter().next());
let (major_axis, sign_x, sign_y) = match $layout.orientation {
Orientation::RightToLeft => (0, -1, -1),
Orientation::LeftToRight => (0, 1, -1),
Orientation::TopToBottom => (1, 1, -1),
Orientation::BottomToTop => (1, 1, 1),
};
let (line_minor_extent, line_gap) = if major_axis == 0 {
($face.height() as i64, $face.line_gap() as i64)
} else {
($face.vertical_height().unwrap_or(0) as i64, $face.vertical_line_gap().unwrap_or(0) as i64)
};
let mut lines = Vec::new();
let mut line_major_extent = 0;
let mut extent = [0; 2];
let mut glyph_positions = Vec::new();
let mut prev_glyph_id = None;
let mut index = 0;
for char in $text.chars() {
index += 1;
let mut glyph_position = extent;
glyph_position[major_axis] = line_major_extent;
if char == '\n' {
glyph_positions.push((glyph_position, ttf_parser::GlyphId(0)));
let mut line_glyph_positions = Vec::new();
std::mem::swap(&mut glyph_positions, &mut line_glyph_positions);
lines.push((index, line_glyph_positions));
extent[major_axis] = extent[major_axis].max(line_major_extent);
extent[1 - major_axis] += line_minor_extent + line_gap;
line_major_extent = 0;
prev_glyph_id = None;
} else {
let glyph_id = $face.glyph_index(char).or(replacement_glyph_id).unwrap();
if let (Some(kerning_table), Some(prev_glyph_id)) = (kerning_table, prev_glyph_id) {
if let Some(kerning) = kerning_table.glyphs_kerning(prev_glyph_id, glyph_id) {
line_major_extent += kerning as i64;
}
}
prev_glyph_id = Some(glyph_id);
let advance = if major_axis == 0 {
$face.glyph_hor_advance(glyph_id)
} else {
$face.glyph_ver_advance(glyph_id)
};
if let Some(advance) = advance {
line_major_extent += advance as i64;
}
glyph_positions.push((glyph_position, glyph_id));
};
}
{
let mut glyph_position = extent;
glyph_position[major_axis] = line_major_extent;
glyph_positions.push((glyph_position, ttf_parser::GlyphId(0)));
lines.push((index + 1, glyph_positions));
extent[major_axis] = extent[major_axis].max(line_major_extent);
extent[1 - major_axis] += line_minor_extent;
}
let mut offset = [0, 0];
offset[1 - major_axis] = match $layout.minor_alignment {
Alignment::Begin => -$face.descender() as i64,
Alignment::Baseline => 0,
Alignment::Center => $face.x_height().unwrap() as i64 / 2,
Alignment::End => -line_minor_extent,
};
for (_line_range_end, glyph_positions) in lines.iter_mut() {
let line_major_extent = glyph_positions.last().unwrap().0[major_axis];
let mut offset = offset;
offset[major_axis] = match $layout.major_alignment {
Alignment::Begin => -extent[major_axis] / 2,
Alignment::Baseline | Alignment::Center => -line_major_extent / 2,
Alignment::End => extent[major_axis] / 2 - line_major_extent,
};
offset[1 - major_axis] -= (extent[1 - major_axis] - line_minor_extent) / 2;
for (position, _glyph_id) in glyph_positions.iter_mut() {
position[0] = sign_x * (position[0] + offset[0]);
position[1] = sign_y * (position[1] + offset[1]);
}
}
(extent, [sign_x * offset[0], sign_y * offset[1]], lines)
}};
}
pub fn paths_of_text(face: &ttf_parser::Face, layout: &Layout, text: &str, clipping_area: Option<&[ppga2d::Point]>) -> Vec<Path> {
let (_extent, _offset, lines) = 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 lines
.iter()
.flat_map(|(_line_range_end, glyph_positions)| glyph_positions[0..glyph_positions.len() - 1].iter())
{
if let (Some(clipping_area), Some(glyph_bounding_box)) = (clipping_area, face.glyph_bounding_box(*glyph_id)) {
let aabb = [
(glyph_bounding_box.x_min as i64 + x) as f32 * scale,
(glyph_bounding_box.y_min as i64 + y) as f32 * scale,
(glyph_bounding_box.x_max as i64 + x) as f32 * scale,
(glyph_bounding_box.y_max as i64 + y) as f32 * scale,
];
if !do_convex_polygons_overlap(&aabb_to_convex_polygon(&aabb), clipping_area) {
continue;
}
}
let motor = translate2d([*x as f32 * scale, *y as f32 * scale]);
let mut paths = paths_of_glyph(face, *glyph_id);
for path in &mut paths {
path.transform(scale, &motor);
}
result.append(&mut paths);
}
result
}
pub struct TextGeometry {
pub major_axis: usize,
pub half_extent: SafeFloat<f32, 2>,
pub lines: Vec<(usize, Vec<SafeFloat<f32, 2>>)>,
}
impl TextGeometry {
pub fn new(face: &ttf_parser::Face, layout: &Layout, text: &str) -> Self {
let major_axis = match layout.orientation {
Orientation::RightToLeft | Orientation::LeftToRight => 0,
Orientation::TopToBottom | Orientation::BottomToTop => 1,
};
let scale = layout.size.unwrap() / face.height() as f32;
let (extent, offset, lines) = calculate_aligned_positions!(face, layout, text);
Self {
major_axis,
half_extent: [extent[0] as f32 * scale * 0.5, extent[1] as f32 * scale * 0.5].into(),
lines: lines
.iter()
.map(|(line_range_end, glyph_positions)| {
(
*line_range_end,
glyph_positions
.iter()
.map(|(position, _glyph_id)| [(position[0] - offset[0]) as f32 * scale, (position[1] - offset[1]) as f32 * scale].into())
.collect(),
)
})
.collect(),
}
}
pub fn line_index_from_char_index(&self, char_index: usize) -> usize {
self.lines
.iter()
.position(|(line_range_end, _glyph_positions)| *line_range_end > char_index)
.unwrap()
}
pub fn char_index_from_position(&self, cursor: SafeFloat<f32, 2>) -> usize {
let cursor = cursor.unwrap();
let minor_half_extent = self.half_extent.unwrap()[1 - self.major_axis];
let line_index = ((minor_half_extent - cursor[1 - self.major_axis]) * self.lines.len() as f32 / (minor_half_extent * 2.0))
.max(0.0)
.min((self.lines.len() - 1) as f32) as usize;
let glyph_positions = &self.lines[line_index].1;
glyph_positions
.iter()
.zip(glyph_positions.iter().skip(1))
.position(|(prev, next)| (prev.unwrap()[self.major_axis] + next.unwrap()[self.major_axis]) * 0.5 > cursor[self.major_axis])
.unwrap_or(glyph_positions.len() - 1)
+ if line_index == 0 { 0 } else { self.lines[line_index - 1].0 }
}
pub fn advance_char_index_by_line_index(&self, char_index: usize, relative_line_index: isize) -> usize {
let line_index = self.line_index_from_char_index(char_index);
if relative_line_index < 0 && line_index == 0 {
return 0;
} else if relative_line_index > 0 && line_index == self.lines.len() - 1 {
return self.lines.last().unwrap().0 - 1;
}
let (line_range_end, glyph_positions) = &self.lines[line_index];
let mut cursor = glyph_positions[char_index + glyph_positions.len() - *line_range_end].unwrap();
let line_minor_extent = self.half_extent.unwrap()[1 - self.major_axis] * 2.0 / self.lines.len() as f32;
cursor[1 - self.major_axis] -= line_minor_extent * relative_line_index as f32;
self.char_index_from_position(cursor.into())
}
}
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())
}