#![deny(missing_docs)]
use std::cmp::Ordering;
use pathfinder_geometry::vector::Vector2F;
use pathfinder_geometry::{line_segment::LineSegment2F, rect::RectF};
use tinyvec::{array_vec, ArrayVec};
use crate::tables::glyf::{BoundingBox, Point as GlyfPoint};
use crate::tables::variable_fonts::OwnedTuple;
use crate::TryNumFrom;
#[derive(Clone, Copy, Debug)]
pub(crate) struct BBox {
rect: Option<RectF>,
}
pub trait OutlineBuilder {
type Error: std::error::Error;
type Output;
fn visit<S: OutlineSink>(
&mut self,
glyph_index: u16,
tuple: Option<&OwnedTuple>,
sink: &mut S,
) -> Result<Self::Output, Self::Error>;
}
pub trait OutlineSink {
fn move_to(&mut self, to: Vector2F);
fn line_to(&mut self, to: Vector2F);
fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F);
fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F);
fn close(&mut self);
}
pub struct BoundingBoxSink {
prev_point: Vector2F,
bbox: BBox,
}
impl BBox {
pub(crate) fn new() -> Self {
BBox { rect: None }
}
pub(crate) fn is_default(&self) -> bool {
self.rect.is_none()
}
pub(crate) fn extend_by_point(&mut self, point: Vector2F) {
self.rect = self
.rect
.map(|rect| rect.union_point(point))
.or_else(|| Some(RectF::from_points(point, point)))
}
pub(crate) fn to_bounding_box(&self) -> Option<BoundingBox> {
match self.rect.map(RectF::round_out) {
Some(rect) => Some(BoundingBox {
x_min: i16::try_num_from(rect.min_x())?,
y_min: i16::try_num_from(rect.min_y())?,
x_max: i16::try_num_from(rect.max_x())?,
y_max: i16::try_num_from(rect.max_y())?,
}),
None => None,
}
}
}
impl BoundingBoxSink {
pub fn new() -> Self {
BoundingBoxSink {
prev_point: Vector2F::zero(),
bbox: BBox::new(),
}
}
pub(crate) fn bbox(&self) -> BBox {
self.bbox
}
pub fn to_bounding_box(&self) -> Option<BoundingBox> {
self.bbox.to_bounding_box()
}
}
impl OutlineSink for BoundingBoxSink {
fn move_to(&mut self, to: Vector2F) {
self.bbox.extend_by_point(to);
self.prev_point = to;
}
fn line_to(&mut self, to: Vector2F) {
self.bbox.extend_by_point(to);
self.prev_point = to;
}
fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
let p0 = self.prev_point;
let p1 = ctrl;
let p2 = to;
if !RectF::from_points(p0.min(p2), p0.max(p2)).contains_point(p1) {
let denominator = p0 - (p1 * 2.0) + p2;
if denominator.x() != 0.0 && denominator.y() != 0.0 {
let t = ((p0 - p1) / denominator).clamp(Vector2F::splat(0.0), Vector2F::splat(1.0));
let s = Vector2F::splat(1.0) - t;
let q = s * s * p0 + (s * 2.0) * t * p1 + t * t * p2;
self.bbox.extend_by_point(q);
}
}
self.bbox.extend_by_point(to);
self.prev_point = to;
}
fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) {
let from = self.prev_point;
let rect = RectF::from_points(from.min(to), from.max(to));
if !(rect.contains_point(ctrl.from()) && rect.contains_point(ctrl.to())) {
let (x_roots, y_roots) = bezier_roots(from, ctrl.from(), ctrl.to(), to);
let coefficients = BezierCoefficients::new(from, ctrl.from(), ctrl.to(), to);
for t in x_roots.iter().chain(y_roots.iter()).copied() {
let point = coefficients.bezier_point(t);
self.bbox.extend_by_point(point);
}
}
self.bbox.extend_by_point(to);
self.prev_point = to;
}
fn close(&mut self) {
}
}
#[derive(Debug, Copy, Clone)]
struct BezierCoefficients {
pub ae: Vector2F,
pub bf: Vector2F,
pub cg: Vector2F,
pub dh: Vector2F,
}
impl BezierCoefficients {
pub fn new(p0: Vector2F, p1: Vector2F, p2: Vector2F, p3: Vector2F) -> Self {
let ae = p3 - p2 * 3.0 + p1 * 3.0 - p0;
let bf = p2 * 3.0 - p1 * 6.0 + p0 * 3.0;
let cg = p1 * 3.0 - p0 * 3.0;
let dh = p0;
Self { ae, bf, cg, dh }
}
#[cfg(test)]
fn as_array(&self) -> [f32; 8] {
[
self.ae.x(),
self.bf.x(),
self.cg.x(),
self.dh.x(),
self.ae.y(),
self.bf.y(),
self.cg.y(),
self.dh.y(),
]
}
fn bezier_point(&self, t: f32) -> Vector2F {
self.ae * t.powi(3) + self.bf * t.powi(2) + self.cg * t + self.dh
}
}
fn bezier_roots(
p1: Vector2F,
p2: Vector2F,
p3: Vector2F,
p4: Vector2F,
) -> (ArrayVec<[f32; 2]>, ArrayVec<[f32; 2]>) {
let x_roots = bezier_component_roots(p1.x(), p2.x(), p3.x(), p4.x());
let y_roots = bezier_component_roots(p1.y(), p2.y(), p3.y(), p4.y());
(x_roots, y_roots)
}
fn bezier_component_roots(p1: f32, p2: f32, p3: f32, p4: f32) -> ArrayVec<[f32; 2]> {
let a = 3.0 * (-p1 + 3.0 * p2 - 3.0 * p3 + p4);
let b = 6.0 * (p1 - 2.0 * p2 + p3);
let c = 3.0 * (p2 - p1);
let roots = if a == 0.0 {
if b == 0.0 {
ArrayVec::new()
} else {
let t = -c / b;
array_vec!([f32; 2] => t)
}
} else {
solve_quadratic(a, b, c)
};
roots
.into_iter()
.filter(|root| (0.0..=1.0).contains(root))
.collect()
}
fn solve_quadratic(a: f32, b: f32, c: f32) -> ArrayVec<[f32; 2]> {
let discriminant = b * b - 4.0 * a * c;
match discriminant.total_cmp(&0.0) {
Ordering::Less => ArrayVec::new(),
Ordering::Equal => array_vec!([f32; 2] => -0.5 * b / a),
Ordering::Greater => {
let sqrt_d = discriminant.sqrt();
match b.total_cmp(&0.0) {
Ordering::Less => {
let q = -0.5 * (b - sqrt_d);
ArrayVec::from([q / a, c / q])
}
Ordering::Equal => {
let root = -0.5 * sqrt_d / a;
ArrayVec::from([root, -root])
}
Ordering::Greater => {
let q = -0.5 * (b + sqrt_d);
ArrayVec::from([q / a, c / q])
}
}
}
}
}
impl From<GlyfPoint> for Vector2F {
fn from(point: GlyfPoint) -> Self {
Vector2F::new(f32::from(point.0), f32::from(point.1))
}
}
#[cfg(test)]
mod tests {
use pathfinder_geometry::vector::vec2f;
use crate::assert_close;
use super::*;
#[test]
fn test_solve_quadratic1() {
let res = solve_quadratic(6.0, 5.0, -4.0);
let &[root1, root2] = res.as_slice() else {
panic!("Expected two roots");
};
assert_close!(root1, -1.33333333);
assert_close!(root2, 0.5);
}
#[test]
fn test_solve_quadratic2() {
let res = solve_quadratic(1.0, 100000.0, 1.0);
let &[root1, root2] = res.as_slice() else {
panic!("Expected two roots");
};
assert_close!(root1, -100000.0);
assert_close!(root2, -0.00001);
}
#[test]
fn bezier_coefficients() {
let actual = BezierCoefficients::new(
vec2f(110., 150.),
vec2f(25., 190.),
vec2f(210., 250.),
vec2f(210., 30.),
);
let expected = [-455.0_f32, 810.0, -255.0, 110.0, -300.0, 60.0, 120.0, 150.0];
for (&a, &b) in actual.as_array().iter().zip(expected.iter()) {
assert_close!(a, b);
}
}
#[test]
fn quadratic_bbox() {
let mut sink = BoundingBoxSink::new();
let start = vec2f(82.148931, 87.063829);
sink.move_to(start);
let ctrl = vec2f(91.627661, 5.968085099999996);
let to = vec2f(103.56383, 64.595743);
sink.quadratic_curve_to(ctrl, to);
let bbox = sink.bbox.rect.unwrap();
assert_close!(bbox.origin_x(), 82.148931);
assert_close!(bbox.origin_y(), 39.995697);
assert_close!(bbox.width(), 103.56383 - 82.148931);
assert_close!(bbox.height(), 87.063829 - 39.995697);
}
}