Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,389 @@
use std::num::NonZeroU16;
use ttf_parser::GlyphId;
use ttf_parser::apple_layout::Lookup;
use crate::{convert, Unit::*};
mod format0 {
use super::*;
#[test]
fn single() {
let data = convert(&[
UInt16(0), // format
UInt16(10), // value
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(0)).unwrap(), 10);
assert!(table.value(GlyphId(1)).is_none());
}
#[test]
fn not_enough_glyphs() {
let data = convert(&[
UInt16(0), // format
UInt16(10), // value
]);
assert!(Lookup::parse(NonZeroU16::new(2).unwrap(), &data).is_none());
}
#[test]
fn too_many_glyphs() {
let data = convert(&[
UInt16(0), // format
UInt16(10), // value
UInt16(11), // value <-- will be ignored
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(0)).unwrap(), 10);
assert!(table.value(GlyphId(1)).is_none());
}
}
mod format2 {
use super::*;
#[test]
fn single() {
let data = convert(&[
UInt16(2), // format
// Binary Search Table
UInt16(6), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(118), // last glyph
UInt16(118), // first glyph
UInt16(10), // value
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(118)).unwrap(), 10);
assert!(table.value(GlyphId(1)).is_none());
}
#[test]
fn range() {
let data = convert(&[
UInt16(2), // format
// Binary Search Table
UInt16(6), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(7), // last glyph
UInt16(5), // first glyph
UInt16(18), // offset
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert!(table.value(GlyphId(4)).is_none());
assert_eq!(table.value(GlyphId(5)).unwrap(), 18);
assert_eq!(table.value(GlyphId(6)).unwrap(), 18);
assert_eq!(table.value(GlyphId(7)).unwrap(), 18);
assert!(table.value(GlyphId(8)).is_none());
}
}
mod format4 {
use super::*;
#[test]
fn single() {
let data = convert(&[
UInt16(4), // format
// Binary Search Table
UInt16(6), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(118), // last glyph
UInt16(118), // first glyph
UInt16(18), // offset
// Values [0]
UInt16(10), // value [0]
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(118)).unwrap(), 10);
assert!(table.value(GlyphId(1)).is_none());
}
#[test]
fn range() {
let data = convert(&[
UInt16(4), // format
// Binary Search Table
UInt16(6), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(7), // last glyph
UInt16(5), // first glyph
UInt16(18), // offset
// Values [0]
UInt16(10), // value [0]
UInt16(11), // value [1]
UInt16(12), // value [2]
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert!(table.value(GlyphId(4)).is_none());
assert_eq!(table.value(GlyphId(5)).unwrap(), 10);
assert_eq!(table.value(GlyphId(6)).unwrap(), 11);
assert_eq!(table.value(GlyphId(7)).unwrap(), 12);
assert!(table.value(GlyphId(8)).is_none());
}
}
mod format6 {
use super::*;
#[test]
fn single() {
let data = convert(&[
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0), // glyph
UInt16(10), // value
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(0)).unwrap(), 10);
assert!(table.value(GlyphId(1)).is_none());
}
#[test]
fn multiple() {
let data = convert(&[
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(3), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0), // glyph
UInt16(10), // value
// Segment [1]
UInt16(5), // glyph
UInt16(20), // value
// Segment [2]
UInt16(10), // glyph
UInt16(30), // value
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(0)).unwrap(), 10);
assert_eq!(table.value(GlyphId(5)).unwrap(), 20);
assert_eq!(table.value(GlyphId(10)).unwrap(), 30);
assert!(table.value(GlyphId(1)).is_none());
}
// Tests below are indirectly testing BinarySearchTable.
#[test]
fn no_segments() {
let data = convert(&[
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(0), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
]);
assert!(Lookup::parse(NonZeroU16::new(1).unwrap(), &data).is_none());
}
#[test]
fn ignore_termination() {
let data = convert(&[
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(2), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0), // glyph
UInt16(10), // value
// Segment [1]
UInt16(0xFFFF), // glyph
UInt16(0xFFFF), // value
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert!(table.value(GlyphId(0xFFFF)).is_none());
}
#[test]
fn only_termination() {
let data = convert(&[
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0xFFFF), // glyph
UInt16(0xFFFF), // value
]);
assert!(Lookup::parse(NonZeroU16::new(1).unwrap(), &data).is_none());
}
#[test]
fn invalid_segment_size() {
let data = convert(&[
UInt16(6), // format
// Binary Search Table
UInt16(8), // segment size <-- must be 4
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0), // glyph
UInt16(10), // value
]);
assert!(Lookup::parse(NonZeroU16::new(1).unwrap(), &data).is_none());
}
}
mod format8 {
use super::*;
#[test]
fn single() {
let data = convert(&[
UInt16(8), // format
UInt16(0), // first glyph
UInt16(1), // glyphs count
UInt16(2), // value [0]
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(0)).unwrap(), 2);
assert!(table.value(GlyphId(1)).is_none());
}
#[test]
fn non_zero_first() {
let data = convert(&[
UInt16(8), // format
UInt16(5), // first glyph
UInt16(1), // glyphs count
UInt16(2), // value [0]
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(5)).unwrap(), 2);
assert!(table.value(GlyphId(1)).is_none());
assert!(table.value(GlyphId(6)).is_none());
}
}
mod format10 {
use super::*;
#[test]
fn single() {
let data = convert(&[
UInt16(10), // format
UInt16(1), // value size: u8
UInt16(0), // first glyph
UInt16(1), // glyphs count
UInt8(2), // value [0]
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(0)).unwrap(), 2);
assert!(table.value(GlyphId(1)).is_none());
}
#[test]
fn invalid_value_size() {
let data = convert(&[
UInt16(10), // format
UInt16(50), // value size <-- invalid
UInt16(0), // first glyph
UInt16(1), // glyphs count
UInt8(2), // value [0]
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert!(table.value(GlyphId(0)).is_none());
}
#[test]
fn unsupported_value_size() {
let data = convert(&[
UInt16(10), // format
UInt16(8), // value size <-- we do not support u64
UInt16(0), // first glyph
UInt16(1), // glyphs count
Raw(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]), // value [0]
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert!(table.value(GlyphId(0)).is_none());
}
#[test]
fn u32_value_size() {
let data = convert(&[
UInt16(10), // format
UInt16(4), // value size
UInt16(0), // first glyph
UInt16(1), // glyphs count
UInt32(0xFFFF + 10), // value [0] <-- will be truncated
]);
let table = Lookup::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.value(GlyphId(0)).unwrap(), 9);
}
}

View File

@@ -0,0 +1,134 @@
use std::num::NonZeroU16;
use ttf_parser::GlyphId;
use ttf_parser::ankr::{Table, Point};
use crate::{convert, Unit::*};
#[test]
fn empty() {
let data = convert(&[
UInt16(0), // version
UInt16(0), // reserved
UInt32(0), // offset to lookup table
UInt32(0), // offset to glyphs data
]);
let _ = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
}
#[test]
fn single() {
let data = convert(&[
UInt16(0), // version
UInt16(0), // reserved
UInt32(12), // offset to lookup table
UInt32(12 + 16), // offset to glyphs data
// Lookup Table
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0), // glyph
UInt16(0), // offset
// Glyphs Data
UInt32(1), // number of points
// Point [0]
Int16(-5), // x
Int16(11), // y
]);
let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
let points = table.points(GlyphId(0)).unwrap();
assert_eq!(points.get(0).unwrap(), Point { x: -5, y: 11 });
}
#[test]
fn two_points() {
let data = convert(&[
UInt16(0), // version
UInt16(0), // reserved
UInt32(12), // offset to lookup table
UInt32(12 + 16), // offset to glyphs data
// Lookup Table
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(1), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0), // glyph
UInt16(0), // offset
// Glyphs Data
// Glyph Data [0]
UInt32(2), // number of points
// Point [0]
Int16(-5), // x
Int16(11), // y
// Point [1]
Int16(10), // x
Int16(-40), // y
]);
let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
let points = table.points(GlyphId(0)).unwrap();
assert_eq!(points.get(0).unwrap(), Point { x: -5, y: 11 });
assert_eq!(points.get(1).unwrap(), Point { x: 10, y: -40 });
}
#[test]
fn two_glyphs() {
let data = convert(&[
UInt16(0), // version
UInt16(0), // reserved
UInt32(12), // offset to lookup table
UInt32(12 + 20), // offset to glyphs data
// Lookup Table
UInt16(6), // format
// Binary Search Table
UInt16(4), // segment size
UInt16(2), // number of segments
UInt16(0), // search range: we don't use it
UInt16(0), // entry selector: we don't use it
UInt16(0), // range shift: we don't use it
// Segment [0]
UInt16(0), // glyph
UInt16(0), // offset
// Segment [1]
UInt16(1), // glyph
UInt16(8), // offset
// Glyphs Data
// Glyph Data [0]
UInt32(1), // number of points
// Point [0]
Int16(-5), // x
Int16(11), // y
// Glyph Data [1]
UInt32(1), // number of points
// Point [0]
Int16(40), // x
Int16(10), // y
]);
let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
let points = table.points(GlyphId(0)).unwrap();
assert_eq!(points.get(0).unwrap(), Point { x: -5, y: 11 });
let points = table.points(GlyphId(1)).unwrap();
assert_eq!(points.get(0).unwrap(), Point { x: 40, y: 10 });
}

View File

@@ -0,0 +1,998 @@
// TODO: simplify/rewrite
use std::fmt::Write;
use ttf_parser::{cff, GlyphId, CFFError, Rect};
struct Builder(String);
impl ttf_parser::OutlineBuilder for Builder {
fn move_to(&mut self, x: f32, y: f32) {
write!(&mut self.0, "M {} {} ", x, y).unwrap();
}
fn line_to(&mut self, x: f32, y: f32) {
write!(&mut self.0, "L {} {} ", x, y).unwrap();
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap();
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap();
}
fn close(&mut self) {
write!(&mut self.0, "Z ").unwrap();
}
}
#[allow(dead_code)]
mod operator {
pub const HORIZONTAL_STEM: u8 = 1;
pub const VERTICAL_STEM: u8 = 3;
pub const VERTICAL_MOVE_TO: u8 = 4;
pub const LINE_TO: u8 = 5;
pub const HORIZONTAL_LINE_TO: u8 = 6;
pub const VERTICAL_LINE_TO: u8 = 7;
pub const CURVE_TO: u8 = 8;
pub const CALL_LOCAL_SUBROUTINE: u8 = 10;
pub const RETURN: u8 = 11;
pub const ENDCHAR: u8 = 14;
pub const HORIZONTAL_STEM_HINT_MASK: u8 = 18;
pub const HINT_MASK: u8 = 19;
pub const COUNTER_MASK: u8 = 20;
pub const MOVE_TO: u8 = 21;
pub const HORIZONTAL_MOVE_TO: u8 = 22;
pub const VERTICAL_STEM_HINT_MASK: u8 = 23;
pub const CURVE_LINE: u8 = 24;
pub const LINE_CURVE: u8 = 25;
pub const VV_CURVE_TO: u8 = 26;
pub const HH_CURVE_TO: u8 = 27;
pub const SHORT_INT: u8 = 28;
pub const CALL_GLOBAL_SUBROUTINE: u8 = 29;
pub const VH_CURVE_TO: u8 = 30;
pub const HV_CURVE_TO: u8 = 31;
pub const HFLEX: u8 = 34;
pub const FLEX: u8 = 35;
pub const HFLEX1: u8 = 36;
pub const FLEX1: u8 = 37;
pub const FIXED_16_16: u8 = 255;
}
#[allow(dead_code)]
mod top_dict_operator {
pub const CHARSET_OFFSET: u16 = 15;
pub const CHAR_STRINGS_OFFSET: u16 = 17;
pub const PRIVATE_DICT_SIZE_AND_OFFSET: u16 = 18;
pub const ROS: u16 = 1230;
pub const FD_ARRAY: u16 = 1236;
pub const FD_SELECT: u16 = 1237;
}
mod private_dict_operator {
pub const LOCAL_SUBROUTINES_OFFSET: u16 = 19;
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
enum TtfType {
Raw(&'static [u8]),
TrueTypeMagic,
OpenTypeMagic,
FontCollectionMagic,
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
CFFInt(i32),
}
use TtfType::*;
fn convert(values: &[TtfType]) -> Vec<u8> {
let mut data = Vec::with_capacity(256);
for v in values {
convert_type(*v, &mut data);
}
data
}
fn convert_type(value: TtfType, data: &mut Vec<u8>) {
match value {
TtfType::Raw(bytes) => {
data.extend_from_slice(bytes);
}
TtfType::TrueTypeMagic => {
data.extend_from_slice(&[0x00, 0x01, 0x00, 0x00]);
}
TtfType::OpenTypeMagic => {
data.extend_from_slice(&[0x4F, 0x54, 0x54, 0x4F]);
}
TtfType::FontCollectionMagic => {
data.extend_from_slice(&[0x74, 0x74, 0x63, 0x66]);
}
TtfType::Int8(n) => {
data.extend_from_slice(&i8::to_be_bytes(n));
}
TtfType::UInt8(n) => {
data.extend_from_slice(&u8::to_be_bytes(n));
}
TtfType::Int16(n) => {
data.extend_from_slice(&i16::to_be_bytes(n));
}
TtfType::UInt16(n) => {
data.extend_from_slice(&u16::to_be_bytes(n));
}
TtfType::Int32(n) => {
data.extend_from_slice(&i32::to_be_bytes(n));
}
TtfType::UInt32(n) => {
data.extend_from_slice(&u32::to_be_bytes(n));
}
TtfType::CFFInt(n) => {
match n {
-107..=107 => {
data.push((n as i16 + 139) as u8);
}
108..=1131 => {
let n = n - 108;
data.push(((n >> 8) + 247) as u8);
data.push((n & 0xFF) as u8);
}
-1131..=-108 => {
let n = -n - 108;
data.push(((n >> 8) + 251) as u8);
data.push((n & 0xFF) as u8);
}
-32768..=32767 => {
data.push(28);
data.extend_from_slice(&i16::to_be_bytes(n as i16));
}
_ => {
data.push(29);
data.extend_from_slice(&i32::to_be_bytes(n));
}
}
}
}
}
#[derive(Debug)]
struct Writer {
data: Vec<u8>,
}
impl Writer {
fn new() -> Self {
Writer { data: Vec::with_capacity(256) }
}
fn offset(&self) -> usize {
self.data.len()
}
fn write(&mut self, value: TtfType) {
convert_type(value, &mut self.data);
}
}
fn gen_cff(
global_subrs: &[&[TtfType]],
local_subrs: &[&[TtfType]],
chars: &[TtfType],
) -> Vec<u8> {
fn gen_global_subrs(subrs: &[&[TtfType]]) -> Vec<u8> {
let mut w = Writer::new();
for v1 in subrs {
for v2 in v1.iter() {
w.write(*v2);
}
}
w.data
}
fn gen_local_subrs(subrs: &[&[TtfType]]) -> Vec<u8> {
let mut w = Writer::new();
for v1 in subrs {
for v2 in v1.iter() {
w.write(*v2);
}
}
w.data
}
const EMPTY_INDEX_SIZE: usize = 2;
const INDEX_HEADER_SIZE: usize = 5;
// TODO: support multiple subrs
assert!(global_subrs.len() <= 1);
assert!(local_subrs.len() <= 1);
let global_subrs_data = gen_global_subrs(global_subrs);
let local_subrs_data = gen_local_subrs(local_subrs);
let chars_data = convert(chars);
assert!(global_subrs_data.len() < 255);
assert!(local_subrs_data.len() < 255);
assert!(chars_data.len() < 255);
let mut w = Writer::new();
// Header
w.write(UInt8(1)); // major version
w.write(UInt8(0)); // minor version
w.write(UInt8(4)); // header size
w.write(UInt8(0)); // absolute offset
// Name INDEX
w.write(UInt16(0)); // count
// Top DICT
// INDEX
w.write(UInt16(1)); // count
w.write(UInt8(1)); // offset size
w.write(UInt8(1)); // index[0]
let top_dict_idx2 = if local_subrs.is_empty() { 3 } else { 6 };
w.write(UInt8(top_dict_idx2)); // index[1]
// Item 0
let mut charstr_offset = w.offset() + 2;
charstr_offset += EMPTY_INDEX_SIZE; // String INDEX
// Global Subroutines INDEX
if !global_subrs_data.is_empty() {
charstr_offset += INDEX_HEADER_SIZE + global_subrs_data.len();
} else {
charstr_offset += EMPTY_INDEX_SIZE;
}
if !local_subrs_data.is_empty() {
charstr_offset += 3;
}
w.write(CFFInt(charstr_offset as i32));
w.write(UInt8(top_dict_operator::CHAR_STRINGS_OFFSET as u8));
if !local_subrs_data.is_empty() {
// Item 1
w.write(CFFInt(2)); // length
w.write(CFFInt((charstr_offset + INDEX_HEADER_SIZE + chars_data.len()) as i32)); // offset
w.write(UInt8(top_dict_operator::PRIVATE_DICT_SIZE_AND_OFFSET as u8));
}
// String INDEX
w.write(UInt16(0)); // count
// Global Subroutines INDEX
if global_subrs_data.is_empty() {
w.write(UInt16(0)); // count
} else {
w.write(UInt16(1)); // count
w.write(UInt8(1)); // offset size
w.write(UInt8(1)); // index[0]
w.write(UInt8(global_subrs_data.len() as u8 + 1)); // index[1]
w.data.extend_from_slice(&global_subrs_data);
}
// CharString INDEX
w.write(UInt16(1)); // count
w.write(UInt8(1)); // offset size
w.write(UInt8(1)); // index[0]
w.write(UInt8(chars_data.len() as u8 + 1)); // index[1]
w.data.extend_from_slice(&chars_data);
if !local_subrs_data.is_empty() {
// The local subroutines offset is relative to the beginning of the Private DICT data.
// Private DICT
w.write(CFFInt(2));
w.write(UInt8(private_dict_operator::LOCAL_SUBROUTINES_OFFSET as u8));
// Local Subroutines INDEX
w.write(UInt16(1)); // count
w.write(UInt8(1)); // offset size
w.write(UInt8(1)); // index[0]
w.write(UInt8(local_subrs_data.len() as u8 + 1)); // index[1]
w.data.extend_from_slice(&local_subrs_data);
}
w.data
}
#[test]
fn unsupported_version() {
let data = convert(&[
UInt8(10), // major version, only 1 is supported
UInt8(0), // minor version
UInt8(4), // header size
UInt8(0), // absolute offset
]);
assert!(cff::Table::parse(&data).is_none());
}
#[test]
fn non_default_header_size() {
let data = convert(&[
// Header
UInt8(1), // major version
UInt8(0), // minor version
UInt8(8), // header size
UInt8(0), // absolute offset
// no-op, should be skipped
UInt8(0),
UInt8(0),
UInt8(0),
UInt8(0),
// Name INDEX
UInt16(0), // count
// Top DICT
// INDEX
UInt16(1), // count
UInt8(1), // offset size
UInt8(1), // index[0]
UInt8(3), // index[1]
// Data
CFFInt(21),
UInt8(top_dict_operator::CHAR_STRINGS_OFFSET as u8),
// String INDEX
UInt16(0), // count
// Global Subroutines INDEX
UInt16(0), // count
// CharString INDEX
UInt16(1), // count
UInt8(1), // offset size
UInt8(1), // index[0]
UInt8(4), // index[1]
// Data
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
UInt8(operator::ENDCHAR),
]);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let rect = table.outline(GlyphId(0), &mut builder).unwrap();
assert_eq!(builder.0, "M 10 0 Z ");
assert_eq!(rect, Rect { x_min: 10, y_min: 0, x_max: 10, y_max: 0 });
}
fn rect(x_min: i16, y_min: i16, x_max: i16, y_max: i16) -> Rect {
Rect { x_min, y_min, x_max, y_max }
}
macro_rules! test_cs_with_subrs {
($name:ident, $glob:expr, $loc:expr, $values:expr, $path:expr, $rect_res:expr) => {
#[test]
fn $name() {
let data = gen_cff($glob, $loc, $values);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let rect = table.outline(GlyphId(0), &mut builder).unwrap();
assert_eq!(builder.0, $path);
assert_eq!(rect, $rect_res);
}
};
}
macro_rules! test_cs {
($name:ident, $values:expr, $path:expr, $rect_res:expr) => {
test_cs_with_subrs!($name, &[], &[], $values, $path, $rect_res);
};
}
macro_rules! test_cs_err {
($name:ident, $values:expr, $err:expr) => {
#[test]
fn $name() {
let data = gen_cff(&[], &[], $values);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let res = table.outline(GlyphId(0), &mut builder);
assert_eq!(res.unwrap_err(), $err);
}
};
}
test_cs!(move_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 Z ",
rect(10, 20, 10, 20)
);
test_cs!(move_to_with_width, &[
CFFInt(5), CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 Z ",
rect(10, 20, 10, 20)
);
test_cs!(hmove_to, &[
CFFInt(10), UInt8(operator::HORIZONTAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 0 Z ",
rect(10, 0, 10, 0)
);
test_cs!(hmove_to_with_width, &[
CFFInt(10), CFFInt(20), UInt8(operator::HORIZONTAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], "M 20 0 Z ",
rect(20, 0, 20, 0)
);
test_cs!(vmove_to, &[
CFFInt(10), UInt8(operator::VERTICAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], "M 0 10 Z ",
rect(0, 10, 0, 10)
);
test_cs!(vmove_to_with_width, &[
CFFInt(10), CFFInt(20), UInt8(operator::VERTICAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], "M 0 20 Z ",
rect(0, 20, 0, 20)
);
test_cs!(line_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 40 60 Z ",
rect(10, 20, 40, 60)
);
test_cs!(line_to_with_multiple_pairs, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 40 60 L 90 120 Z ",
rect(10, 20, 90, 120)
);
test_cs!(hline_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), UInt8(operator::HORIZONTAL_LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 40 20 Z ",
rect(10, 20, 40, 20)
);
test_cs!(hline_to_with_two_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), UInt8(operator::HORIZONTAL_LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 40 20 L 40 60 Z ",
rect(10, 20, 40, 60)
);
test_cs!(hline_to_with_three_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), UInt8(operator::HORIZONTAL_LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 40 20 L 40 60 L 90 60 Z ",
rect(10, 20, 90, 60)
);
test_cs!(vline_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), UInt8(operator::VERTICAL_LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 10 50 Z ",
rect(10, 20, 10, 50)
);
test_cs!(vline_to_with_two_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), UInt8(operator::VERTICAL_LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 10 50 L 50 50 Z ",
rect(10, 20, 50, 50)
);
test_cs!(vline_to_with_three_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), UInt8(operator::VERTICAL_LINE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 L 10 50 L 50 50 L 50 100 Z ",
rect(10, 20, 50, 100)
);
test_cs!(curve_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), CFFInt(70), CFFInt(80),
UInt8(operator::CURVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 C 40 60 90 120 160 200 Z ",
rect(10, 20, 160, 200)
);
test_cs!(curve_to_with_two_sets_of_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), CFFInt(70), CFFInt(80),
CFFInt(90), CFFInt(100), CFFInt(110), CFFInt(120), CFFInt(130), CFFInt(140),
UInt8(operator::CURVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 C 40 60 90 120 160 200 C 250 300 360 420 490 560 Z ",
rect(10, 20, 490, 560)
);
test_cs!(hh_curve_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), UInt8(operator::HH_CURVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 C 40 20 80 70 140 70 Z ",
rect(10, 20, 140, 70)
);
test_cs!(hh_curve_to_with_y, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), CFFInt(70), UInt8(operator::HH_CURVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 C 50 50 100 110 170 110 Z ",
rect(10, 20, 170, 110)
);
test_cs!(vv_curve_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), UInt8(operator::VV_CURVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 C 10 50 50 100 50 160 Z ",
rect(10, 20, 50, 160)
);
test_cs!(vv_curve_to_with_x, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), CFFInt(70), UInt8(operator::VV_CURVE_TO),
UInt8(operator::ENDCHAR),
], "M 10 20 C 40 60 90 120 90 190 Z ",
rect(10, 20, 90, 190)
);
#[test]
fn only_endchar() {
let data = gen_cff(&[], &[], &[UInt8(operator::ENDCHAR)]);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
assert!(table.outline(GlyphId(0), &mut builder).is_err());
}
test_cs_with_subrs!(local_subr,
&[],
&[&[
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
UInt8(operator::RETURN),
]],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
UInt8(operator::ENDCHAR),
],
"M 10 0 L 40 40 Z ",
rect(10, 0, 40, 40)
);
test_cs_with_subrs!(endchar_in_subr,
&[],
&[&[
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
]],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
],
"M 10 0 L 40 40 Z ",
rect(10, 0, 40, 40)
);
test_cs_with_subrs!(global_subr,
&[&[
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
UInt8(operator::RETURN),
]],
&[],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_GLOBAL_SUBROUTINE),
UInt8(operator::ENDCHAR),
],
"M 10 0 L 40 40 Z ",
rect(10, 0, 40, 40)
);
test_cs_err!(reserved_operator, &[
CFFInt(10), UInt8(2),
UInt8(operator::ENDCHAR),
], CFFError::InvalidOperator);
test_cs_err!(line_to_without_move_to, &[
CFFInt(10), CFFInt(20), UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
], CFFError::MissingMoveTo);
// Width must be set only once.
test_cs_err!(two_vmove_to_with_width, &[
CFFInt(10), CFFInt(20), UInt8(operator::VERTICAL_MOVE_TO),
CFFInt(10), CFFInt(20), UInt8(operator::VERTICAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(move_to_with_too_many_coords, &[
CFFInt(10), CFFInt(10), CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(move_to_with_not_enought_coords, &[
CFFInt(10), UInt8(operator::MOVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(hmove_to_with_too_many_coords, &[
CFFInt(10), CFFInt(10), CFFInt(10), UInt8(operator::HORIZONTAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(hmove_to_with_not_enought_coords, &[
UInt8(operator::HORIZONTAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(vmove_to_with_too_many_coords, &[
CFFInt(10), CFFInt(10), CFFInt(10), UInt8(operator::VERTICAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(vmove_to_with_not_enought_coords, &[
UInt8(operator::VERTICAL_MOVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(line_to_with_single_coord, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(line_to_with_odd_number_of_coord, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(hline_to_without_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
UInt8(operator::HORIZONTAL_LINE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(vline_to_without_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
UInt8(operator::VERTICAL_LINE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(curve_to_with_invalid_num_of_coords_1, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), UInt8(operator::CURVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(curve_to_with_invalid_num_of_coords_2, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(60), CFFInt(70), CFFInt(80), CFFInt(90),
UInt8(operator::CURVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(hh_curve_to_with_not_enought_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), UInt8(operator::HH_CURVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(hh_curve_to_with_too_many_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(30), CFFInt(40), CFFInt(50),
UInt8(operator::HH_CURVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(vv_curve_to_with_not_enought_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), UInt8(operator::VV_CURVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(vv_curve_to_with_too_many_coords, &[
CFFInt(10), CFFInt(20), UInt8(operator::MOVE_TO),
CFFInt(30), CFFInt(40), CFFInt(50), CFFInt(30), CFFInt(40), CFFInt(50),
UInt8(operator::VV_CURVE_TO),
UInt8(operator::ENDCHAR),
], CFFError::InvalidArgumentsStackLength);
test_cs_err!(multiple_endchar, &[
UInt8(operator::ENDCHAR),
UInt8(operator::ENDCHAR),
], CFFError::DataAfterEndChar);
test_cs_err!(seac_with_not_enough_data, &[
CFFInt(0),
CFFInt(0),
CFFInt(0),
CFFInt(0),
UInt8(operator::ENDCHAR),
], CFFError::NestingLimitReached);
test_cs_err!(operands_overflow, &[
CFFInt(0), CFFInt(1), CFFInt(2), CFFInt(3), CFFInt(4), CFFInt(5), CFFInt(6), CFFInt(7), CFFInt(8), CFFInt(9),
CFFInt(0), CFFInt(1), CFFInt(2), CFFInt(3), CFFInt(4), CFFInt(5), CFFInt(6), CFFInt(7), CFFInt(8), CFFInt(9),
CFFInt(0), CFFInt(1), CFFInt(2), CFFInt(3), CFFInt(4), CFFInt(5), CFFInt(6), CFFInt(7), CFFInt(8), CFFInt(9),
CFFInt(0), CFFInt(1), CFFInt(2), CFFInt(3), CFFInt(4), CFFInt(5), CFFInt(6), CFFInt(7), CFFInt(8), CFFInt(9),
CFFInt(0), CFFInt(1), CFFInt(2), CFFInt(3), CFFInt(4), CFFInt(5), CFFInt(6), CFFInt(7), CFFInt(8), CFFInt(9),
], CFFError::ArgumentsStackLimitReached);
test_cs_err!(operands_overflow_with_4_byte_ints, &[
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000), CFFInt(30000),
], CFFError::ArgumentsStackLimitReached);
test_cs_err!(bbox_overflow, &[
CFFInt(32767), UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(32767), UInt8(operator::HORIZONTAL_LINE_TO),
UInt8(operator::ENDCHAR),
], CFFError::BboxOverflow);
#[test]
fn endchar_in_subr_with_extra_data_1() {
let data = gen_cff(
&[],
&[&[
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
]],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
]
);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let res = table.outline(GlyphId(0), &mut builder);
assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar);
}
#[test]
fn endchar_in_subr_with_extra_data_2() {
let data = gen_cff(
&[],
&[&[
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
]],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
]
);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let res = table.outline(GlyphId(0), &mut builder);
assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar);
}
#[test]
fn subr_without_return() {
let data = gen_cff(
&[],
&[&[
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
UInt8(operator::ENDCHAR),
CFFInt(30),
CFFInt(40),
UInt8(operator::LINE_TO),
]],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
]
);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let res = table.outline(GlyphId(0), &mut builder);
assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar);
}
#[test]
fn recursive_local_subr() {
let data = gen_cff(
&[],
&[&[
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
]],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
]
);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let res = table.outline(GlyphId(0), &mut builder);
assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached);
}
#[test]
fn recursive_global_subr() {
let data = gen_cff(
&[&[
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_GLOBAL_SUBROUTINE),
]],
&[],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_GLOBAL_SUBROUTINE),
]
);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let res = table.outline(GlyphId(0), &mut builder);
assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached);
}
#[test]
fn recursive_mixed_subr() {
let data = gen_cff(
&[&[
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_LOCAL_SUBROUTINE),
]],
&[&[
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_GLOBAL_SUBROUTINE),
]],
&[
CFFInt(10),
UInt8(operator::HORIZONTAL_MOVE_TO),
CFFInt(0 - 107), // subr index - subr bias
UInt8(operator::CALL_GLOBAL_SUBROUTINE),
]
);
let table = cff::Table::parse(&data).unwrap();
let mut builder = Builder(String::new());
let res = table.outline(GlyphId(0), &mut builder);
assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached);
}
#[test]
fn zero_char_string_offset() {
let data = convert(&[
// Header
UInt8(1), // major version
UInt8(0), // minor version
UInt8(4), // header size
UInt8(0), // absolute offset
// Name INDEX
UInt16(0), // count
// Top DICT
// INDEX
UInt16(1), // count
UInt8(1), // offset size
UInt8(1), // index[0]
UInt8(3), // index[1]
// Data
CFFInt(0), // zero offset!
UInt8(top_dict_operator::CHAR_STRINGS_OFFSET as u8),
]);
assert!(cff::Table::parse(&data).is_none());
}
#[test]
fn invalid_char_string_offset() {
let data = convert(&[
// Header
UInt8(1), // major version
UInt8(0), // minor version
UInt8(4), // header size
UInt8(0), // absolute offset
// Name INDEX
UInt16(0), // count
// Top DICT
// INDEX
UInt16(1), // count
UInt8(1), // offset size
UInt8(1), // index[0]
UInt8(3), // index[1]
// Data
CFFInt(2), // invalid offset!
UInt8(top_dict_operator::CHAR_STRINGS_OFFSET as u8),
]);
assert!(cff::Table::parse(&data).is_none());
}
// TODO: return from main
// TODO: return without endchar
// TODO: data after return
// TODO: recursive subr
// TODO: HORIZONTAL_STEM
// TODO: VERTICAL_STEM
// TODO: HORIZONTAL_STEM_HINT_MASK
// TODO: HINT_MASK
// TODO: COUNTER_MASK
// TODO: VERTICAL_STEM_HINT_MASK
// TODO: CURVE_LINE
// TODO: LINE_CURVE
// TODO: VH_CURVE_TO
// TODO: HFLEX
// TODO: FLEX
// TODO: HFLEX1
// TODO: FLEX1

View File

@@ -0,0 +1,555 @@
mod format0 {
use ttf_parser::{cmap, GlyphId};
use crate::{convert, Unit::*};
#[test]
fn maps_not_all_256_codepoints() {
let mut data = convert(&[
UInt16(0), // format
UInt16(262), // subtable size
UInt16(0), // language ID
]);
// Map (only) codepoint 0x40 to 100.
data.extend(std::iter::repeat(0).take(256));
data[6 + 0x40] = 100;
let subtable = cmap::Subtable0::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0), None);
assert_eq!(subtable.glyph_index(0x40), Some(GlyphId(100)));
assert_eq!(subtable.glyph_index(100), None);
let mut vec = vec![];
subtable.codepoints(|c| vec.push(c));
assert_eq!(vec, [0x40]);
}
}
mod format2 {
use ttf_parser::{cmap, GlyphId};
use crate::{convert, Unit::*};
const U16_SIZE: usize = std::mem::size_of::<u16>();
#[test]
fn collect_codepoints() {
let mut data = convert(&[
UInt16(2), // format
UInt16(534), // subtable size
UInt16(0), // language ID
]);
// Make only high byte 0x28 multi-byte.
data.extend(std::iter::repeat(0x00).take(256 * U16_SIZE));
data[6 + 0x28 * U16_SIZE + 1] = 0x08;
data.extend(convert(&[
// First sub header (for single byte mapping)
UInt16(254), // first code
UInt16(2), // entry count
UInt16(0), // id delta: uninteresting
UInt16(0), // id range offset: uninteresting
// Second sub header (for high byte 0x28)
UInt16(16), // first code: (0x28 << 8) + 0x10 = 10256
UInt16(3), // entry count
UInt16(0), // id delta: uninteresting
UInt16(0), // id range offset: uninteresting
]));
// Now only glyph ID's would follow. Not interesting for codepoints.
let subtable = cmap::Subtable2::parse(&data).unwrap();
let mut vec = vec![];
subtable.codepoints(|c| vec.push(c));
assert_eq!(vec, [10256, 10257, 10258, 254, 255]);
}
#[test]
fn codepoint_at_range_end() {
let mut data = convert(&[
UInt16(2), // format
UInt16(532), // subtable size
UInt16(0), // language ID
]);
// Only single bytes.
data.extend(std::iter::repeat(0x00).take(256 * U16_SIZE));
data.extend(convert(&[
// First sub header (for single byte mapping)
UInt16(40), // first code
UInt16(2), // entry count
UInt16(0), // id delta
UInt16(2), // id range offset
// Glyph index
UInt16(100), // glyph ID [0]
UInt16(1000), // glyph ID [1]
UInt16(10000), // glyph ID [2] (unused)
]));
let subtable = cmap::Subtable2::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(39), None);
assert_eq!(subtable.glyph_index(40), Some(GlyphId(100)));
assert_eq!(subtable.glyph_index(41), Some(GlyphId(1000)));
assert_eq!(subtable.glyph_index(42), None);
}
}
mod format4 {
use ttf_parser::{cmap, GlyphId};
use crate::{convert, Unit::*};
#[test]
fn single_glyph() {
let data = convert(&[
UInt16(4), // format
UInt16(32), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(-64), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0x41), Some(GlyphId(1)));
assert_eq!(subtable.glyph_index(0x42), None);
}
#[test]
fn continuous_range() {
let data = convert(&[
UInt16(4), // format
UInt16(32), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(73), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(-64), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0x40), None);
assert_eq!(subtable.glyph_index(0x41), Some(GlyphId(1)));
assert_eq!(subtable.glyph_index(0x42), Some(GlyphId(2)));
assert_eq!(subtable.glyph_index(0x43), Some(GlyphId(3)));
assert_eq!(subtable.glyph_index(0x44), Some(GlyphId(4)));
assert_eq!(subtable.glyph_index(0x45), Some(GlyphId(5)));
assert_eq!(subtable.glyph_index(0x46), Some(GlyphId(6)));
assert_eq!(subtable.glyph_index(0x47), Some(GlyphId(7)));
assert_eq!(subtable.glyph_index(0x48), Some(GlyphId(8)));
assert_eq!(subtable.glyph_index(0x49), Some(GlyphId(9)));
assert_eq!(subtable.glyph_index(0x4A), None);
}
#[test]
fn multiple_ranges() {
let data = convert(&[
UInt16(4), // format
UInt16(48), // subtable size
UInt16(0), // language ID
UInt16(8), // 2 x segCount
UInt16(4), // search range
UInt16(1), // entry selector
UInt16(4), // range shift
// End character codes
UInt16(65), // char code [0]
UInt16(69), // char code [1]
UInt16(73), // char code [2]
UInt16(65535), // char code [3]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(67), // char code [1]
UInt16(71), // char code [2]
UInt16(65535), // char code [3]
// Deltas
Int16(-64), // delta [0]
Int16(-65), // delta [1]
Int16(-66), // delta [2]
Int16(1), // delta [3]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
UInt16(0), // offset [2]
UInt16(0), // offset [3]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0x40), None);
assert_eq!(subtable.glyph_index(0x41), Some(GlyphId(1)));
assert_eq!(subtable.glyph_index(0x42), None);
assert_eq!(subtable.glyph_index(0x43), Some(GlyphId(2)));
assert_eq!(subtable.glyph_index(0x44), Some(GlyphId(3)));
assert_eq!(subtable.glyph_index(0x45), Some(GlyphId(4)));
assert_eq!(subtable.glyph_index(0x46), None);
assert_eq!(subtable.glyph_index(0x47), Some(GlyphId(5)));
assert_eq!(subtable.glyph_index(0x48), Some(GlyphId(6)));
assert_eq!(subtable.glyph_index(0x49), Some(GlyphId(7)));
assert_eq!(subtable.glyph_index(0x4A), None);
}
#[test]
fn unordered_ids() {
let data = convert(&[
UInt16(4), // format
UInt16(42), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(69), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(0), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(4), // offset [0]
UInt16(0), // offset [1]
// Glyph index array
UInt16(1), // glyph ID [0]
UInt16(10), // glyph ID [1]
UInt16(100), // glyph ID [2]
UInt16(1000), // glyph ID [3]
UInt16(10000), // glyph ID [4]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0x40), None);
assert_eq!(subtable.glyph_index(0x41), Some(GlyphId(1)));
assert_eq!(subtable.glyph_index(0x42), Some(GlyphId(10)));
assert_eq!(subtable.glyph_index(0x43), Some(GlyphId(100)));
assert_eq!(subtable.glyph_index(0x44), Some(GlyphId(1000)));
assert_eq!(subtable.glyph_index(0x45), Some(GlyphId(10000)));
assert_eq!(subtable.glyph_index(0x46), None);
}
#[test]
fn unordered_chars_and_ids() {
let data = convert(&[
UInt16(4), // format
UInt16(64), // subtable size
UInt16(0), // language ID
UInt16(12), // 2 x segCount
UInt16(8), // search range
UInt16(2), // entry selector
UInt16(4), // range shift
// End character codes
UInt16(80), // char code [0]
UInt16(256), // char code [1]
UInt16(336), // char code [2]
UInt16(512), // char code [3]
UInt16(592), // char code [4]
UInt16(65535), // char code [5]
UInt16(0), // reserved
// Start character codes
UInt16(80), // char code [0]
UInt16(256), // char code [1]
UInt16(336), // char code [2]
UInt16(512), // char code [3]
UInt16(592), // char code [4]
UInt16(65535), // char code [5]
// Deltas
Int16(-79), // delta [0]
Int16(-246), // delta [1]
Int16(-236), // delta [2]
Int16(488), // delta [3]
Int16(9408), // delta [4]
Int16(1), // delta [5]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
UInt16(0), // offset [2]
UInt16(0), // offset [3]
UInt16(0), // offset [4]
UInt16(0), // offset [5]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0x40), None);
assert_eq!(subtable.glyph_index(0x50), Some(GlyphId(1)));
assert_eq!(subtable.glyph_index(0x100), Some(GlyphId(10)));
assert_eq!(subtable.glyph_index(0x150), Some(GlyphId(100)));
assert_eq!(subtable.glyph_index(0x200), Some(GlyphId(1000)));
assert_eq!(subtable.glyph_index(0x250), Some(GlyphId(10000)));
assert_eq!(subtable.glyph_index(0x300), None);
}
#[test]
fn no_end_codes() {
let data = convert(&[
UInt16(4), // format
UInt16(28), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(73), // char code [0]
// 0xFF, 0xFF, // char code [1] <-- removed
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
// 0xFF, 0xFF, // char code [1] <-- removed
// Deltas
Int16(-64), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
]);
assert!(cmap::Subtable4::parse(&data).is_none());
}
#[test]
fn invalid_segment_count() {
let data = convert(&[
UInt16(4), // format
UInt16(32), // subtable size
UInt16(0), // language ID
UInt16(1), // 2 x segCount <-- must be more than 1
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(-64), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
]);
assert!(cmap::Subtable4::parse(&data).is_none());
}
#[test]
fn only_end_segments() {
let data = convert(&[
UInt16(4), // format
UInt16(32), // subtable size
UInt16(0), // language ID
UInt16(2), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65535), // char code [1]
// Deltas
Int16(-64), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
// Should not loop forever.
assert_eq!(subtable.glyph_index(0x41), None);
}
#[test]
fn invalid_length() {
let data = convert(&[
UInt16(4), // format
UInt16(16), // subtable size <-- the size should be 32, but we don't check it anyway
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(-64), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0x41), Some(GlyphId(1)));
assert_eq!(subtable.glyph_index(0x42), None);
}
#[test]
fn codepoint_out_of_range() {
let data = convert(&[
UInt16(4), // format
UInt16(32), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(-64), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(0), // offset [0]
UInt16(0), // offset [1]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
// Format 4 support only u16 codepoints, so we have to bail immediately otherwise.
assert_eq!(subtable.glyph_index(0x1FFFF), None);
}
#[test]
fn zero() {
let data = convert(&[
UInt16(4), // format
UInt16(42), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(69), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(0), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(4), // offset [0]
UInt16(0), // offset [1]
// Glyph index array
UInt16(0), // glyph ID [0] <-- indicates missing glyph
UInt16(10), // glyph ID [1]
UInt16(100), // glyph ID [2]
UInt16(1000), // glyph ID [3]
UInt16(10000), // glyph ID [4]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(0x41), None);
}
#[test]
fn invalid_offset() {
let data = convert(&[
UInt16(4), // format
UInt16(42), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(69), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(65), // char code [0]
UInt16(65535), // char code [1]
// Deltas
Int16(0), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(4), // offset [0]
UInt16(65535), // offset [1]
// Glyph index array
UInt16(1), // glyph ID [0]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
assert_eq!(subtable.glyph_index(65535), None);
}
#[test]
fn collect_codepoints() {
let data = convert(&[
UInt16(4), // format
UInt16(24), // subtable size
UInt16(0), // language ID
UInt16(4), // 2 x segCount
UInt16(2), // search range
UInt16(0), // entry selector
UInt16(2), // range shift
// End character codes
UInt16(34), // char code [0]
UInt16(65535), // char code [1]
UInt16(0), // reserved
// Start character codes
UInt16(27), // char code [0]
UInt16(65533), // char code [1]
// Deltas
Int16(0), // delta [0]
Int16(1), // delta [1]
// Offsets into Glyph index array
UInt16(4), // offset [0]
UInt16(0), // offset [1]
// Glyph index array
UInt16(0), // glyph ID [0]
UInt16(10), // glyph ID [1]
]);
let subtable = cmap::Subtable4::parse(&data).unwrap();
let mut vec = vec![];
subtable.codepoints(|c| vec.push(c));
assert_eq!(vec, [27, 28, 29, 30, 31, 32, 33, 34, 65533, 65534, 65535]);
}
}

View File

@@ -0,0 +1,104 @@
use crate::{convert, Unit::*};
use ttf_parser::colr::{self, Painter};
use ttf_parser::{cpal, GlyphId, RgbaColor};
#[test]
fn basic() {
let cpal_data = convert(&[
UInt16(0), // version
UInt16(3), // number of palette entries
UInt16(1), // number of palettes
UInt16(3), // number of colors
UInt32(14), // offset to colors
UInt16(0), // index of palette 0's first color
UInt8(10), UInt8(15), UInt8(20), UInt8(25), // color 0
UInt8(30), UInt8(35), UInt8(40), UInt8(45), // color 1
UInt8(50), UInt8(55), UInt8(60), UInt8(65), // color 2
]);
let colr_data = convert(&[
UInt16(0), // version
UInt16(3), // number of base glyphs
UInt32(14), // offset to base glyphs
UInt32(32), // offset to layers
UInt16(4), // number of layers
UInt16(2), UInt16(2), UInt16(2), // base glyph 0 (id 2)
UInt16(3), UInt16(0), UInt16(3), // base glyph 1 (id 3)
UInt16(7), UInt16(1), UInt16(1), // base glyph 2 (id 7)
UInt16(10), UInt16(2), // layer 0
UInt16(11), UInt16(1), // layer 1
UInt16(12), UInt16(2), // layer 2
UInt16(13), UInt16(0), // layer 3
]);
let cpal = cpal::Table::parse(&cpal_data).unwrap();
let colr = colr::Table::parse(cpal, &colr_data).unwrap();
let paint = |id| {
let mut painter = VecPainter(vec![]);
colr.paint(GlyphId(id), 0, &mut painter).map(|_| painter.0)
};
let a = RgbaColor::new(20, 15, 10, 25);
let b = RgbaColor::new(40, 35, 30, 45);
let c = RgbaColor::new(60, 55, 50, 65);
assert_eq!(cpal.get(0, 0), Some(a));
assert_eq!(cpal.get(0, 1), Some(b));
assert_eq!(cpal.get(0, 2), Some(c));
assert_eq!(cpal.get(0, 3), None);
assert_eq!(cpal.get(1, 0), None);
assert!(!colr.contains(GlyphId(1)));
assert!(colr.contains(GlyphId(2)));
assert!(colr.contains(GlyphId(3)));
assert!(!colr.contains(GlyphId(4)));
assert!(!colr.contains(GlyphId(5)));
assert!(!colr.contains(GlyphId(6)));
assert!(colr.contains(GlyphId(7)));
assert_eq!(paint(1), None);
assert_eq!(paint(2).unwrap(), vec![
Command::Outline(12),
Command::PaintColor(c),
Command::Outline(13),
Command::PaintColor(a),
]);
assert_eq!(paint(3).unwrap(), vec![
Command::Outline(10),
Command::PaintColor(c),
Command::Outline(11),
Command::PaintColor(b),
Command::Outline(12),
Command::PaintColor(c),
]);
assert_eq!(paint(7).unwrap(), vec![
Command::Outline(11),
Command::PaintColor(b),
]);
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum Command {
Outline(u16),
Foreground,
PaintColor(RgbaColor),
}
struct VecPainter(Vec<Command>);
impl Painter for VecPainter {
fn outline(&mut self, glyph_id: GlyphId) {
self.0.push(Command::Outline(glyph_id.0));
}
fn paint_foreground(&mut self) {
self.0.push(Command::Foreground);
}
fn paint_color(&mut self, color: RgbaColor) {
self.0.push(Command::PaintColor(color));
}
}

View File

@@ -0,0 +1,83 @@
use ttf_parser::feat::Table;
use crate::{convert, Unit::*};
#[test]
fn basic() {
let data = convert(&[
Fixed(1.0), // version
UInt16(4), // number of features
UInt16(0), // reserved
UInt32(0), // reserved
// Feature Name [0]
UInt16(0), // feature
UInt16(1), // number of settings
UInt32(60), // offset to settings table
UInt16(0), // flags: none
UInt16(260), // name index
// Feature Name [1]
UInt16(1), // feature
UInt16(1), // number of settings
UInt32(64), // offset to settings table
UInt16(0), // flags: none
UInt16(256), // name index
// Feature Name [2]
UInt16(3), // feature
UInt16(3), // number of settings
UInt32(68), // offset to settings table
Raw(&[0x80, 0x00]), // flags: exclusive
UInt16(262), // name index
// Feature Name [3]
UInt16(6), // feature
UInt16(2), // number of settings
UInt32(80), // offset to settings table
Raw(&[0xC0, 0x01]), // flags: exclusive and other
UInt16(258), // name index
// Setting Name [0]
UInt16(0), // setting
UInt16(261), // name index
// Setting Name [1]
UInt16(2), // setting
UInt16(257), // name index
// Setting Name [2]
UInt16(0), // setting
UInt16(268), // name index
UInt16(3), // setting
UInt16(264), // name index
UInt16(4), // setting
UInt16(265), // name index
// Setting Name [3]
UInt16(0), // setting
UInt16(259), // name index
UInt16(1), // setting
UInt16(260), // name index
]);
let table = Table::parse(&data).unwrap();
assert_eq!(table.names.len(), 4);
let feature0 = table.names.get(0).unwrap();
assert_eq!(feature0.feature, 0);
assert_eq!(feature0.setting_names.len(), 1);
assert_eq!(feature0.exclusive, false);
assert_eq!(feature0.name_index, 260);
let feature2 = table.names.get(2).unwrap();
assert_eq!(feature2.feature, 3);
assert_eq!(feature2.setting_names.len(), 3);
assert_eq!(feature2.exclusive, true);
assert_eq!(feature2.setting_names.get(1).unwrap().setting, 3);
assert_eq!(feature2.setting_names.get(1).unwrap().name_index, 264);
let feature3 = table.names.get(3).unwrap();
assert_eq!(feature3.default_setting_index, 1);
assert_eq!(feature3.exclusive, true);
}

View File

@@ -0,0 +1,47 @@
use std::fmt::Write;
struct Builder(String);
impl ttf_parser::OutlineBuilder for Builder {
fn move_to(&mut self, x: f32, y: f32) {
write!(&mut self.0, "M {} {} ", x, y).unwrap();
}
fn line_to(&mut self, x: f32, y: f32) {
write!(&mut self.0, "L {} {} ", x, y).unwrap();
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap();
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap();
}
fn close(&mut self) {
write!(&mut self.0, "Z ").unwrap();
}
}
#[test]
fn endless_loop() {
let data = b"\x00\x01\x00\x00\x00\x0f\x00\x10\x00PTT-W\x002h\xd7\x81x\x00\
\x00\x00?L\xbaN\x00c\x9a\x9e\x8f\x96\xe3\xfeu\xff\x00\xb2\x00@\x03\x00\xb8\
cvt 5:\x00\x00\x00\xb5\xf8\x01\x00\x03\x9ckEr\x92\xd7\xe6\x98M\xdc\x00\x00\
\x03\xe0\x00\x00\x00dglyf\"\t\x15`\x00\x00\x03\xe0\x00\x00\x00dglyf\"\t\x15\
`\x00\x00\x00 \x00\x00\x00\xfc\x97\x9fmx\x87\xc9\xc8\xfe\x00\x00\xbad\xff\
\xff\xf1\xc8head\xc7\x17\xce[\x00\x00\x00\xfc\x00\x00\x006hhea\x03\xc6\x05\
\xe4\x00\x00\x014\x00\x00\x00$hmtx\xc9\xfdq\xed\x00\x00\xb5\xf8\x01\x00\x03\
\x9ckEr\x92\xd7\xe6\xdch\x00\x00\xc9d\x00\x00\x04 loca\x00M\x82\x11\x00\x00\
\x00\x06\x00\x00\x00\xa0maxp\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 name\
\xf4\xd6\xfe\xad\x00OTTO\x00\x02gpost5;5\xe1\x00\x00\xb0P\x00\x00\x01\xf0perp%\
\xb0{\x04\x93D\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\x00\x00\xe1!yf%1\
\x08\x95\x00\x00\x00\x00\x00\xaa\x06\x80fmtx\x02\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\xcc\xff\
\xce\x03CCCCCCCCC\x00\x00\x00\x00\x00C\x00\x00\x00\x00\xb5\xf8\x01\x00\x00\x9c";
let face = ttf_parser::Face::parse(data, 0).unwrap();
let _ = face.outline_glyph(ttf_parser::GlyphId(0), &mut Builder(String::new()));
}

View File

@@ -0,0 +1,114 @@
use std::num::NonZeroU16;
use ttf_parser::GlyphId;
use ttf_parser::hmtx::Table;
use crate::{convert, Unit::*};
macro_rules! nzu16 {
($n:expr) => { NonZeroU16::new($n).unwrap() };
}
#[test]
fn simple_case() {
let data = convert(&[
UInt16(1), // advance width [0]
Int16(2), // side bearing [0]
]);
let table = Table::parse(1, nzu16!(1), &data).unwrap();
assert_eq!(table.advance(GlyphId(0)), Some(1));
assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
}
#[test]
fn empty() {
assert!(Table::parse(1, nzu16!(1), &[]).is_none());
}
#[test]
fn zero_metrics() {
let data = convert(&[
UInt16(1), // advance width [0]
Int16(2), // side bearing [0]
]);
assert!(Table::parse(0, nzu16!(1), &data).is_none());
}
#[test]
fn smaller_than_glyphs_count() {
let data = convert(&[
UInt16(1), // advance width [0]
Int16(2), // side bearing [0]
Int16(3), // side bearing [1]
]);
let table = Table::parse(1, nzu16!(2), &data).unwrap();
assert_eq!(table.advance(GlyphId(0)), Some(1));
assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
assert_eq!(table.advance(GlyphId(1)), Some(1));
assert_eq!(table.side_bearing(GlyphId(1)), Some(3));
}
#[test]
fn no_additional_side_bearings() {
let data = convert(&[
UInt16(1), // advance width [0]
Int16(2), // side bearing [0]
// A single side bearing should be present here.
// We should simply ignore it and not return None during Table parsing.
]);
let table = Table::parse(1, nzu16!(2), &data).unwrap();
assert_eq!(table.advance(GlyphId(0)), Some(1));
assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
}
#[test]
fn less_metrics_than_glyphs() {
let data = convert(&[
UInt16(1), // advance width [0]
Int16(2), // side bearing [0]
UInt16(3), // advance width [1]
Int16(4), // side bearing [1]
Int16(5), // side bearing [2]
]);
let table = Table::parse(2, nzu16!(1), &data).unwrap();
assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
assert_eq!(table.side_bearing(GlyphId(1)), Some(4));
assert_eq!(table.side_bearing(GlyphId(2)), None);
}
#[test]
fn glyph_out_of_bounds_0() {
let data = convert(&[
UInt16(1), // advance width [0]
Int16(2), // side bearing [0]
]);
let table = Table::parse(1, nzu16!(1), &data).unwrap();
assert_eq!(table.advance(GlyphId(0)), Some(1));
assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
assert_eq!(table.advance(GlyphId(1)), None);
assert_eq!(table.side_bearing(GlyphId(1)), None);
}
#[test]
fn glyph_out_of_bounds_1() {
let data = convert(&[
UInt16(1), // advance width [0]
Int16(2), // side bearing [0]
Int16(3), // side bearing [1]
]);
let table = Table::parse(1, nzu16!(2), &data).unwrap();
assert_eq!(table.advance(GlyphId(1)), Some(1));
assert_eq!(table.side_bearing(GlyphId(1)), Some(3));
assert_eq!(table.advance(GlyphId(2)), None);
assert_eq!(table.side_bearing(GlyphId(2)), None);
}

View File

@@ -0,0 +1,176 @@
#[rustfmt::skip] mod aat;
#[rustfmt::skip] mod ankr;
#[rustfmt::skip] mod cff1;
#[rustfmt::skip] mod cmap;
#[rustfmt::skip] mod colr;
#[rustfmt::skip] mod feat;
#[rustfmt::skip] mod glyf;
#[rustfmt::skip] mod hmtx;
#[rustfmt::skip] mod maxp;
#[rustfmt::skip] mod sbix;
#[rustfmt::skip] mod trak;
use ttf_parser::{fonts_in_collection, Face, FaceParsingError};
#[allow(dead_code)]
#[derive(Clone, Copy)]
pub enum Unit {
Raw(&'static [u8]),
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Fixed(f32),
}
pub fn convert(units: &[Unit]) -> Vec<u8> {
let mut data = Vec::with_capacity(256);
for v in units {
convert_unit(*v, &mut data);
}
data
}
fn convert_unit(unit: Unit, data: &mut Vec<u8>) {
match unit {
Unit::Raw(bytes) => {
data.extend_from_slice(bytes);
}
Unit::Int8(n) => {
data.extend_from_slice(&i8::to_be_bytes(n));
}
Unit::UInt8(n) => {
data.extend_from_slice(&u8::to_be_bytes(n));
}
Unit::Int16(n) => {
data.extend_from_slice(&i16::to_be_bytes(n));
}
Unit::UInt16(n) => {
data.extend_from_slice(&u16::to_be_bytes(n));
}
Unit::Int32(n) => {
data.extend_from_slice(&i32::to_be_bytes(n));
}
Unit::UInt32(n) => {
data.extend_from_slice(&u32::to_be_bytes(n));
}
Unit::Fixed(n) => {
data.extend_from_slice(&i32::to_be_bytes((n * 65536.0) as i32));
}
}
}
#[test]
fn empty_font() {
assert_eq!(
Face::parse(&[], 0).unwrap_err(),
FaceParsingError::UnknownMagic
);
}
#[test]
fn zero_tables() {
use Unit::*;
let data = convert(&[
Raw(&[0x00, 0x01, 0x00, 0x00]), // magic
UInt16(0), // numTables
UInt16(0), // searchRange
UInt16(0), // entrySelector
UInt16(0), // rangeShift
]);
assert_eq!(
Face::parse(&data, 0).unwrap_err(),
FaceParsingError::NoHeadTable
);
}
#[test]
fn tables_count_overflow() {
use Unit::*;
let data = convert(&[
Raw(&[0x00, 0x01, 0x00, 0x00]), // magic
UInt16(std::u16::MAX), // numTables
UInt16(0), // searchRange
UInt16(0), // entrySelector
UInt16(0), // rangeShift
]);
assert_eq!(
Face::parse(&data, 0).unwrap_err(),
FaceParsingError::MalformedFont
);
}
#[test]
fn empty_font_collection() {
use Unit::*;
let data = convert(&[
Raw(&[0x74, 0x74, 0x63, 0x66]), // magic
UInt16(0), // majorVersion
UInt16(0), // minorVersion
UInt32(0), // numFonts
]);
assert_eq!(fonts_in_collection(&data), Some(0));
assert_eq!(
Face::parse(&data, 0).unwrap_err(),
FaceParsingError::FaceIndexOutOfBounds
);
}
#[test]
fn font_collection_num_fonts_overflow() {
use Unit::*;
let data = convert(&[
Raw(&[0x74, 0x74, 0x63, 0x66]), // magic
UInt16(0), // majorVersion
UInt16(0), // minorVersion
UInt32(std::u32::MAX), // numFonts
]);
assert_eq!(fonts_in_collection(&data), Some(std::u32::MAX));
assert_eq!(
Face::parse(&data, 0).unwrap_err(),
FaceParsingError::MalformedFont
);
}
#[test]
fn font_index_overflow() {
use Unit::*;
let data = convert(&[
Raw(&[0x74, 0x74, 0x63, 0x66]), // magic
UInt16(0), // majorVersion
UInt16(0), // minorVersion
UInt32(1), // numFonts
UInt32(12), // offset [0]
]);
assert_eq!(fonts_in_collection(&data), Some(1));
assert_eq!(
Face::parse(&data, std::u32::MAX).unwrap_err(),
FaceParsingError::FaceIndexOutOfBounds
);
}
#[test]
fn font_index_overflow_on_regular_font() {
use Unit::*;
let data = convert(&[
Raw(&[0x00, 0x01, 0x00, 0x00]), // magic
UInt16(0), // numTables
UInt16(0), // searchRange
UInt16(0), // entrySelector
UInt16(0), // rangeShift
]);
assert_eq!(fonts_in_collection(&data), None);
assert_eq!(
Face::parse(&data, 1).unwrap_err(),
FaceParsingError::FaceIndexOutOfBounds
);
}

View File

@@ -0,0 +1,65 @@
use std::num::NonZeroU16;
use ttf_parser::maxp::Table;
use crate::{convert, Unit::*};
#[test]
fn version_05() {
let table = Table::parse(&convert(&[
Fixed(0.3125), // version
UInt16(1), // number of glyphs
])).unwrap();
assert_eq!(table.number_of_glyphs, NonZeroU16::new(1).unwrap());
}
#[test]
fn version_1_full() {
let table = Table::parse(&convert(&[
Fixed(1.0), // version
UInt16(1), // number of glyphs
UInt16(0), // maximum points in a non-composite glyph
UInt16(0), // maximum contours in a non-composite glyph
UInt16(0), // maximum points in a composite glyph
UInt16(0), // maximum contours in a composite glyph
UInt16(0), // maximum zones
UInt16(0), // maximum twilight points
UInt16(0), // number of Storage Area locations
UInt16(0), // number of FDEFs
UInt16(0), // number of IDEFs
UInt16(0), // maximum stack depth
UInt16(0), // maximum byte count for glyph instructions
UInt16(0), // maximum number of components
UInt16(0), // maximum levels of recursion
])).unwrap();
assert_eq!(table.number_of_glyphs, NonZeroU16::new(1).unwrap());
}
#[test]
fn version_1_trimmed() {
// We don't really care about the data after the number of glyphs.
let table = Table::parse(&convert(&[
Fixed(1.0), // version
UInt16(1), // number of glyphs
])).unwrap();
assert_eq!(table.number_of_glyphs, NonZeroU16::new(1).unwrap());
}
#[test]
fn unknown_version() {
let table = Table::parse(&convert(&[
Fixed(0.0), // version
UInt16(1), // number of glyphs
]));
assert!(table.is_none());
}
#[test]
fn zero_glyphs() {
let table = Table::parse(&convert(&[
Fixed(0.3125), // version
UInt16(0), // number of glyphs
]));
assert!(table.is_none());
}
// TODO: what to do when the number of glyphs is 0xFFFF?
// we're actually checking this in loca

View File

@@ -0,0 +1,135 @@
use std::num::NonZeroU16;
use ttf_parser::{GlyphId, RasterImageFormat};
use ttf_parser::sbix::Table;
use crate::{convert, Unit::*};
#[test]
fn single_glyph() {
let data = convert(&[
UInt16(1), // version
UInt16(0), // flags
UInt32(1), // number of strikes
UInt32(12), // strike offset [0]
// Strike [0]
UInt16(20), // pixels_per_em
UInt16(72), // ppi
UInt32(12), // glyph data offset [0]
UInt32(44), // glyph data offset [1]
// Glyph Data [0]
UInt16(1), // x
UInt16(2), // y
Raw(b"png "), // type tag
// PNG data, just the part we need
Raw(&[0x89, 0x50, 0x4E, 0x47]),
Raw(&[0x0D, 0x0A, 0x1A, 0x0A]),
Raw(&[0x00, 0x00, 0x00, 0x0D]),
Raw(&[0x49, 0x48, 0x44, 0x52]),
UInt32(20), // width
UInt32(30), // height
]);
let table = Table::parse(NonZeroU16::new(1).unwrap(), &data).unwrap();
assert_eq!(table.strikes.len(), 1);
let strike = table.strikes.get(0).unwrap();
assert_eq!(strike.pixels_per_em, 20);
assert_eq!(strike.ppi, 72);
assert_eq!(strike.len(), 1);
let glyph_data = strike.get(GlyphId(0)).unwrap();
assert_eq!(glyph_data.x, 1);
assert_eq!(glyph_data.y, 2);
assert_eq!(glyph_data.width, 20);
assert_eq!(glyph_data.height, 30);
assert_eq!(glyph_data.pixels_per_em, 20);
assert_eq!(glyph_data.format, RasterImageFormat::PNG);
assert_eq!(glyph_data.data.len(), 24);
}
#[test]
fn duplicate_glyph() {
let data = convert(&[
UInt16(1), // version
UInt16(0), // flags
UInt32(1), // number of strikes
UInt32(12), // strike offset [0]
// Strike [0]
UInt16(20), // pixels_per_em
UInt16(72), // ppi
UInt32(16), // glyph data offset [0]
UInt32(48), // glyph data offset [1]
UInt32(58), // glyph data offset [2]
// Glyph Data [0]
UInt16(1), // x
UInt16(2), // y
Raw(b"png "), // type tag
// PNG data, just the part we need
Raw(&[0x89, 0x50, 0x4E, 0x47]),
Raw(&[0x0D, 0x0A, 0x1A, 0x0A]),
Raw(&[0x00, 0x00, 0x00, 0x0D]),
Raw(&[0x49, 0x48, 0x44, 0x52]),
UInt32(20), // width
UInt32(30), // height
// Glyph Data [1]
UInt16(3), // x
UInt16(4), // y
Raw(b"dupe"), // type tag
UInt16(0), // glyph id
]);
let table = Table::parse(NonZeroU16::new(2).unwrap(), &data).unwrap();
assert_eq!(table.strikes.len(), 1);
let strike = table.strikes.get(0).unwrap();
assert_eq!(strike.pixels_per_em, 20);
assert_eq!(strike.ppi, 72);
assert_eq!(strike.len(), 2);
let glyph_data = strike.get(GlyphId(1)).unwrap();
assert_eq!(glyph_data.x, 1);
assert_eq!(glyph_data.y, 2);
assert_eq!(glyph_data.width, 20);
assert_eq!(glyph_data.height, 30);
assert_eq!(glyph_data.pixels_per_em, 20);
assert_eq!(glyph_data.format, RasterImageFormat::PNG);
assert_eq!(glyph_data.data.len(), 24);
}
#[test]
fn recursive() {
let data = convert(&[
UInt16(1), // version
UInt16(0), // flags
UInt32(1), // number of strikes
UInt32(12), // strike offset [0]
// Strike [0]
UInt16(20), // pixels_per_em
UInt16(72), // ppi
UInt32(16), // glyph data offset [0]
UInt32(26), // glyph data offset [1]
UInt32(36), // glyph data offset [2]
// Glyph Data [0]
UInt16(1), // x
UInt16(2), // y
Raw(b"dupe"), // type tag
UInt16(0), // glyph id
// Glyph Data [1]
UInt16(1), // x
UInt16(2), // y
Raw(b"dupe"), // type tag
UInt16(0), // glyph id
]);
let table = Table::parse(NonZeroU16::new(2).unwrap(), &data).unwrap();
let strike = table.strikes.get(0).unwrap();
assert!(strike.get(GlyphId(0)).is_none());
assert!(strike.get(GlyphId(1)).is_none());
}

View File

@@ -0,0 +1,88 @@
use ttf_parser::trak::Table;
use crate::{convert, Unit::*};
#[test]
fn empty() {
let data = convert(&[
Fixed(1.0), // version
UInt16(0), // format
UInt16(0), // horizontal data offset
UInt16(0), // vertical data offset
UInt16(0), // padding
]);
let table = Table::parse(&data).unwrap();
assert_eq!(table.horizontal.tracks.len(), 0);
assert_eq!(table.horizontal.sizes.len(), 0);
assert_eq!(table.vertical.tracks.len(), 0);
assert_eq!(table.vertical.sizes.len(), 0);
}
#[test]
fn basic() {
let data = convert(&[
Fixed(1.0), // version
UInt16(0), // format
UInt16(12), // horizontal data offset
UInt16(0), // vertical data offset
UInt16(0), // padding
// TrackData
UInt16(3), // number of tracks
UInt16(2), // number of sizes
UInt32(44), // offset to size table
// TrackTableEntry [0]
Fixed(-1.0), // track
UInt16(256), // name index
UInt16(52), // offset of the two per-size tracking values
// TrackTableEntry [1]
Fixed(0.0), // track
UInt16(258), // name index
UInt16(60), // offset of the two per-size tracking values
// TrackTableEntry [2]
Fixed(1.0), // track
UInt16(257), // name index
UInt16(56), // offset of the two per-size tracking values
// Size [0]
Fixed(12.0), // points
// Size [1]
Fixed(24.0), // points
// Per-size tracking values.
Int16(-15),
Int16(-7),
Int16(50),
Int16(20),
Int16(0),
Int16(0),
]);
let table = Table::parse(&data).unwrap();
assert_eq!(table.horizontal.tracks.len(), 3);
assert_eq!(table.horizontal.tracks.get(0).unwrap().value, -1.0);
assert_eq!(table.horizontal.tracks.get(1).unwrap().value, 0.0);
assert_eq!(table.horizontal.tracks.get(2).unwrap().value, 1.0);
assert_eq!(table.horizontal.tracks.get(0).unwrap().name_index, 256);
assert_eq!(table.horizontal.tracks.get(1).unwrap().name_index, 258);
assert_eq!(table.horizontal.tracks.get(2).unwrap().name_index, 257);
assert_eq!(table.horizontal.tracks.get(0).unwrap().values.len(), 2);
assert_eq!(table.horizontal.tracks.get(0).unwrap().values.get(0).unwrap(), -15);
assert_eq!(table.horizontal.tracks.get(0).unwrap().values.get(1).unwrap(), -7);
assert_eq!(table.horizontal.tracks.get(1).unwrap().values.len(), 2);
assert_eq!(table.horizontal.tracks.get(1).unwrap().values.get(0).unwrap(), 0);
assert_eq!(table.horizontal.tracks.get(1).unwrap().values.get(1).unwrap(), 0);
assert_eq!(table.horizontal.tracks.get(2).unwrap().values.len(), 2);
assert_eq!(table.horizontal.tracks.get(2).unwrap().values.get(0).unwrap(), 50);
assert_eq!(table.horizontal.tracks.get(2).unwrap().values.get(1).unwrap(), 20);
assert_eq!(table.horizontal.sizes.len(), 2);
assert_eq!(table.horizontal.sizes.get(0).unwrap().0, 12.0);
assert_eq!(table.horizontal.sizes.get(1).unwrap().0, 24.0);
assert_eq!(table.vertical.tracks.len(), 0);
assert_eq!(table.vertical.sizes.len(), 0);
}