use ab_glyph_rasterizer::{Point, Rasterizer, point};
use cranpose_ui_graphics::{Point as PixelPoint, whole};
use read_fonts::{
model::glyph::outline::{DrawMemory, DrawOptions, PathSink},
types::GlyphId as FontGlyphId,
};
use crate::{glyph_id::GlyphId, simple_glyf, typeface::Typeface};
pub struct GlyphOutline {
segments: Vec<Segment>,
left: i32,
top: i32,
width: u32,
height: u32,
}
#[derive(Clone, Copy)]
enum Segment {
Line(Point, Point),
Quad(Point, Point, Point),
Cubic(Point, Point, Point, Point),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OutlineSegment {
Line(PixelPoint, PixelPoint),
Quad(PixelPoint, PixelPoint, PixelPoint),
Cubic(PixelPoint, PixelPoint, PixelPoint, PixelPoint),
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct OutlineTransform {
pub size: f32,
pub x_offset: f32,
pub y_offset: f32,
pub skew: f32,
}
impl GlyphOutline {
pub fn of(typeface: &Typeface, glyph: GlyphId, transform: OutlineTransform) -> Option<Self> {
let font = typeface.font();
let glyph = FontGlyphId::new(glyph.0);
let mut path = PathCollector {
segments: Vec::new(),
start: point(0.0, 0.0),
pen: point(0.0, 0.0),
offset: point(transform.x_offset, transform.y_offset),
scale: transform.size / f32::from(typeface.units_per_em()),
skew: transform.skew,
};
let simple = typeface
.glyf_tables()
.and_then(|tables| simple_glyf::draw(font, tables, glyph, &mut path));
if simple.is_none() {
path.segments.clear();
font.outline_glyphs()
.get(glyph)?
.draw(
&DrawOptions::new().with_ppem(None),
DrawMemory::Scratch,
&mut path,
)
.ok()?;
}
let (min, max) = path.bounds()?;
let (left, top) = (whole::floor(min.x), whole::floor(min.y));
let width = pixels(whole::ceil(max.x) - left);
let height = pixels(whole::ceil(max.y) - top);
let corner = point(left, top);
let moved = |at: Point| point(at.x - corner.x, at.y - corner.y);
for segment in &mut path.segments {
*segment = match *segment {
Segment::Line(a, b) => Segment::Line(moved(a), moved(b)),
Segment::Quad(a, b, c) => Segment::Quad(moved(a), moved(b), moved(c)),
Segment::Cubic(a, b, c, d) => {
Segment::Cubic(moved(a), moved(b), moved(c), moved(d))
}
};
}
Some(Self {
segments: path.segments,
left: whole::to_i32(left),
top: whole::to_i32(top),
width,
height,
})
}
pub fn left(&self) -> i32 {
self.left
}
pub fn top(&self) -> i32 {
self.top
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn segments(&self) -> impl ExactSizeIterator<Item = OutlineSegment> + '_ {
let pixel = |at: Point| PixelPoint::new(at.x, at.y);
self.segments.iter().map(move |segment| match *segment {
Segment::Line(a, b) => OutlineSegment::Line(pixel(a), pixel(b)),
Segment::Quad(a, b, c) => OutlineSegment::Quad(pixel(a), pixel(b), pixel(c)),
Segment::Cubic(a, b, c, d) => {
OutlineSegment::Cubic(pixel(a), pixel(b), pixel(c), pixel(d))
}
})
}
pub fn draw(&self, rasterizer: &mut Rasterizer, visit: impl FnMut(u32, u32, f32)) {
rasterizer.reset(usize_of(self.width), usize_of(self.height));
for segment in &self.segments {
match *segment {
Segment::Line(a, b) => rasterizer.draw_line(a, b),
Segment::Quad(a, b, c) => rasterizer.draw_quad(a, b, c),
Segment::Cubic(a, b, c, d) => rasterizer.draw_cubic(a, b, c, d),
}
}
rasterizer.for_each_pixel_2d(visit);
}
}
pub(crate) struct PathCollector {
segments: Vec<Segment>,
start: Point,
pen: Point,
offset: Point,
scale: f32,
skew: f32,
}
impl PathCollector {
pub(crate) fn reserve(&mut self, segments: usize) {
self.segments.reserve(segments);
}
fn point(&self, x: f32, y: f32) -> Point {
point(
(x + y * self.skew) * self.scale + self.offset.x,
-y * self.scale + self.offset.y,
)
}
fn bounds(&self) -> Option<(Point, Point)> {
let mut points = self.segments.iter().flat_map(|segment| match *segment {
Segment::Line(a, b) => [a, b, b, b],
Segment::Quad(a, b, c) => [a, b, c, c],
Segment::Cubic(a, b, c, d) => [a, b, c, d],
});
let first = points.next()?;
Some(points.fold((first, first), |(min, max), at| {
(
point(min.x.min(at.x), min.y.min(at.y)),
point(max.x.max(at.x), max.y.max(at.y)),
)
}))
}
}
impl PathSink for PathCollector {
fn move_to(&mut self, x: f32, y: f32) {
self.start = self.point(x, y);
self.pen = self.start;
}
fn line_to(&mut self, x: f32, y: f32) {
let to = self.point(x, y);
self.segments.push(Segment::Line(self.pen, to));
self.pen = to;
}
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
let to = self.point(x, y);
self.segments
.push(Segment::Quad(self.pen, self.point(cx0, cy0), to));
self.pen = to;
}
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
let to = self.point(x, y);
self.segments.push(Segment::Cubic(
self.pen,
self.point(cx0, cy0),
self.point(cx1, cy1),
to,
));
self.pen = to;
}
fn close(&mut self) {
if self.pen != self.start {
self.segments.push(Segment::Line(self.pen, self.start));
}
self.pen = self.start;
}
}
fn pixels(value: f32) -> u32 {
u32::try_from(whole::to_i32(value).max(0)).unwrap_or(0)
}
fn usize_of(value: u32) -> usize {
usize::try_from(value).unwrap_or(0)
}