use crate::platform::{self, abs, atan2, f32x4, sqrt}; use crate::{Glyph, OutlineBounds}; use alloc::vec; use alloc::vec::*; #[derive(Copy, Clone, PartialEq, Debug)] struct AABB { /// Coordinate of the left-most edge. xmin: f32, /// Coordinate of the right-most edge. xmax: f32, /// Coordinate of the bottom-most edge. ymin: f32, /// Coordinate of the top-most edge. ymax: f32, } impl Default for AABB { fn default() -> Self { AABB { xmin: 0.0, xmax: 0.0, ymin: 0.0, ymax: 0.0, } } } #[derive(Copy, Clone, Debug, PartialEq)] struct CubeCurve { a: Point, b: Point, c: Point, d: Point, } impl CubeCurve { const fn new(a: Point, b: Point, c: Point, d: Point) -> CubeCurve { CubeCurve { a, b, c, d, } } fn scale(&self, scale: f32) -> CubeCurve { CubeCurve { a: self.a.scale(scale), b: self.b.scale(scale), c: self.c.scale(scale), d: self.d.scale(scale), } } fn is_flat(&self, threshold: f32) -> bool { let (d1, d2, d3, d4) = f32x4::new( self.a.distance_squared(self.b), self.b.distance_squared(self.c), self.c.distance_squared(self.d), self.a.distance_squared(self.d), ) .sqrt() .copied(); (d1 + d2 + d3) < threshold * d4 } fn split(&self) -> (CubeCurve, CubeCurve) { let q0 = self.a.midpoint(self.b); let q1 = self.b.midpoint(self.c); let q2 = self.c.midpoint(self.d); let r0 = q0.midpoint(q1); let r1 = q1.midpoint(q2); let s0 = r0.midpoint(r1); (CubeCurve::new(self.a, q0, r0, s0), CubeCurve::new(s0, r1, q2, self.d)) } /// The point at time t in the curve. fn point(&self, t: f32) -> Point { let tm = 1.0 - t; let a = tm * tm * tm; let b = 3.0 * (tm * tm) * t; let c = 3.0 * tm * (t * t); let d = t * t * t; let x = a * self.a.x + b * self.b.x + c * self.c.x + d * self.d.x; let y = a * self.a.y + b * self.b.y + c * self.c.y + d * self.d.y; Point::new(x, y) } /// The slope of the tangent line at time t. fn slope(&self, t: f32) -> (f32, f32) { let tm = 1.0 - t; let a = 3.0 * (tm * tm); let b = 6.0 * tm * t; let c = 3.0 * (t * t); let x = a * (self.b.x - self.a.x) + b * (self.c.x - self.b.x) + c * (self.d.x - self.c.x); let y = a * (self.b.y - self.a.y) + b * (self.c.y - self.b.y) + c * (self.d.y - self.c.y); (x, y) } /// The angle of the tangent line at time t in rads. fn angle(&self, t: f32) -> f32 { let (x, y) = self.slope(t); abs(atan2(x, y)) } } #[derive(Copy, Clone, Debug, PartialEq)] struct QuadCurve { a: Point, b: Point, c: Point, } impl QuadCurve { fn new(a: Point, b: Point, c: Point) -> QuadCurve { QuadCurve { a, b, c, } } fn scale(&self, scale: f32) -> QuadCurve { QuadCurve { a: self.a.scale(scale), b: self.b.scale(scale), c: self.c.scale(scale), } } fn is_flat(&self, threshold: f32) -> bool { let (d1, d2, d3, _) = f32x4::new( self.a.distance_squared(self.b), self.b.distance_squared(self.c), self.a.distance_squared(self.c), 1.0, ) .sqrt() .copied(); (d1 + d2) < threshold * d3 } fn split(&self) -> (QuadCurve, QuadCurve) { let q0 = self.a.midpoint(self.b); let q1 = self.b.midpoint(self.c); let r0 = q0.midpoint(q1); (QuadCurve::new(self.a, q0, r0), QuadCurve::new(r0, q1, self.c)) } /// The point at time t in the curve. fn point(&self, t: f32) -> Point { let tm = 1.0 - t; let a = tm * tm; let b = 2.0 * tm * t; let c = t * t; let x = a * self.a.x + b * self.b.x + c * self.c.x; let y = a * self.a.y + b * self.b.y + c * self.c.y; Point::new(x, y) } /// The slope of the tangent line at time t. fn slope(&self, t: f32) -> (f32, f32) { let tm = 1.0 - t; let a = 2.0 * tm; let b = 2.0 * t; let x = a * (self.b.x - self.a.x) + b * (self.c.x - self.b.x); let y = a * (self.b.y - self.a.y) + b * (self.c.y - self.b.y); (x, y) } /// The angle of the tangent line at time t in rads. fn angle(&self, t: f32) -> f32 { let (x, y) = self.slope(t); abs(atan2(x, y)) } } #[derive(Copy, Clone, Debug, PartialEq)] pub struct Point { /// Absolute X coordinate. pub x: f32, /// Absolute Y coordinate. pub y: f32, } impl Default for Point { fn default() -> Self { Point { x: 0.0, y: 0.0, } } } impl Point { pub const fn new(x: f32, y: f32) -> Point { Point { x, y, } } pub fn scale(&self, scale: f32) -> Point { Point { x: self.x * scale, y: self.y * scale, } } pub fn distance_squared(&self, other: Point) -> f32 { let x = self.x - other.x; let y = self.y - other.y; x * x + y * y } pub fn distance(&self, other: Point) -> f32 { let x = self.x - other.x; let y = self.y - other.y; sqrt(x * x + y * y) } pub fn midpoint(&self, other: Point) -> Point { Point { x: (self.x + other.x) / 2.0, y: (self.y + other.y) / 2.0, } } } #[derive(Copy, Clone)] pub struct Line { /// X0, Y0, X1, Y1. pub coords: f32x4, /// start_x_nudge, start_y_nudge, end_x_nudge, end_y_nudge. pub nudge: f32x4, /// x_first_adj, y_first_adj, none, none. pub adjustment: f32x4, /// tdx, tdy, dx, dy. pub params: f32x4, } impl Line { pub fn new(start: Point, end: Point) -> Line { // Floor adjustment and nudge: 0.0, 0 // Ceil adjustment and nudge: 1.0, 1 const FLOOR_NUDGE: u32 = 0; const CEIL_NUDGE: u32 = 1; let (x_start_nudge, x_first_adj) = if end.x >= start.x { (FLOOR_NUDGE, 1.0) } else { (CEIL_NUDGE, 0.0) }; let (y_start_nudge, y_first_adj) = if end.y >= start.y { (FLOOR_NUDGE, 1.0) } else { (CEIL_NUDGE, 0.0) }; let x_end_nudge = if end.x > start.x { CEIL_NUDGE } else { FLOOR_NUDGE }; let y_end_nudge = if end.y > start.y { CEIL_NUDGE } else { FLOOR_NUDGE }; let dx = end.x - start.x; let dy = end.y - start.y; let tdx = if dx == 0.0 { core::f32::MAX } else { 1.0 / dx }; let tdy = 1.0 / dy; Line { coords: f32x4::new(start.x, start.y, end.x, end.y), nudge: f32x4::new_u32(x_start_nudge, y_start_nudge, x_end_nudge, y_end_nudge), adjustment: f32x4::new(x_first_adj, y_first_adj, 0.0, 0.0), params: f32x4::new(tdx, tdy, dx, dy), } } fn reposition(&mut self, bounds: AABB, reverse: bool) { let (mut x0, mut y0, mut x1, mut y1) = if !reverse { self.coords.copied() } else { let (x0, y0, x1, y1) = self.coords.copied(); (x1, y1, x0, y0) }; x0 -= bounds.xmin; y0 -= bounds.ymax; y0 = abs(y0); x1 -= bounds.xmin; y1 -= bounds.ymax; y1 = abs(y1); *self = Self::new(Point::new(x0, y0), Point::new(x1, y1)); } } #[derive(Clone)] pub struct Geometry { v_lines: Vec, m_lines: Vec, effective_bounds: AABB, start_point: Point, previous_point: Point, area: f32, reverse_points: bool, max_area: f32, } struct Segment { a: Point, at: f32, c: Point, ct: f32, } impl Segment { const fn new(a: Point, at: f32, c: Point, ct: f32) -> Segment { Segment { a, at, c, ct, } } } impl ttf_parser::OutlineBuilder for Geometry { fn move_to(&mut self, x0: f32, y0: f32) { let next_point = Point::new(x0, y0); self.start_point = next_point; self.previous_point = next_point; } fn line_to(&mut self, x0: f32, y0: f32) { let next_point = Point::new(x0, y0); self.push(self.previous_point, next_point); self.previous_point = next_point; } fn quad_to(&mut self, x0: f32, y0: f32, x1: f32, y1: f32) { let control_point = Point::new(x0, y0); let next_point = Point::new(x1, y1); let curve = QuadCurve::new(self.previous_point, control_point, next_point); let mut stack = vec![Segment::new(self.previous_point, 0.0, next_point, 1.0)]; while let Some(seg) = stack.pop() { let bt = (seg.at + seg.ct) * 0.5; let b = curve.point(bt); // This is twice the triangle area let area = (b.x - seg.a.x) * (seg.c.y - seg.a.y) - (seg.c.x - seg.a.x) * (b.y - seg.a.y); if platform::abs(area) > self.max_area { stack.push(Segment::new(seg.a, seg.at, b, bt)); stack.push(Segment::new(b, bt, seg.c, seg.ct)); } else { self.push(seg.a, seg.c); } } self.previous_point = next_point; } fn curve_to(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, x2: f32, y2: f32) { let first_control = Point::new(x0, y0); let second_control = Point::new(x1, y1); let next_point = Point::new(x2, y2); let curve = CubeCurve::new(self.previous_point, first_control, second_control, next_point); let mut stack = vec![Segment::new(self.previous_point, 0.0, next_point, 1.0)]; while let Some(seg) = stack.pop() { let bt = (seg.at + seg.ct) * 0.5; let b = curve.point(bt); // This is twice the triangle area let area = (b.x - seg.a.x) * (seg.c.y - seg.a.y) - (seg.c.x - seg.a.x) * (b.y - seg.a.y); if platform::abs(area) > self.max_area { stack.push(Segment::new(seg.a, seg.at, b, bt)); stack.push(Segment::new(b, bt, seg.c, seg.ct)); } else { self.push(seg.a, seg.c); } } self.previous_point = next_point; } fn close(&mut self) { if self.start_point != self.previous_point { self.push(self.previous_point, self.start_point); } self.previous_point = self.start_point; } } impl Geometry { // Artisanal bespoke hand carved curves pub fn new(scale: f32, units_per_em: f32) -> Geometry { const ERROR_THRESHOLD: f32 = 3.0; // In pixels. let max_area = ERROR_THRESHOLD * 2.0 * (units_per_em / scale); Geometry { v_lines: Vec::new(), m_lines: Vec::new(), effective_bounds: AABB { xmin: core::f32::MAX, xmax: core::f32::MIN, ymin: core::f32::MAX, ymax: core::f32::MIN, }, start_point: Point::default(), previous_point: Point::default(), area: 0.0, reverse_points: false, max_area, } } fn push(&mut self, start: Point, end: Point) { // We're using to_bits here because we only care if they're _exactly_ the same. if start.y.to_bits() != end.y.to_bits() { self.area += (end.y - start.y) * (end.x + start.x); if start.x.to_bits() == end.x.to_bits() { self.v_lines.push(Line::new(start, end)); } else { self.m_lines.push(Line::new(start, end)); } Self::recalculate_bounds(&mut self.effective_bounds, start.x, start.y); Self::recalculate_bounds(&mut self.effective_bounds, end.x, end.y); } } pub(crate) fn finalize(mut self, glyph: &mut Glyph) { if self.v_lines.is_empty() && self.m_lines.is_empty() { self.effective_bounds = AABB::default(); } else { self.reverse_points = self.area > 0.0; for line in self.v_lines.iter_mut().chain(self.m_lines.iter_mut()) { line.reposition(self.effective_bounds, self.reverse_points); } self.v_lines.shrink_to_fit(); self.m_lines.shrink_to_fit(); } glyph.v_lines = self.v_lines; glyph.m_lines = self.m_lines; glyph.bounds = OutlineBounds { xmin: self.effective_bounds.xmin, ymin: self.effective_bounds.ymin, width: self.effective_bounds.xmax - self.effective_bounds.xmin, height: self.effective_bounds.ymax - self.effective_bounds.ymin, }; } fn recalculate_bounds(bounds: &mut AABB, x: f32, y: f32) { if x < bounds.xmin { bounds.xmin = x; } if x > bounds.xmax { bounds.xmax = x; } if y < bounds.ymin { bounds.ymin = y; } if y > bounds.ymax { bounds.ymax = y; } } }