use crate::{
from_obj::{FromObjRef, FromTableRef, ToOwnedTable},
util::{self, MultiZip, WrappingGet},
FontWrite, OtRound,
};
use kurbo::BezPath;
use read_fonts::{
tables::glyf::{CurvePoint, SimpleGlyphFlags},
FontRead,
};
use super::Bbox;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SimpleGlyph {
pub bbox: Bbox,
contours: Vec<Contour>,
_instructions: Vec<u8>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Contour(Vec<CurvePoint>);
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum MalformedPath {
HasCubic,
TooSmall,
MissingMove,
UnequalNumberOfElements(Vec<usize>),
InconsistentPathElements(usize, Vec<&'static str>),
}
impl SimpleGlyph {
pub fn from_bezpath(path: &BezPath) -> Result<Self, MalformedPath> {
Self::interpolatable_glyphs_from_bezpaths(std::slice::from_ref(path))
.map(|mut x| x.pop().unwrap())
}
pub fn interpolatable_glyphs_from_bezpaths(
paths: &[BezPath],
) -> Result<Vec<Self>, MalformedPath> {
simple_glyphs_from_kurbo(paths)
}
fn compute_point_deltas(
&self,
) -> impl Iterator<Item = (SimpleGlyphFlags, CoordDelta, CoordDelta)> + '_ {
fn flag_and_delta(
value: i16,
short_flag: SimpleGlyphFlags,
same_or_pos: SimpleGlyphFlags,
) -> (SimpleGlyphFlags, CoordDelta) {
const SHORT_MAX: i16 = u8::MAX as i16;
const SHORT_MIN: i16 = -SHORT_MAX;
match value {
0 => (same_or_pos, CoordDelta::Skip),
SHORT_MIN..=-1 => (short_flag, CoordDelta::Short(value.unsigned_abs() as u8)),
1..=SHORT_MAX => (short_flag | same_or_pos, CoordDelta::Short(value as _)),
_other => (SimpleGlyphFlags::empty(), CoordDelta::Long(value)),
}
}
let (mut last_x, mut last_y) = (0, 0);
let mut iter = self.contours.iter().flat_map(|c| c.iter());
std::iter::from_fn(move || {
let point = iter.next()?;
let mut flag = SimpleGlyphFlags::empty();
let d_x = point.x - last_x;
let d_y = point.y - last_y;
last_x = point.x;
last_y = point.y;
if point.on_curve {
flag |= SimpleGlyphFlags::ON_CURVE_POINT;
}
let (x_flag, x_data) = flag_and_delta(
d_x,
SimpleGlyphFlags::X_SHORT_VECTOR,
SimpleGlyphFlags::X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR,
);
let (y_flag, y_data) = flag_and_delta(
d_y,
SimpleGlyphFlags::Y_SHORT_VECTOR,
SimpleGlyphFlags::Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR,
);
flag |= x_flag | y_flag;
Some((flag, x_data, y_data))
})
}
pub fn contours(&self) -> &[Contour] {
&self.contours
}
}
impl Contour {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &CurvePoint> {
self.0.iter()
}
}
impl MalformedPath {
fn inconsistent_path_els(idx: usize, elements: &[kurbo::PathEl]) -> Self {
fn el_types(elements: &[kurbo::PathEl]) -> Vec<&'static str> {
elements
.iter()
.map(|el| match el {
kurbo::PathEl::MoveTo(_) => "M",
kurbo::PathEl::LineTo(_) => "L",
kurbo::PathEl::QuadTo(_, _) => "Q",
kurbo::PathEl::CurveTo(_, _, _) => "C",
kurbo::PathEl::ClosePath => "Z",
})
.collect()
}
MalformedPath::InconsistentPathElements(idx, el_types(elements))
}
}
#[derive(Clone, Copy, Debug)]
enum CoordDelta {
Skip,
Short(u8),
Long(i16),
}
impl FontWrite for CoordDelta {
fn write_into(&self, writer: &mut crate::TableWriter) {
match self {
CoordDelta::Skip => (),
CoordDelta::Short(val) => val.write_into(writer),
CoordDelta::Long(val) => val.write_into(writer),
}
}
}
impl<'a> FromObjRef<read_fonts::tables::glyf::SimpleGlyph<'a>> for SimpleGlyph {
fn from_obj_ref(
from: &read_fonts::tables::glyf::SimpleGlyph,
_data: read_fonts::FontData,
) -> Self {
let bbox = Bbox {
x_min: from.x_min(),
y_min: from.y_min(),
x_max: from.x_max(),
y_max: from.y_max(),
};
let mut points = from.points();
let mut last_end = 0;
let mut contours = vec![];
for end_pt in from.end_pts_of_contours() {
let end = end_pt.get() as usize + 1;
let count = end - last_end;
last_end = end;
contours.push(Contour(points.by_ref().take(count).collect()));
}
Self {
bbox,
contours,
_instructions: from.instructions().to_owned(),
}
}
}
impl<'a> FromTableRef<read_fonts::tables::glyf::SimpleGlyph<'a>> for SimpleGlyph {}
impl<'a> FontRead<'a> for SimpleGlyph {
fn read(data: read_fonts::FontData<'a>) -> Result<Self, read_fonts::ReadError> {
read_fonts::tables::glyf::SimpleGlyph::read(data).map(|g| g.to_owned_table())
}
}
impl FontWrite for SimpleGlyph {
fn write_into(&self, writer: &mut crate::TableWriter) {
assert!(self.contours.len() < i16::MAX as usize);
assert!(self._instructions.len() < u16::MAX as usize);
let n_contours = self.contours.len() as i16;
if n_contours == 0 {
return;
}
n_contours.write_into(writer);
self.bbox.write_into(writer);
let mut cur = 0;
for contour in &self.contours {
cur += contour.len();
(cur as u16 - 1).write_into(writer);
}
(self._instructions.len() as u16).write_into(writer);
self._instructions.write_into(writer);
let deltas = self.compute_point_deltas().collect::<Vec<_>>();
RepeatableFlag::iter_from_flags(deltas.iter().map(|(flag, _, _)| *flag))
.for_each(|flag| flag.write_into(writer));
deltas.iter().for_each(|(_, x, _)| x.write_into(writer));
deltas.iter().for_each(|(_, _, y)| y.write_into(writer));
writer.pad_to_2byte_aligned();
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct RepeatableFlag {
flag: SimpleGlyphFlags,
repeat: u8,
}
impl FontWrite for RepeatableFlag {
fn write_into(&self, writer: &mut crate::TableWriter) {
debug_assert_eq!(
self.flag.contains(SimpleGlyphFlags::REPEAT_FLAG),
self.repeat > 0
);
self.flag.bits().write_into(writer);
if self.flag.contains(SimpleGlyphFlags::REPEAT_FLAG) {
self.repeat.write_into(writer);
}
}
}
impl RepeatableFlag {
fn iter_from_flags(
flags: impl IntoIterator<Item = SimpleGlyphFlags>,
) -> impl Iterator<Item = RepeatableFlag> {
let mut iter = flags.into_iter();
let mut prev = None;
let mut decompose_single_repeat = None;
std::iter::from_fn(move || loop {
if let Some(repeat) = decompose_single_repeat.take() {
return Some(repeat);
}
match (iter.next(), prev.take()) {
(None, Some(RepeatableFlag { flag, repeat: 1 })) => {
let flag = flag & !SimpleGlyphFlags::REPEAT_FLAG;
decompose_single_repeat = Some(RepeatableFlag { flag, repeat: 0 });
return decompose_single_repeat;
}
(None, prev) => return prev,
(Some(flag), None) => prev = Some(RepeatableFlag { flag, repeat: 0 }),
(Some(flag), Some(mut last)) => {
if (last.flag & !SimpleGlyphFlags::REPEAT_FLAG) == flag && last.repeat < u8::MAX
{
last.repeat += 1;
last.flag |= SimpleGlyphFlags::REPEAT_FLAG;
prev = Some(last);
} else {
if last.repeat == 1 {
last.flag &= !SimpleGlyphFlags::REPEAT_FLAG;
last.repeat = 0;
decompose_single_repeat = Some(last);
}
prev = Some(RepeatableFlag { flag, repeat: 0 });
return Some(last);
}
}
}
})
}
}
impl crate::validate::Validate for SimpleGlyph {
fn validate_impl(&self, ctx: &mut crate::codegen_prelude::ValidationCtx) {
if self._instructions.len() > u16::MAX as usize {
ctx.report("instructions len overflows");
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct ContourPoint {
point: kurbo::Point,
on_curve: bool,
}
impl ContourPoint {
fn new(point: kurbo::Point, on_curve: bool) -> Self {
Self { point, on_curve }
}
fn on_curve(point: kurbo::Point) -> Self {
Self::new(point, true)
}
fn off_curve(point: kurbo::Point) -> Self {
Self::new(point, false)
}
}
impl From<ContourPoint> for CurvePoint {
fn from(pt: ContourPoint) -> Self {
let (x, y) = pt.point.ot_round();
CurvePoint::new(x, y, pt.on_curve)
}
}
#[derive(Clone, Debug, PartialEq)]
struct InterpolatableContourBuilder(Vec<Vec<ContourPoint>>);
impl InterpolatableContourBuilder {
fn new(move_pts: &[kurbo::Point]) -> Self {
assert!(!move_pts.is_empty());
Self(
move_pts
.iter()
.map(|pt| vec![ContourPoint::on_curve(*pt)])
.collect(),
)
}
fn len(&self) -> usize {
self.0.len()
}
fn line_to(&mut self, pts: &[kurbo::Point]) {
assert_eq!(pts.len(), self.len());
for (i, pt) in pts.iter().enumerate() {
self.0[i].push(ContourPoint::on_curve(*pt));
}
}
fn quad_to(&mut self, pts: &[(kurbo::Point, kurbo::Point)]) {
for (i, (p0, p1)) in pts.iter().enumerate() {
self.0[i].push(ContourPoint::off_curve(*p0));
self.0[i].push(ContourPoint::on_curve(*p1));
}
}
fn num_points(&self) -> usize {
let n = self.0[0].len();
assert!(self.0.iter().all(|c| c.len() == n));
n
}
fn first(&self) -> impl Iterator<Item = &ContourPoint> {
self.0.iter().map(|v| v.first().unwrap())
}
fn last(&self) -> impl Iterator<Item = &ContourPoint> {
self.0.iter().map(|v| v.last().unwrap())
}
fn remove_last(&mut self) {
self.0.iter_mut().for_each(|c| {
c.pop().unwrap();
});
}
fn is_implicit_on_curve(&self, idx: usize) -> bool {
self.0
.iter()
.all(|points| is_implicit_on_curve(points, idx))
}
fn build(self) -> Vec<Contour> {
let num_contours = self.len();
let num_points = self.num_points();
let mut contours = vec![Contour::default(); num_contours];
contours.iter_mut().for_each(|c| c.0.reserve(num_points));
for point_idx in (0..num_points).filter(|point_idx| !self.is_implicit_on_curve(*point_idx))
{
for (contour_idx, contour) in contours.iter_mut().enumerate() {
contour
.0
.push(CurvePoint::from(self.0[contour_idx][point_idx]));
}
}
contours
}
}
#[inline]
fn is_mid_point(p0: kurbo::Point, p1: kurbo::Point, p2: kurbo::Point) -> bool {
let mid = p0.midpoint(p2);
(util::isclose(mid.x, p1.x) && util::isclose(mid.y, p1.y))
|| p0.to_vec2().ot_round() + p2.to_vec2().ot_round() == p1.to_vec2().ot_round() * 2.0
}
fn is_implicit_on_curve(points: &[ContourPoint], idx: usize) -> bool {
let p1 = &points[idx]; if !p1.on_curve {
return false;
}
let p0 = points.wrapping_prev(idx);
let p2 = points.wrapping_next(idx);
if p0.on_curve || p0.on_curve != p2.on_curve {
return false;
}
is_mid_point(p0.point, p1.point, p2.point)
}
fn simple_glyphs_from_kurbo(paths: &[BezPath]) -> Result<Vec<SimpleGlyph>, MalformedPath> {
let num_elements: Vec<usize> = paths.iter().map(|path| path.elements().len()).collect();
if num_elements.iter().any(|n| *n != num_elements[0]) {
return Err(MalformedPath::UnequalNumberOfElements(num_elements));
}
let path_iters = MultiZip::new(paths.iter().map(|path| path.iter()).collect());
let mut contours: Vec<InterpolatableContourBuilder> = Vec::new();
let mut current: Option<InterpolatableContourBuilder> = None;
let num_glyphs = paths.len();
let mut pts = Vec::with_capacity(num_glyphs);
let mut quad_pts = Vec::with_capacity(num_glyphs);
for (i, elements) in path_iters.enumerate() {
let first_el = elements.first().unwrap();
match first_el {
kurbo::PathEl::MoveTo(_) => {
if let Some(prev) = current.take() {
contours.push(prev);
}
pts.clear();
for el in &elements {
match el {
&kurbo::PathEl::MoveTo(pt) => {
pts.push(pt);
}
_ => return Err(MalformedPath::inconsistent_path_els(i, &elements)),
}
}
current = Some(InterpolatableContourBuilder::new(&pts));
}
kurbo::PathEl::LineTo(_) => {
pts.clear();
for el in &elements {
match el {
&kurbo::PathEl::LineTo(pt) => {
pts.push(pt);
}
_ => return Err(MalformedPath::inconsistent_path_els(i, &elements)),
}
}
current
.as_mut()
.ok_or(MalformedPath::MissingMove)?
.line_to(&pts)
}
kurbo::PathEl::QuadTo(_, _) => {
quad_pts.clear();
for el in &elements {
match el {
&kurbo::PathEl::QuadTo(p0, p1) => {
quad_pts.push((p0, p1));
}
_ => return Err(MalformedPath::inconsistent_path_els(i, &elements)),
}
}
current
.as_mut()
.ok_or(MalformedPath::MissingMove)?
.quad_to(&quad_pts)
}
kurbo::PathEl::CurveTo(_, _, _) => return Err(MalformedPath::HasCubic),
kurbo::PathEl::ClosePath => {
let contour = current.as_mut().ok_or(MalformedPath::MissingMove)?;
if contour.num_points() > 1 && contour.last().eq(contour.first()) {
contour.remove_last();
}
}
}
}
contours.extend(current);
let mut glyph_contours = vec![Vec::new(); num_glyphs];
for builder in contours {
assert_eq!(builder.len(), num_glyphs);
for (i, contour) in builder.build().into_iter().enumerate() {
glyph_contours[i].push(contour);
}
}
let mut glyphs = Vec::new();
for (contours, path) in glyph_contours.into_iter().zip(paths.iter()) {
glyphs.push(SimpleGlyph {
bbox: path.control_box().into(),
contours,
_instructions: Default::default(),
})
}
Ok(glyphs)
}
#[cfg(test)]
mod tests {
use font_types::GlyphId;
use kurbo::Affine;
use read_fonts::{tables::glyf as read_glyf, FontRef, TableProvider};
use super::*;
fn pad_for_loca_format(loca: &read_fonts::tables::loca::Loca, mut bytes: Vec<u8>) -> Vec<u8> {
if matches!(loca, read_fonts::tables::loca::Loca::Short(_)) && bytes.len() & 1 != 0 {
bytes.push(0);
}
bytes
}
fn simple_glyph_to_bezpath(glyph: &read_fonts::tables::glyf::SimpleGlyph) -> BezPath {
use types::{F26Dot6, Pen};
#[derive(Default)]
struct Path(BezPath);
impl Pen for Path {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to((x as f64, y as f64));
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to((x as f64, y as f64));
}
fn quad_to(&mut self, x0: f32, y0: f32, x1: f32, y1: f32) {
self.0
.quad_to((x0 as f64, y0 as f64), (x1 as f64, y1 as f64));
}
fn curve_to(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, x2: f32, y2: f32) {
self.0.curve_to(
(x0 as f64, y0 as f64),
(x1 as f64, y1 as f64),
(x2 as f64, y2 as f64),
);
}
fn close(&mut self) {
self.0.close_path();
}
}
let contours = glyph
.end_pts_of_contours()
.iter()
.map(|x| x.get())
.collect::<Vec<_>>();
let num_points = glyph.num_points();
let mut points = vec![Default::default(); num_points];
let mut flags = vec![Default::default(); num_points];
glyph.read_points_fast(&mut points, &mut flags).unwrap();
let points = points
.into_iter()
.map(|point| point.map(F26Dot6::from_i32))
.collect::<Vec<_>>();
let mut path = Path::default();
read_fonts::tables::glyf::to_path(&points, &flags, &contours, &mut path).unwrap();
path.0
}
#[test]
fn bad_path_input() {
let mut path = BezPath::new();
path.move_to((0., 0.));
path.curve_to((10., 10.), (20., 20.), (30., 30.));
path.line_to((50., 50.));
path.line_to((10., 10.));
let err = SimpleGlyph::from_bezpath(&path).unwrap_err();
assert!(matches!(err, MalformedPath::HasCubic));
}
#[test]
fn read_write_simple() {
let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
let loca = font.loca(None).unwrap();
let glyf = font.glyf().unwrap();
let read_glyf::Glyph::Simple(orig) =
loca.get_glyf(GlyphId::new(0), &glyf).unwrap().unwrap()
else {
panic!("not a simple glyph")
};
let orig_bytes = orig.offset_data();
let ours = SimpleGlyph::from_table_ref(&orig);
let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
let our_points = ours.points().collect::<Vec<_>>();
let their_points = orig.points().collect::<Vec<_>>();
assert_eq!(our_points, their_points);
assert_eq!(orig_bytes.as_ref(), bytes);
assert_eq!(orig.glyph_data(), ours.glyph_data());
assert_eq!(orig_bytes.len(), bytes.len());
}
#[test]
fn round_trip_simple() {
let font = FontRef::new(font_test_data::SIMPLE_GLYF).unwrap();
let loca = font.loca(None).unwrap();
let glyf = font.glyf().unwrap();
let read_glyf::Glyph::Simple(orig) =
loca.get_glyf(GlyphId::new(2), &glyf).unwrap().unwrap()
else {
panic!("not a simple glyph")
};
let orig_bytes = orig.offset_data();
let bezpath = simple_glyph_to_bezpath(&orig);
let ours = SimpleGlyph::from_bezpath(&bezpath).unwrap();
let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
let our_points = ours.points().collect::<Vec<_>>();
let their_points = orig.points().collect::<Vec<_>>();
assert_eq!(our_points, their_points);
assert_eq!(orig_bytes.as_ref(), bytes);
assert_eq!(orig.glyph_data(), ours.glyph_data());
assert_eq!(orig_bytes.len(), bytes.len());
}
#[test]
fn round_trip_multi_contour() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let loca = font.loca(None).unwrap();
let glyf = font.glyf().unwrap();
let read_glyf::Glyph::Simple(orig) =
loca.get_glyf(GlyphId::new(1), &glyf).unwrap().unwrap()
else {
panic!("not a simple glyph")
};
let orig_bytes = orig.offset_data();
let bezpath = simple_glyph_to_bezpath(&orig);
let ours = SimpleGlyph::from_bezpath(&bezpath).unwrap();
let bytes = pad_for_loca_format(&loca, crate::dump_table(&ours).unwrap());
let ours = read_glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
let our_points = ours.points().collect::<Vec<_>>();
let their_points = orig.points().collect::<Vec<_>>();
dbg!(
SimpleGlyphFlags::from_bits(1),
SimpleGlyphFlags::from_bits(9)
);
assert_eq!(our_points, their_points);
assert_eq!(orig.glyph_data(), ours.glyph_data());
assert_eq!(orig_bytes.len(), bytes.len());
assert_eq!(orig_bytes.as_ref(), bytes);
}
#[test]
fn simple_glyph_open_path() {
let mut path = BezPath::new();
path.move_to((20., -100.));
path.quad_to((1337., 1338.), (-50., -69.0));
path.quad_to((13., 255.), (-255., 256.));
path.line_to((20., -100.));
let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
let bytes = crate::dump_table(&glyph).unwrap();
let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
assert_eq!(read.number_of_contours(), 1);
assert_eq!(read.num_points(), 6);
assert_eq!(read.end_pts_of_contours(), &[5]);
let points = read.points().collect::<Vec<_>>();
assert_eq!(points[0].x, 20);
assert_eq!(points[0].y, -100);
assert!(points[0].on_curve);
assert_eq!(points[1].x, 1337);
assert_eq!(points[1].y, 1338);
assert!(!points[1].on_curve);
assert_eq!(points[4].x, -255);
assert_eq!(points[4].y, 256);
assert!(points[4].on_curve);
assert_eq!(points[5].x, 20);
assert_eq!(points[5].y, -100);
assert!(points[5].on_curve);
}
#[test]
fn simple_glyph_closed_path_implicit_vs_explicit_closing_line() {
let mut path1 = BezPath::new();
path1.move_to((20., -100.));
path1.quad_to((1337., 1338.), (-50., -69.0));
path1.quad_to((13., 255.), (-255., 256.));
path1.close_path();
let mut path2 = BezPath::new();
path2.move_to((20., -100.));
path2.quad_to((1337., 1338.), (-50., -69.0));
path2.quad_to((13., 255.), (-255., 256.));
path2.line_to((20., -100.));
path2.close_path();
for path in &[path1, path2] {
let glyph = SimpleGlyph::from_bezpath(path).unwrap();
let bytes = crate::dump_table(&glyph).unwrap();
let read =
read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
assert_eq!(read.number_of_contours(), 1);
assert_eq!(read.num_points(), 5);
assert_eq!(read.end_pts_of_contours(), &[4]);
let points = read.points().collect::<Vec<_>>();
assert_eq!(points[0].x, 20);
assert_eq!(points[0].y, -100);
assert!(points[0].on_curve);
assert_eq!(points[1].x, 1337);
assert_eq!(points[1].y, 1338);
assert!(!points[1].on_curve);
assert_eq!(points[4].x, -255);
assert_eq!(points[4].y, 256);
assert!(points[4].on_curve);
}
}
#[test]
fn keep_single_point_contours() {
let mut path = BezPath::new();
path.move_to((0.0, 0.0));
path.move_to((1.0, 2.0));
path.close_path();
let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
let bytes = crate::dump_table(&glyph).unwrap();
let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
assert_eq!(read.number_of_contours(), 2);
assert_eq!(read.num_points(), 2);
assert_eq!(read.end_pts_of_contours(), &[0, 1]);
let points = read.points().collect::<Vec<_>>();
assert_eq!(points[0].x, 0);
assert_eq!(points[0].y, 0);
assert!(points[0].on_curve);
assert_eq!(points[1].x, 1);
assert_eq!(points[1].y, 2);
assert!(points[0].on_curve);
}
#[test]
fn compile_repeatable_flags() {
let mut path = BezPath::new();
path.move_to((20., -100.));
path.line_to((25., -90.));
path.line_to((50., -69.));
path.line_to((80., -20.));
let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
let flags = glyph
.compute_point_deltas()
.map(|x| x.0)
.collect::<Vec<_>>();
let r_flags = RepeatableFlag::iter_from_flags(flags.iter().copied()).collect::<Vec<_>>();
assert_eq!(r_flags.len(), 2, "{r_flags:?}");
let bytes = crate::dump_table(&glyph).unwrap();
let read = read_fonts::tables::glyf::SimpleGlyph::read(bytes.as_slice().into()).unwrap();
assert_eq!(read.number_of_contours(), 1);
assert_eq!(read.num_points(), 4);
assert_eq!(read.end_pts_of_contours(), &[3]);
let points = read.points().collect::<Vec<_>>();
assert_eq!(points[0].x, 20);
assert_eq!(points[0].y, -100);
assert_eq!(points[1].x, 25);
assert_eq!(points[1].y, -90);
assert_eq!(points[2].x, 50);
assert_eq!(points[2].y, -69);
assert_eq!(points[3].x, 80);
assert_eq!(points[3].y, -20);
}
#[test]
fn simple_glyphs_from_kurbo_unequal_number_of_elements() {
let mut path1 = BezPath::new();
path1.move_to((0., 0.));
path1.line_to((1., 1.));
path1.line_to((2., 2.));
path1.line_to((0., 0.));
path1.close_path();
assert_eq!(path1.elements().len(), 5);
let mut path2 = BezPath::new();
path2.move_to((3., 3.));
path2.line_to((4., 4.));
path2.line_to((5., 5.));
path2.line_to((6., 6.));
path2.line_to((3., 3.));
path2.close_path();
assert_eq!(path2.elements().len(), 6);
let err = simple_glyphs_from_kurbo(&[path1, path2]).unwrap_err();
assert!(matches!(err, MalformedPath::UnequalNumberOfElements(_)));
assert_eq!(format!("{:?}", err), "UnequalNumberOfElements([5, 6])");
}
#[test]
fn simple_glyphs_from_kurbo_inconsistent_path_elements() {
let mut path1 = BezPath::new();
path1.move_to((0., 0.));
path1.line_to((1., 1.));
path1.quad_to((2., 2.), (0., 0.));
path1.close_path();
let mut path2 = BezPath::new();
path2.move_to((3., 3.));
path2.quad_to((4., 4.), (5., 5.)); path2.line_to((3., 3.));
path2.close_path();
let err = simple_glyphs_from_kurbo(&[path1, path2]).unwrap_err();
assert!(matches!(err, MalformedPath::InconsistentPathElements(1, _)));
assert_eq!(
format!("{:?}", err),
"InconsistentPathElements(1, [\"L\", \"Q\"])"
);
}
fn make_interpolatable_paths(
num_paths: usize,
el_types: &str,
last_pt_equal_move: bool,
) -> Vec<BezPath> {
let mut paths = Vec::new();
let mut start = 0.0;
let mut points = std::iter::from_fn(move || {
let value = start;
start += 1.0;
Some((value, value))
});
let el_types = el_types.chars().collect::<Vec<_>>();
assert!(!el_types.is_empty());
for _ in 0..num_paths {
let mut path = BezPath::new();
let mut start_pt = None;
let mut el_types_iter = el_types.iter().peekable();
while let Some(&el_type) = el_types_iter.next() {
let next_el_type = el_types_iter.peek().map(|x| **x).unwrap_or('M');
match el_type {
'M' => {
start_pt = points.next();
path.move_to(start_pt.unwrap());
}
'L' => {
if matches!(next_el_type, 'Z' | 'M') && last_pt_equal_move {
path.line_to(start_pt.unwrap());
} else {
path.line_to(points.next().unwrap());
}
}
'Q' => {
let p1 = points.next().unwrap();
let p2 = if matches!(next_el_type, 'Z' | 'M') && last_pt_equal_move {
start_pt.unwrap()
} else {
points.next().unwrap()
};
path.quad_to(p1, p2);
}
'Z' => {
path.close_path();
start_pt = None;
}
_ => panic!("Usupported element type {:?}", el_type),
}
}
paths.push(path);
}
assert_eq!(paths.len(), num_paths);
paths
}
fn assert_contour_points(glyph: &SimpleGlyph, all_points: Vec<Vec<CurvePoint>>) {
let expected_num_contours = all_points.len();
assert_eq!(glyph.contours().len(), expected_num_contours);
for (contour, expected_points) in glyph.contours().iter().zip(all_points.iter()) {
let points = contour.iter().copied().collect::<Vec<_>>();
assert_eq!(points, *expected_points);
}
}
#[test]
fn simple_glyphs_from_kurbo_3_lines_closed() {
let paths = make_interpolatable_paths(2, "MLLLZ", true);
let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::on_curve(1, 1),
CurvePoint::on_curve(2, 2),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(3, 3),
CurvePoint::on_curve(4, 4),
CurvePoint::on_curve(5, 5),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_3_lines_implicitly_closed() {
let paths = make_interpolatable_paths(2, "MLLZ", false);
let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::on_curve(1, 1),
CurvePoint::on_curve(2, 2),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(3, 3),
CurvePoint::on_curve(4, 4),
CurvePoint::on_curve(5, 5),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_2_quads_closed() {
let paths = make_interpolatable_paths(2, "MQQZ", true);
let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(1, 1),
CurvePoint::off_curve(3, 3),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(4, 4),
CurvePoint::off_curve(5, 5),
CurvePoint::off_curve(7, 7),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_2_quads_1_line_implictly_closed() {
let paths = make_interpolatable_paths(2, "MQQZ", false);
let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(1, 1),
CurvePoint::off_curve(3, 3),
CurvePoint::on_curve(4, 4),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(5, 5),
CurvePoint::off_curve(6, 6),
CurvePoint::off_curve(8, 8),
CurvePoint::on_curve(9, 9),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_multiple_contours_mixed_segments() {
let paths = make_interpolatable_paths(4, "MLQQZMQLQLZ", true);
let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
assert_contour_points(
&glyphs[0],
vec![
vec![
CurvePoint::on_curve(0, 0),
CurvePoint::on_curve(1, 1),
CurvePoint::off_curve(2, 2),
CurvePoint::off_curve(4, 4),
],
vec![
CurvePoint::on_curve(5, 5),
CurvePoint::off_curve(6, 6),
CurvePoint::on_curve(7, 7),
CurvePoint::on_curve(8, 8),
CurvePoint::off_curve(9, 9),
CurvePoint::on_curve(10, 10),
],
],
);
}
#[test]
fn simple_glyphs_from_kurbo_all_quad_off_curves() {
let mut path1 = BezPath::new();
path1.move_to((0.0, 1.0));
path1.quad_to((1.0, 1.0), (1.0, 0.0));
path1.quad_to((1.0, -1.0), (0.0, -1.0));
path1.quad_to((-1.0, -1.0), (-1.0, 0.0));
path1.quad_to((-1.0, 1.0), (0.0, 1.0));
path1.close_path();
let mut path2 = path1.clone();
path2.apply_affine(Affine::scale(2.0));
let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::off_curve(1, 1),
CurvePoint::off_curve(1, -1),
CurvePoint::off_curve(-1, -1),
CurvePoint::off_curve(-1, 1),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::off_curve(2, 2),
CurvePoint::off_curve(2, -2),
CurvePoint::off_curve(-2, -2),
CurvePoint::off_curve(-2, 2),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_keep_on_curve_unless_impliable_for_all() {
let mut path1 = BezPath::new();
path1.move_to((0.0, 0.0));
path1.quad_to((0.0, 1.0), (1.0, 1.0)); path1.quad_to((2.0, 1.0), (2.0, 0.0));
path1.line_to((0.0, 0.0));
path1.close_path();
assert_contour_points(
&SimpleGlyph::from_bezpath(&path1).unwrap(),
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(0, 1),
CurvePoint::off_curve(2, 1),
CurvePoint::on_curve(2, 0),
]],
);
let mut path2 = BezPath::new();
path2.move_to((0.0, 0.0));
path2.quad_to((0.0, 2.0), (2.0, 2.0)); path2.quad_to((3.0, 2.0), (3.0, 0.0));
path2.line_to((0.0, 0.0));
path2.close_path();
let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(0, 1),
CurvePoint::on_curve(1, 1), CurvePoint::off_curve(2, 1),
CurvePoint::on_curve(2, 0),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(0, 2),
CurvePoint::on_curve(2, 2), CurvePoint::off_curve(3, 2),
CurvePoint::on_curve(3, 0),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_2_lines_open() {
let paths = make_interpolatable_paths(2, "MLL", false);
let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::on_curve(1, 1),
CurvePoint::on_curve(2, 2),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(3, 3),
CurvePoint::on_curve(4, 4),
CurvePoint::on_curve(5, 5),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_3_lines_open_duplicate_last_pt() {
let paths = make_interpolatable_paths(2, "MLLL", true);
let glyphs = simple_glyphs_from_kurbo(&paths).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::on_curve(1, 1),
CurvePoint::on_curve(2, 2),
CurvePoint::on_curve(0, 0),
]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(3, 3),
CurvePoint::on_curve(4, 4),
CurvePoint::on_curve(5, 5),
CurvePoint::on_curve(3, 3),
]],
);
}
#[test]
fn simple_glyphs_from_kurbo_4_lines_closed_duplicate_last_pt() {
for implicit_closing_line in &[true, false] {
let mut path1 = BezPath::new();
path1.move_to((0.0, 0.0));
path1.line_to((0.0, 1.0));
path1.line_to((1.0, 1.0));
path1.line_to((0.0, 0.0));
if !*implicit_closing_line {
path1.line_to((0.0, 0.0));
}
path1.close_path();
let mut path2 = BezPath::new();
path2.move_to((0.0, 0.0));
path2.line_to((0.0, 2.0));
path2.line_to((2.0, 2.0));
path2.line_to((2.0, 0.0));
if !*implicit_closing_line {
path2.line_to((0.0, 0.0));
}
path2.close_path();
let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::on_curve(0, 1),
CurvePoint::on_curve(1, 1),
CurvePoint::on_curve(0, 0), ]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::on_curve(0, 2),
CurvePoint::on_curve(2, 2),
CurvePoint::on_curve(2, 0),
]],
);
}
}
#[test]
fn simple_glyphs_from_kurbo_2_quads_1_line_closed_duplicate_last_pt() {
for implicit_closing_line in &[true, false] {
let mut path1 = BezPath::new();
path1.move_to((0.0, 0.0));
path1.quad_to((0.0, 1.0), (1.0, 1.0));
path1.quad_to((1.0, 0.0), (0.0, 0.0));
if !*implicit_closing_line {
path1.line_to((0.0, 0.0));
}
path1.close_path();
let mut path2 = BezPath::new();
path2.move_to((0.0, 0.0));
path2.quad_to((0.0, 2.0), (2.0, 2.0));
path2.quad_to((2.0, 1.0), (1.0, 0.0));
if !*implicit_closing_line {
path2.line_to((0.0, 0.0));
}
path2.close_path();
let glyphs = simple_glyphs_from_kurbo(&[path1, path2]).unwrap();
assert_contour_points(
&glyphs[0],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(0, 1),
CurvePoint::on_curve(1, 1),
CurvePoint::off_curve(1, 0),
CurvePoint::on_curve(0, 0), ]],
);
assert_contour_points(
&glyphs[1],
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(0, 2),
CurvePoint::on_curve(2, 2),
CurvePoint::off_curve(2, 1),
CurvePoint::on_curve(1, 0),
]],
);
}
}
#[test]
fn simple_glyph_from_kurbo_equidistant_but_not_collinear_points() {
let mut path = BezPath::new();
path.move_to((0.0, 0.0));
path.quad_to((2.0, 2.0), (4.0, 3.0));
path.quad_to((6.0, 2.0), (8.0, 0.0));
path.close_path();
let glyph = SimpleGlyph::from_bezpath(&path).unwrap();
assert_contour_points(
&glyph,
vec![vec![
CurvePoint::on_curve(0, 0),
CurvePoint::off_curve(2, 2),
CurvePoint::on_curve(4, 3),
CurvePoint::off_curve(6, 2),
CurvePoint::on_curve(8, 0),
]],
);
}
#[test]
fn repeatable_flags_basic() {
let flags = [
SimpleGlyphFlags::ON_CURVE_POINT,
SimpleGlyphFlags::X_SHORT_VECTOR,
SimpleGlyphFlags::X_SHORT_VECTOR,
];
let repeatable = RepeatableFlag::iter_from_flags(flags).collect::<Vec<_>>();
let expected = flags
.into_iter()
.map(|flag| RepeatableFlag { flag, repeat: 0 })
.collect::<Vec<_>>();
assert_eq!(repeatable, expected);
}
#[test]
fn repeatable_flags_repeats() {
let some_dupes = std::iter::repeat(SimpleGlyphFlags::ON_CURVE_POINT).take(4);
let many_dupes = std::iter::repeat(SimpleGlyphFlags::Y_SHORT_VECTOR).take(257);
let repeatable =
RepeatableFlag::iter_from_flags(some_dupes.chain(many_dupes)).collect::<Vec<_>>();
assert_eq!(repeatable.len(), 3);
assert_eq!(
repeatable[0],
RepeatableFlag {
flag: SimpleGlyphFlags::ON_CURVE_POINT | SimpleGlyphFlags::REPEAT_FLAG,
repeat: 3
}
);
assert_eq!(
repeatable[1],
RepeatableFlag {
flag: SimpleGlyphFlags::Y_SHORT_VECTOR | SimpleGlyphFlags::REPEAT_FLAG,
repeat: u8::MAX,
}
);
assert_eq!(
repeatable[2],
RepeatableFlag {
flag: SimpleGlyphFlags::Y_SHORT_VECTOR,
repeat: 0,
}
)
}
#[test]
fn mid_points() {
assert!(is_mid_point(
kurbo::Point::new(0.0, 0.0),
kurbo::Point::new(1.0, 1.0),
kurbo::Point::new(2.0, 2.0)
));
assert!(is_mid_point(
kurbo::Point::new(0.5, 0.5),
kurbo::Point::new(3.0, 3.0),
kurbo::Point::new(5.5, 5.5)
));
assert!(is_mid_point(
kurbo::Point::new(0.0, 0.0),
kurbo::Point::new(1.00001, 0.99999),
kurbo::Point::new(2.0, 2.0)
));
assert!(is_mid_point(
kurbo::Point::new(0.0, 0.0),
kurbo::Point::new(-1.499999, 0.500001),
kurbo::Point::new(-2.0, 2.0)
));
assert!(!is_mid_point(
kurbo::Point::new(0.0, 0.0),
kurbo::Point::new(1.0, 1.5),
kurbo::Point::new(2.0, 2.0)
));
}
}