use oxideav_ttf::Font;
const DEJAVU_MONO: &[u8] = include_bytes!("fixtures/DejaVuSansMono.ttf");
const DEJAVU_SANS: &[u8] = include_bytes!("fixtures/DejaVuSans.ttf");
const INTER: &[u8] = include_bytes!("fixtures/InterVariable.ttf");
#[test]
fn dejavu_mono_has_no_base() {
let f = Font::from_bytes(DEJAVU_MONO).unwrap();
assert!(!f.has_base());
assert!(f.base_table().is_none());
assert!(f
.base_horiz_y_for_script_baseline(*b"latn", *b"romn")
.is_none());
assert!(f
.base_vert_x_for_script_baseline(*b"latn", *b"romn")
.is_none());
}
#[test]
fn dejavu_sans_has_no_base() {
let f = Font::from_bytes(DEJAVU_SANS).unwrap();
assert!(!f.has_base());
}
#[test]
fn inter_has_no_base() {
let f = Font::from_bytes(INTER).unwrap();
assert!(!f.has_base());
}
fn build_minimal_sfnt(tables: &[(&[u8; 4], Vec<u8>)]) -> Vec<u8> {
let n = tables.len() as u16;
let mut out = Vec::new();
out.extend_from_slice(&0x00010000u32.to_be_bytes());
out.extend_from_slice(&n.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes());
let dir_size = 16usize * n as usize;
let mut payload_offset = 12usize + dir_size;
let mut records = Vec::with_capacity(n as usize);
for (tag, payload) in tables {
records.push((*tag, payload_offset as u32, payload.len() as u32));
payload_offset += payload.len();
while payload_offset % 4 != 0 {
payload_offset += 1;
}
}
for (tag, offset, length) in &records {
out.extend_from_slice(tag.as_slice());
out.extend_from_slice(&0u32.to_be_bytes());
out.extend_from_slice(&offset.to_be_bytes());
out.extend_from_slice(&length.to_be_bytes());
}
for (_tag, payload) in tables {
out.extend_from_slice(payload);
while out.len() % 4 != 0 {
out.push(0);
}
}
out
}
fn make_head(units_per_em: u16, index_to_loc_format: i16) -> Vec<u8> {
let mut h = vec![0u8; 54];
h[0..4].copy_from_slice(&0x00010000u32.to_be_bytes());
h[4..8].copy_from_slice(&0x00010000u32.to_be_bytes());
h[12..16].copy_from_slice(&0x5F0F3CF5u32.to_be_bytes());
h[18..20].copy_from_slice(&units_per_em.to_be_bytes());
h[50..52].copy_from_slice(&index_to_loc_format.to_be_bytes());
h
}
fn make_hhea(num_h_metrics: u16) -> Vec<u8> {
let mut h = vec![0u8; 36];
h[0..4].copy_from_slice(&0x00010000u32.to_be_bytes());
h[34..36].copy_from_slice(&num_h_metrics.to_be_bytes());
h
}
fn make_maxp(num_glyphs: u16) -> Vec<u8> {
let mut h = vec![0u8; 6];
h[0..4].copy_from_slice(&0x00005000u32.to_be_bytes());
h[4..6].copy_from_slice(&num_glyphs.to_be_bytes());
h
}
fn make_cmap_empty() -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes());
out.extend_from_slice(&3u16.to_be_bytes());
out.extend_from_slice(&1u16.to_be_bytes());
out.extend_from_slice(&12u32.to_be_bytes());
let mut sub = Vec::new();
sub.extend_from_slice(&4u16.to_be_bytes());
sub.extend_from_slice(&32u16.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&2u16.to_be_bytes());
sub.extend_from_slice(&2u16.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&0xFFFFu16.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
sub.extend_from_slice(&0xFFFFu16.to_be_bytes());
sub.extend_from_slice(&1u16.to_be_bytes());
sub.extend_from_slice(&0u16.to_be_bytes());
let total = sub.len() as u16;
sub[2..4].copy_from_slice(&total.to_be_bytes());
out.extend_from_slice(&sub);
out
}
fn make_name_empty() -> Vec<u8> {
let mut out = vec![0u8; 6];
out[0..2].copy_from_slice(&0u16.to_be_bytes());
out[2..4].copy_from_slice(&0u16.to_be_bytes());
out[4..6].copy_from_slice(&6u16.to_be_bytes());
out
}
fn make_hmtx(num_h_metrics: u16, num_glyphs: u16) -> Vec<u8> {
let mut out = Vec::new();
for _ in 0..num_h_metrics {
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&0i16.to_be_bytes());
}
for _ in num_h_metrics..num_glyphs {
out.extend_from_slice(&0i16.to_be_bytes());
}
out
}
fn make_loca_short(num_glyphs: u16) -> Vec<u8> {
let mut out = Vec::new();
for _ in 0..=num_glyphs {
out.extend_from_slice(&0u16.to_be_bytes());
}
out
}
fn make_glyf_empty() -> Vec<u8> {
vec![0u8; 2]
}
fn make_base_horiz_two_scripts() -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let horiz_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let horiz_start = buf.len();
buf[horiz_off_pos..horiz_off_pos + 2].copy_from_slice(&(horiz_start as u16).to_be_bytes());
let tag_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let script_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let tag_start = buf.len();
buf[tag_off_pos..tag_off_pos + 2]
.copy_from_slice(&((tag_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&2u16.to_be_bytes());
buf.extend_from_slice(b"ideo");
buf.extend_from_slice(b"romn");
let script_start = buf.len();
buf[script_off_pos..script_off_pos + 2]
.copy_from_slice(&((script_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&2u16.to_be_bytes()); buf.extend_from_slice(b"hani");
let hani_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(b"latn");
let latn_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let hani_start = buf.len();
buf[hani_off_pos..hani_off_pos + 2]
.copy_from_slice(&((hani_start - script_start) as u16).to_be_bytes());
let hani_bv_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
let hani_bv_start = buf.len();
buf[hani_bv_off_pos..hani_bv_off_pos + 2]
.copy_from_slice(&((hani_bv_start - hani_start) as u16).to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&2u16.to_be_bytes()); let hani_bc0_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); let hani_bc1_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let hani_bc0_start = buf.len();
buf[hani_bc0_off_pos..hani_bc0_off_pos + 2]
.copy_from_slice(&((hani_bc0_start - hani_bv_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&0i16.to_be_bytes());
let hani_bc1_start = buf.len();
buf[hani_bc1_off_pos..hani_bc1_off_pos + 2]
.copy_from_slice(&((hani_bc1_start - hani_bv_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&120i16.to_be_bytes());
let latn_start = buf.len();
buf[latn_off_pos..latn_off_pos + 2]
.copy_from_slice(&((latn_start - script_start) as u16).to_be_bytes());
let latn_bv_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
let latn_bv_start = buf.len();
buf[latn_bv_off_pos..latn_bv_off_pos + 2]
.copy_from_slice(&((latn_bv_start - latn_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(&2u16.to_be_bytes());
let latn_bc0_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_bc1_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_bc0_start = buf.len();
buf[latn_bc0_off_pos..latn_bc0_off_pos + 2]
.copy_from_slice(&((latn_bc0_start - latn_bv_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&(-120i16).to_be_bytes());
let latn_bc1_start = buf.len();
buf[latn_bc1_off_pos..latn_bc1_off_pos + 2]
.copy_from_slice(&((latn_bc1_start - latn_bv_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&0i16.to_be_bytes());
buf
}
fn make_base_horiz_and_vert() -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let horiz_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let vert_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let horiz_start = buf.len();
buf[horiz_off_pos..horiz_off_pos + 2].copy_from_slice(&(horiz_start as u16).to_be_bytes());
let tag_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let script_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let tag_start = buf.len();
buf[tag_off_pos..tag_off_pos + 2]
.copy_from_slice(&((tag_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"romn");
let script_start = buf.len();
buf[script_off_pos..script_off_pos + 2]
.copy_from_slice(&((script_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"latn");
let latn_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_start = buf.len();
buf[latn_off_pos..latn_off_pos + 2]
.copy_from_slice(&((latn_start - script_start) as u16).to_be_bytes());
let latn_bv_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_bv_start = buf.len();
buf[latn_bv_off_pos..latn_bv_off_pos + 2]
.copy_from_slice(&((latn_bv_start - latn_start) as u16).to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
let latn_bc0_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_bc0_start = buf.len();
buf[latn_bc0_off_pos..latn_bc0_off_pos + 2]
.copy_from_slice(&((latn_bc0_start - latn_bv_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&0i16.to_be_bytes());
let vert_start = buf.len();
buf[vert_off_pos..vert_off_pos + 2].copy_from_slice(&(vert_start as u16).to_be_bytes());
let vtag_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let vscript_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let vtag_start = buf.len();
buf[vtag_off_pos..vtag_off_pos + 2]
.copy_from_slice(&((vtag_start - vert_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"ideo");
let vscript_start = buf.len();
buf[vscript_off_pos..vscript_off_pos + 2]
.copy_from_slice(&((vscript_start - vert_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"hani");
let hani_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let hani_start = buf.len();
buf[hani_off_pos..hani_off_pos + 2]
.copy_from_slice(&((hani_start - vscript_start) as u16).to_be_bytes());
let hani_bv_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let hani_bv_start = buf.len();
buf[hani_bv_off_pos..hani_bv_off_pos + 2]
.copy_from_slice(&((hani_bv_start - hani_start) as u16).to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
let hani_bc0_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let hani_bc0_start = buf.len();
buf[hani_bc0_off_pos..hani_bc0_off_pos + 2]
.copy_from_slice(&((hani_bc0_start - hani_bv_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&50i16.to_be_bytes());
buf
}
#[test]
fn synth_horiz_only_table_walks_both_scripts() {
let num_glyphs = 16u16;
let tables: Vec<(&[u8; 4], Vec<u8>)> = vec![
(b"head", make_head(1000, 0)),
(b"hhea", make_hhea(1)),
(b"maxp", make_maxp(num_glyphs)),
(b"cmap", make_cmap_empty()),
(b"name", make_name_empty()),
(b"hmtx", make_hmtx(1, num_glyphs)),
(b"loca", make_loca_short(num_glyphs)),
(b"glyf", make_glyf_empty()),
(b"BASE", make_base_horiz_two_scripts()),
];
let font_bytes = build_minimal_sfnt(&tables);
let font = Font::from_bytes(&font_bytes).expect("parse synth");
assert!(font.has_base());
let base = font.base_table().unwrap();
assert_eq!(base.major_version, 1);
assert_eq!(base.minor_version, 0);
let h = base.horiz_axis.as_ref().unwrap();
let tags = h.baseline_tags.as_ref().unwrap();
assert_eq!(tags, &vec![*b"ideo", *b"romn"]);
assert_eq!(h.base_scripts.len(), 2);
assert_eq!(
font.base_horiz_y_for_script_baseline(*b"latn", *b"romn"),
Some(0)
);
assert_eq!(
font.base_horiz_y_for_script_baseline(*b"latn", *b"ideo"),
Some(-120)
);
assert_eq!(
font.base_horiz_y_for_script_baseline(*b"hani", *b"ideo"),
Some(0)
);
assert_eq!(
font.base_horiz_y_for_script_baseline(*b"hani", *b"romn"),
Some(120)
);
let latn = h.base_script_for_tag(*b"latn").unwrap();
assert_eq!(latn.base_values.as_ref().unwrap().default_baseline_index, 1);
let hani = h.base_script_for_tag(*b"hani").unwrap();
assert_eq!(hani.base_values.as_ref().unwrap().default_baseline_index, 0);
assert!(font
.base_horiz_y_for_script_baseline(*b"arab", *b"romn")
.is_none());
assert!(font
.base_horiz_y_for_script_baseline(*b"latn", *b"hang")
.is_none());
assert!(font
.base_vert_x_for_script_baseline(*b"latn", *b"romn")
.is_none());
}
#[test]
fn synth_table_with_horiz_and_vert_axes() {
let num_glyphs = 16u16;
let tables: Vec<(&[u8; 4], Vec<u8>)> = vec![
(b"head", make_head(1000, 0)),
(b"hhea", make_hhea(1)),
(b"maxp", make_maxp(num_glyphs)),
(b"cmap", make_cmap_empty()),
(b"name", make_name_empty()),
(b"hmtx", make_hmtx(1, num_glyphs)),
(b"loca", make_loca_short(num_glyphs)),
(b"glyf", make_glyf_empty()),
(b"BASE", make_base_horiz_and_vert()),
];
let font_bytes = build_minimal_sfnt(&tables);
let font = Font::from_bytes(&font_bytes).expect("parse synth");
assert!(font.has_base());
let base = font.base_table().unwrap();
assert!(base.horiz_axis.is_some());
assert!(base.vert_axis.is_some());
assert_eq!(
font.base_horiz_y_for_script_baseline(*b"latn", *b"romn"),
Some(0)
);
assert!(font
.base_horiz_y_for_script_baseline(*b"hani", *b"ideo")
.is_none());
assert_eq!(
font.base_vert_x_for_script_baseline(*b"hani", *b"ideo"),
Some(50)
);
assert!(font
.base_vert_x_for_script_baseline(*b"latn", *b"romn")
.is_none());
}