use yog_graphics::{Color, Fill, Op, Seg, Stroke};
use crate::format::*;
use crate::{raster, shape};
fn rect(x0: f32, y0: f32, x1: f32, y1: f32) -> Vec<Seg> {
vec![
Seg::Move { x: x0, y: y0 },
Seg::Line { x: x1, y: y0 },
Seg::Line { x: x1, y: y1 },
Seg::Line { x: x0, y: y1 },
Seg::Close,
]
}
fn glyph(name: &str, advance: i32, ops: Vec<Op>) -> Glyph {
Glyph {
name: name.into(),
advance,
ops,
entry: None,
exit: None,
}
}
fn latin() -> Font {
Font {
units_per_em: 100,
ascent: 80,
descent: -20,
glyphs: vec![
glyph(
"a",
100,
vec![Op::Fill(Fill {
path: rect(10.0, 0.0, 90.0, 80.0),
color: None,
})],
),
glyph("b", 50, vec![]),
glyph("a_b", 300, vec![]),
],
chars: vec![
CharGlyph {
char: 'A' as u32,
glyph: 0,
},
CharGlyph {
char: 'B' as u32,
glyph: 1,
},
],
ligatures: vec![Ligature {
glyphs: vec![0, 1],
glyph: 2,
}],
kerning: vec![Kern {
left: 0,
right: 0,
x: -10,
}],
..Font::default()
}
}
#[test]
fn characters_become_glyphs_ligatures_and_kerning() {
let f = latin();
let s = shape(&f, "AAB?");
assert_eq!(
s.glyphs.iter().map(|p| p.glyph).collect::<Vec<_>>(),
vec![0, 2]
);
assert_eq!(
s.glyphs.iter().map(|p| p.char).collect::<Vec<_>>(),
vec![0, 1]
);
assert_eq!(s.missing, vec![3]);
assert_eq!(s.advance, 100.0 + 300.0);
let k = shape(&f, "AA");
assert_eq!((k.glyphs[1].x, k.advance), (90.0, 190.0));
}
fn runes() -> Font {
let stroke = |name: &str, dx: i32, dy: i32| Glyph {
name: name.into(),
advance: 10,
ops: vec![Op::Stroke(Stroke {
path: vec![
Seg::Move { x: 0.0, y: 0.0 },
Seg::Line {
x: dx as f32,
y: dy as f32,
},
],
width: 10.0,
color: None,
})],
entry: Some([0, 0]),
exit: Some([dx, dy]),
};
let mut glyphs = vec![
stroke("e", 100, 0),
stroke("n", 0, 100),
stroke("w", -100, 0),
stroke("s", 0, -100),
];
glyphs.push(glyph(
"letter_w",
60,
vec![Op::Fill(Fill {
path: rect(0.0, 0.0, 50.0, 50.0),
color: None,
})],
));
glyphs.push(glyph(
"letter_q",
60,
vec![Op::Fill(Fill {
path: rect(0.0, 0.0, 50.0, 50.0),
color: None,
})],
));
let (start, straight, left) = (1, 2, 3);
let mut transitions = Vec::new();
for state in 0..=4 {
transitions.push(Transition {
state,
class: start,
next: 1,
glyph: None,
});
}
for h in 0..4u32 {
transitions.push(Transition {
state: h + 1,
class: straight,
next: h + 1,
glyph: Some(h),
});
let l = (h + 1) % 4;
transitions.push(Transition {
state: h + 1,
class: left,
next: l + 1,
glyph: Some(l),
});
}
Font {
units_per_em: 100,
ascent: 80,
descent: -20,
glyphs,
chars: vec![
CharGlyph {
char: '>' as u32,
glyph: 0,
},
CharGlyph {
char: 'w' as u32,
glyph: 4,
},
CharGlyph {
char: 'q' as u32,
glyph: 5,
},
],
machines: vec![Machine {
classes: vec![
GlyphClass {
class: start,
glyphs: vec![0],
},
GlyphClass {
class: straight,
glyphs: vec![4],
},
GlyphClass {
class: left,
glyphs: vec![5],
},
],
transitions,
}],
fit: Some(Fit {
height: 60.0,
center: 30.0,
}),
..Font::default()
}
}
#[test]
fn a_script_of_strokes_turns_and_joins() {
let f = runes();
let s = shape(&f, ">wqq");
assert_eq!(
s.glyphs.iter().map(|p| p.glyph).collect::<Vec<_>>(),
vec![0, 0, 1, 2]
);
let unscaled: Vec<(f32, f32)> = s
.glyphs
.iter()
.map(|p| ((p.x - s.glyphs[0].x) / p.scale, p.y / p.scale))
.collect();
let rel: Vec<(f32, f32)> = unscaled
.iter()
.map(|(x, y)| (*x, y - unscaled[0].1))
.collect();
assert_eq!(
rel,
vec![(0.0, 0.0), (100.0, 0.0), (200.0, 0.0), (200.0, 100.0)]
);
let scale = s.glyphs[0].scale;
assert!((scale - 60.0 / 110.0).abs() < 1e-4, "{scale}");
let (lo, hi) = (s.glyphs[0].y - 5.0 * scale, s.glyphs[3].y + 5.0 * scale);
assert!(((lo + hi) / 2.0 - 30.0).abs() < 1e-3, "{lo} {hi}");
assert!(
(s.advance - (210.0 * scale + 10.0)).abs() < 1e-3,
"{}",
s.advance
);
}
#[test]
fn without_its_start_a_rune_letter_stands_alone() {
let f = runes();
let s = shape(&f, "q>w");
assert_eq!(
s.glyphs.iter().map(|p| p.glyph).collect::<Vec<_>>(),
vec![5, 0, 0]
);
assert_eq!(s.glyphs[0].x, 0.0);
assert!(s.glyphs[1].x >= 60.0);
}
#[test]
fn glyphs_draw_filled_and_stroked() {
let f = latin();
let a = raster::glyph(&f, 0, 1.0, Color::WHITE);
assert_eq!((a.x, a.y, a.image.width, a.image.height), (9, -81, 82, 82));
assert_eq!(a.image.pixel(41, 41), Color::WHITE);
assert_eq!(a.image.pixel(0, 0).a(), 0);
let r = runes();
let s = raster::glyph(&r, 0, 1.0, Color::rgb(0xff0000));
let (x, base) = ((50 - s.x) as u32, (-s.y) as u32);
assert_eq!(s.image.pixel(x, base), Color::rgb(0xff0000));
assert_eq!(s.image.pixel(x, base - 4).a(), 255);
assert_eq!(s.image.pixel(x, 0).a(), 0);
}
#[test]
fn a_run_draws_whole_and_a_font_survives_its_file() {
let f = runes();
let s = shape(&f, ">wqq");
let b = raster::run(&f, &s, 1.0, Color::WHITE);
assert!(b.image.width > 0 && b.image.rgba.chunks(4).any(|p| p[3] == 255));
assert_eq!(Font::from_bytes(&f.to_bytes()).unwrap(), f);
}
#[test]
fn invalid_font_references_are_reported() {
let mut f = latin();
f.chars[0].glyph = 99;
f.ligatures[0].glyphs = vec![0];
let problems = f.validate().unwrap_err();
assert!(problems.iter().any(|p| p.contains("chars[0].glyph 99")));
assert!(problems.iter().any(|p| p.contains("at least two glyphs")));
assert!(Font::from_bytes(&f.to_bytes()).is_err());
}