use std::fs;
use ttf_parser as ttf;
use ttf::{Font, GlyphId};
struct Builder(svgtypes::Path);
impl Builder {
fn new() -> Self {
Builder(svgtypes::Path::new())
}
}
impl ttf::OutlineBuilder for Builder {
fn move_to(&mut self, x: f32, y: f32) {
self.0.push(svgtypes::PathSegment::MoveTo { abs: true, x: x as f64, y: y as f64 });
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.push(svgtypes::PathSegment::LineTo { abs: true, x: x as f64, y: y as f64 });
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.0.push(svgtypes::PathSegment::Quadratic {
abs: true, x1: x1 as f64, y1: y1 as f64, x: x as f64, y: y as f64
});
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.0.push(svgtypes::PathSegment::CurveTo {
abs: true,
x1: x1 as f64, y1: y1 as f64,
x2: x2 as f64, y2: y2 as f64,
x: x as f64, y: y as f64
});
}
fn close(&mut self) {
self.0.push(svgtypes::PathSegment::ClosePath { abs: true });
}
}
#[test]
fn empty_font() {
assert_eq!(Font::from_data(&[], 0).unwrap_err().to_string(),
"not a TrueType font");
}
#[test]
fn incomplete_header() {
let data = &[
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
for i in 0..data.len() {
assert_eq!(Font::from_data(&data[0..i], 0).unwrap_err().to_string(),
"not a TrueType font");
}
}
#[test]
fn zero_tables() {
let data = &[
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(),
"font doesn't have a Header table");
}
#[test]
fn tables_count_overflow() {
let data = &[
0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(),
"an attempt to slice 12..1048572 on 0..12");
}
#[test]
fn open_type_magic() {
let data = &[
0x4F, 0x54, 0x54, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(),
"font doesn't have a Header table");
}
#[test]
fn unknown_magic() {
let data = &[
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(),
"not a TrueType font");
}
#[test]
fn empty_font_collection() {
let data = &[
0x74, 0x74, 0x63, 0x66, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(ttf::fonts_in_collection(data), Some(0));
assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(),
"font index is out of bounds");
}
#[test]
fn font_collection_num_fonts_overflow() {
let data = &[
0x74, 0x74, 0x63, 0x66, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ];
assert_eq!(ttf::fonts_in_collection(data), Some(4294967295));
assert_eq!(Font::from_data(data, 0).unwrap_err().to_string(),
"an attempt to slice 12..16 on 0..12");
}
#[test]
fn font_index_overflow() {
let data = &[
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(Font::from_data(data, std::u32::MAX).unwrap_err().to_string(),
"not a TrueType font");
}
#[test]
fn number_of_glyphs() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.number_of_glyphs(), 7);
}
#[test]
fn outline_glyph_single_contour() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let mut builder = Builder::new();
font.outline_glyph(GlyphId(0), &mut builder).unwrap();
assert_eq!(builder.0.to_string(),
"M 50 0 L 50 750 L 450 750 L 450 0 L 50 0 Z");
}
#[test]
fn outline_glyph_two_contours() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let mut builder = Builder::new();
font.outline_glyph(GlyphId(1), &mut builder).unwrap();
assert_eq!(builder.0.to_string(),
"M 56 416 L 56 487 L 514 487 L 514 416 L 56 416 Z \
M 56 217 L 56 288 L 514 288 L 514 217 L 56 217 Z");
}
#[test]
fn outline_glyph_composite() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let mut builder = Builder::new();
font.outline_glyph(GlyphId(4), &mut builder).unwrap();
assert_eq!(builder.0.to_string(),
"M 332 468 L 197 468 L 197 0 L 109 0 L 109 468 L 15 468 L 15 509 L 109 539 \
L 109 570 Q 109 674 155 719.5 Q 201 765 283 765 Q 315 765 341.5 759.5 \
Q 368 754 387 747 L 364 678 Q 348 683 327 688 Q 306 693 284 693 \
Q 240 693 218.5 663.5 Q 197 634 197 571 L 197 536 L 332 536 L 332 468 Z \
M 474 737 Q 494 737 509.5 723.5 Q 525 710 525 681 Q 525 653 509.5 639 \
Q 494 625 474 625 Q 452 625 437 639 Q 422 653 422 681 Q 422 710 437 723.5 \
Q 452 737 474 737 Z M 517 536 L 517 0 L 429 0 L 429 536 L 517 536 Z");
}
#[test]
fn outline_glyph_single_point() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let mut builder = Builder::new();
font.outline_glyph(GlyphId(5), &mut builder).unwrap();
assert_eq!(builder.0.to_string(), "");
}
#[test]
fn outline_glyph_single_point_2() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let mut builder = Builder::new();
font.outline_glyph(GlyphId(6), &mut builder).unwrap();
assert_eq!(builder.0.to_string(),
"M 332 468 L 197 468 L 197 0 L 109 0 L 109 468 L 15 468 L 15 509 L 109 539 \
L 109 570 Q 109 674 155 719.5 Q 201 765 283 765 Q 315 765 341.5 759.5 \
Q 368 754 387 747 L 364 678 Q 348 683 327 688 Q 306 693 284 693 \
Q 240 693 218.5 663.5 Q 197 634 197 571 L 197 536 L 332 536 L 332 468 Z");
}
#[test]
fn outline_glyph_cff_flex() {
let data = fs::read("tests/fonts/cff1_flex.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let mut builder = Builder::new();
font.outline_glyph(GlyphId(1), &mut builder).unwrap();
assert_eq!(builder.0.to_string(),
"M 0 0 C 100 0 150 -20 250 -20 C 350 -20 400 0 500 0 C 500 100 520 150 520 250 \
C 520 350 500 400 500 500 C 400 500 350 520 250 520 C 150 520 100 500 0 500 \
C 0 400 -20 350 -20 250 C -20 150 0 100 0 0 Z M 50 50 C 50 130 34 170 34 250 \
C 34 330 50 370 50 450 C 130 450 170 466 250 466 C 330 466 370 450 450 450 \
C 450 370 466 330 466 250 C 466 170 450 130 450 50 C 370 50 330 34 250 34 \
C 170 34 130 50 50 50 Z");
}
#[test]
fn outline_glyph_cff_1() {
let data = fs::read("tests/fonts/cff1_dotsect.nohints.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let mut builder = Builder::new();
font.outline_glyph(GlyphId(1), &mut builder).unwrap();
assert_eq!(builder.0.to_string(),
"M 82 0 L 164 0 L 164 486 L 82 486 Z M 124 586 C 156 586 181 608 181 639 \
C 181 671 156 692 124 692 C 92 692 67 671 67 639 C 67 608 92 586 124 586 Z");
}
#[test]
fn units_per_em() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.units_per_em(), Some(1000));
}
#[test]
fn units_per_em_invalid() {
let data = fs::read("tests/fonts/invalid-em.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.units_per_em(), None);
}
#[test]
fn ascender() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.ascender(), 900);
}
#[test]
fn descender() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.descender(), -300);
}
#[test]
fn line_gap() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.line_gap(), 200);
}
#[test]
fn underline_metrics() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.underline_metrics().unwrap(),
ttf::LineMetrics { position: -75, thickness: 50 });
}
#[test]
fn weight() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.weight(), ttf::Weight::SemiBold);
}
#[test]
fn width() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.width(), ttf::Width::Expanded);
}
#[test]
fn invalid_width() {
let data = fs::read("tests/fonts/os2-invalid-width.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.width(), ttf::Width::default());
}
#[test]
fn x_height() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.x_height().unwrap(), 536);
}
#[test]
fn no_x_height() {
let data = fs::read("tests/fonts/os2-v0.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert!(font.x_height().is_none());
}
#[test]
fn strikeout_metrics() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.strikeout_metrics().unwrap(),
ttf::LineMetrics { position: 322, thickness: 50 });
}
#[test]
fn is_regular() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.is_regular(), true);
}
#[test]
fn is_italic() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.is_italic(), false);
}
#[test]
fn os2_is_bold() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.is_bold(), false);
}
#[test]
fn is_oblique() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.is_oblique(), false);
}
#[test]
fn no_is_oblique() {
let data = fs::read("tests/fonts/os2-v0.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.is_oblique(), false);
}
#[test]
fn subscript_metrics() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(
font.subscript_metrics().unwrap(),
ttf::ScriptMetrics { x_size: 650, y_size: 600, x_offset: 0, y_offset: 75 },
);
}
#[test]
fn superscript_metrics() {
let data = fs::read("tests/fonts/glyphs.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(
font.superscript_metrics().unwrap(),
ttf::ScriptMetrics { x_size: 550, y_size: 800, x_offset: 100, y_offset: 350 },
);
}
#[test]
fn glyph_ver_metrics_1() {
let data = fs::read("tests/fonts/vmtx.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(
font.glyph_ver_metrics(GlyphId(0)).unwrap(),
ttf::VerticalMetrics { advance: 1000, top_side_bearing: 666 },
);
}
#[test]
fn glyph_ver_metrics_2() {
let data = fs::read("tests/fonts/vmtx.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(
font.glyph_ver_metrics(GlyphId(1)).unwrap(),
ttf::VerticalMetrics { advance: 1000, top_side_bearing: 83 },
);
}
#[test]
fn glyph_index_f00_01() {
let data = fs::read("tests/fonts/cmap0_font1.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.glyph_index('4').unwrap(), GlyphId(17));
assert_eq!(font.glyph_index('5').unwrap(), GlyphId(56));
assert_eq!(font.glyph_index('6').unwrap(), GlyphId(12));
}
#[test]
fn glyph_index_f02_01() {
let data = fs::read("tests/fonts/cmap2_font1.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.glyph_index('4').unwrap(), GlyphId(17));
assert_eq!(font.glyph_index('5').unwrap(), GlyphId(56));
assert_eq!(font.glyph_index('6').unwrap(), GlyphId(12));
assert_eq!(font.glyph_index('\u{8432}').unwrap(), GlyphId(20));
assert_eq!(font.glyph_index('\u{8433}').unwrap(), GlyphId(21));
assert_eq!(font.glyph_index('\u{8434}').unwrap(), GlyphId(22));
assert_eq!(font.glyph_index('\u{9232}').unwrap(), GlyphId(23));
assert_eq!(font.glyph_index('\u{9233}').unwrap(), GlyphId(24));
assert_eq!(font.glyph_index('\u{9234}').unwrap(), GlyphId(25));
}
#[test]
fn glyph_index_f04_01() {
let data = fs::read("tests/fonts/TestCMAP14.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.glyph_index('芦').unwrap(), GlyphId(1));
}
#[test]
fn glyph_index_f06_01() {
let data = fs::read("tests/fonts/cmap-6.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.glyph_index('"').unwrap(), GlyphId(6));
assert_eq!(font.glyph_index('#').unwrap(), GlyphId(7));
assert_eq!(font.glyph_index('$').unwrap(), GlyphId(5));
assert!(font.glyph_index('!').is_err());
assert!(font.glyph_index('A').is_err());
}
#[test]
fn glyph_index_f10_01() {
let data = fs::read("tests/fonts/cmap-10.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.glyph_index('\u{109423}').unwrap(), GlyphId(26));
assert_eq!(font.glyph_index('\u{109424}').unwrap(), GlyphId(27));
assert_eq!(font.glyph_index('\u{109425}').unwrap(), GlyphId(32));
assert!(font.glyph_index('!').is_err());
assert!(font.glyph_index('\u{109426}').is_err());
}
#[test]
fn glyph_index_f12_01() {
let data = fs::read("tests/fonts/vmtx.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.glyph_index('明').unwrap(), GlyphId(1));
}
#[test]
fn glyph_variation_index_01() {
let data = fs::read("tests/fonts/TestCMAP14.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert_eq!(font.glyph_variation_index('芦', '\u{E0101}').unwrap(), GlyphId(2));
}
#[test]
fn glyphs_kerning_01() {
let data = fs::read("tests/fonts/TestKERNOne.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
let t_id = font.glyph_index('T').unwrap();
let u_id = font.glyph_index('u').unwrap();
let dotless_i_id = font.glyph_index('\u{131}').unwrap();
assert_eq!(font.glyphs_kerning(t_id, dotless_i_id).unwrap(), -200);
assert_eq!(font.glyphs_kerning(t_id, u_id).unwrap(), -200);
assert_eq!(font.glyphs_kerning(dotless_i_id, t_id).unwrap(), -200);
assert_eq!(font.glyphs_kerning(dotless_i_id, dotless_i_id).unwrap(), 500);
assert_eq!(font.glyphs_kerning(u_id, t_id).unwrap(), -200);
}
#[test]
fn glyphs_kerning_02() {
let data = fs::read("tests/fonts/TestKERNOne.otf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
assert!(font.glyphs_kerning(GlyphId(0), GlyphId(0)).is_err());
assert!(font.glyphs_kerning(GlyphId(0), GlyphId(100)).is_err());
}
#[test]
fn slicing() {
#[inline(never)]
fn subslice1(font: &Font) {
assert_eq!(font.ascender(), 984);
}
#[inline(never)]
fn subslice2(font: Font) {
assert_eq!(font.ascender(), 984);
}
let data = fs::read("fonts/SourceSansPro-Regular.ttf").unwrap();
let font = Font::from_data(&data, 0).unwrap();
subslice1(&font);
subslice2(font.clone());
subslice2(font);
}