use std::convert::TryInto;
use bitflags::bitflags;
use crate::binary::read::{ReadBinaryDep, ReadCtxt};
use crate::binary::write::{WriteBinary, WriteContext};
use crate::binary::{I16Be, U16Be, U32Be};
use crate::error::{ParseError, WriteError};
use crate::tables::Fixed;
#[derive(Clone)]
pub struct Os2 {
pub version: u16,
pub x_avg_char_width: i16,
pub us_weight_class: u16,
pub us_width_class: u16,
pub fs_type: u16,
pub y_subscript_x_size: i16,
pub y_subscript_y_size: i16,
pub y_subscript_x_offset: i16,
pub y_subscript_y_offset: i16,
pub y_superscript_x_size: i16,
pub y_superscript_y_size: i16,
pub y_superscript_x_offset: i16,
pub y_superscript_y_offset: i16,
pub y_strikeout_size: i16,
pub y_strikeout_position: i16,
pub s_family_class: i16,
pub panose: [u8; 10],
pub ul_unicode_range1: u32,
pub ul_unicode_range2: u32,
pub ul_unicode_range3: u32,
pub ul_unicode_range4: u32,
pub ach_vend_id: u32, pub fs_selection: FsSelection,
pub us_first_char_index: u16,
pub us_last_char_index: u16,
pub version0: Option<Version0>,
pub version1: Option<Version1>,
pub version2to4: Option<Version2to4>,
pub version5: Option<Version5>,
}
#[derive(Clone)]
pub struct Version0 {
pub s_typo_ascender: i16,
pub s_typo_descender: i16,
pub s_typo_line_gap: i16,
pub us_win_ascent: u16,
pub us_win_descent: u16,
}
#[derive(Clone)]
pub struct Version1 {
pub ul_code_page_range1: u32,
pub ul_code_page_range2: u32,
}
#[derive(Clone)]
pub struct Version2to4 {
pub s_x_height: i16,
pub s_cap_height: i16,
pub us_default_char: u16,
pub us_break_char: u16,
pub us_max_context: u16,
}
#[derive(Clone)]
pub struct Version5 {
pub us_lower_optical_point_size: u16,
pub us_upper_optical_point_size: u16,
}
bitflags! {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct FsSelection: u16 {
const ITALIC = 1 << 0;
const UNDERSCORE = 1 << 1;
const NEGATIVE = 1 << 2;
const OUTLINED = 1 << 3;
const STRIKEOUT = 1 << 4;
const BOLD = 1 << 5;
const REGULAR = 1 << 6;
const USE_TYPO_METRICS = 1 << 7;
const WWS = 1 << 8;
const OBLIQUE = 1 << 9;
}
}
impl Os2 {
pub(crate) fn value_to_width_class(value: Fixed) -> u16 {
const WIDTH_CLASS_MAP: &[(Fixed, u16)] = &[
(Fixed::from_raw(3276800), 1), (Fixed::from_raw(4096000), 2), (Fixed::from_raw(4915200), 3), (Fixed::from_raw(5734400), 4), (Fixed::from_raw(6553600), 5), (Fixed::from_raw(7372800), 6), (Fixed::from_raw(8192000), 7), (Fixed::from_raw(9830400), 8), (Fixed::from_raw(13107200), 9), ];
match WIDTH_CLASS_MAP.binary_search_by_key(&value, |&(val, _cls)| val) {
Ok(i) => WIDTH_CLASS_MAP[i].1,
Err(i) => {
if i < 1 {
return WIDTH_CLASS_MAP[0].1;
}
if i >= WIDTH_CLASS_MAP.len() {
return WIDTH_CLASS_MAP.last().unwrap().1;
}
let (a, clsa) = WIDTH_CLASS_MAP[i - 1];
let (b, clsb) = WIDTH_CLASS_MAP[i];
if (value - a) > (b - value) {
clsb
} else {
clsa
}
}
}
}
}
impl ReadBinaryDep for Os2 {
type HostType<'a> = Self;
type Args<'a> = usize;
fn read_dep(ctxt: &mut ReadCtxt<'_>, table_size: usize) -> Result<Self, ParseError> {
let version = ctxt.read::<U16Be>()?;
let x_avg_char_width = ctxt.read::<I16Be>()?;
let us_weight_class = ctxt.read::<U16Be>()?;
let us_width_class = ctxt.read::<U16Be>()?;
let fs_type = ctxt.read::<U16Be>()?;
let y_subscript_x_size = ctxt.read::<I16Be>()?;
let y_subscript_y_size = ctxt.read::<I16Be>()?;
let y_subscript_x_offset = ctxt.read::<I16Be>()?;
let y_subscript_y_offset = ctxt.read::<I16Be>()?;
let y_superscript_x_size = ctxt.read::<I16Be>()?;
let y_superscript_y_size = ctxt.read::<I16Be>()?;
let y_superscript_x_offset = ctxt.read::<I16Be>()?;
let y_superscript_y_offset = ctxt.read::<I16Be>()?;
let y_strikeout_size = ctxt.read::<I16Be>()?;
let y_strikeout_position = ctxt.read::<I16Be>()?;
let s_family_class = ctxt.read::<I16Be>()?;
let panose: [u8; 10] = ctxt.read_slice(10)?.try_into().unwrap();
let ul_unicode_range1 = ctxt.read::<U32Be>()?;
let ul_unicode_range2 = ctxt.read::<U32Be>()?;
let ul_unicode_range3 = ctxt.read::<U32Be>()?;
let ul_unicode_range4 = ctxt.read::<U32Be>()?;
let ach_vend_id = ctxt.read::<U32Be>()?;
let fs_selection = ctxt.read::<U16Be>().map(FsSelection::from_bits_truncate)?;
let us_first_char_index = ctxt.read::<U16Be>()?;
let us_last_char_index = ctxt.read::<U16Be>()?;
let version0 = if table_size >= 78 {
let s_typo_ascender = ctxt.read::<I16Be>()?;
let s_typo_descender = ctxt.read::<I16Be>()?;
let s_typo_line_gap = ctxt.read::<I16Be>()?;
let us_win_ascent = ctxt.read::<U16Be>()?;
let us_win_descent = ctxt.read::<U16Be>()?;
Some(Version0 {
s_typo_ascender,
s_typo_descender,
s_typo_line_gap,
us_win_ascent,
us_win_descent,
})
} else {
None
};
let version1 = if version >= 1 {
let ul_code_page_range1 = ctxt.read::<U32Be>()?;
let ul_code_page_range2 = ctxt.read::<U32Be>()?;
Some(Version1 {
ul_code_page_range1,
ul_code_page_range2,
})
} else {
None
};
let version2to4 = if version >= 2 {
let s_x_height = ctxt.read::<I16Be>()?;
let s_cap_height = ctxt.read::<I16Be>()?;
let us_default_char = ctxt.read::<U16Be>()?;
let us_break_char = ctxt.read::<U16Be>()?;
let us_max_context = ctxt.read::<U16Be>()?;
Some(Version2to4 {
s_x_height,
s_cap_height,
us_default_char,
us_break_char,
us_max_context,
})
} else {
None
};
let version5 = if version >= 5 {
let us_lower_optical_point_size = ctxt.read::<U16Be>()?;
let us_upper_optical_point_size = ctxt.read::<U16Be>()?;
Some(Version5 {
us_lower_optical_point_size,
us_upper_optical_point_size,
})
} else {
None
};
Ok(Os2 {
version,
x_avg_char_width,
us_weight_class,
us_width_class,
fs_type,
y_subscript_x_size,
y_subscript_y_size,
y_subscript_x_offset,
y_subscript_y_offset,
y_superscript_x_size,
y_superscript_y_size,
y_superscript_x_offset,
y_superscript_y_offset,
y_strikeout_size,
y_strikeout_position,
s_family_class,
panose,
ul_unicode_range1,
ul_unicode_range2,
ul_unicode_range3,
ul_unicode_range4,
ach_vend_id,
fs_selection,
us_first_char_index,
us_last_char_index,
version0,
version1,
version2to4,
version5,
})
}
}
impl WriteBinary<&Self> for Os2 {
type Output = ();
fn write<C: WriteContext>(ctxt: &mut C, table: &Self) -> Result<Self::Output, WriteError> {
let version = if table.version5.is_some() {
5_u16
} else if table.version2to4.is_some() {
4
} else if table.version1.is_some() {
1
} else {
0
};
U16Be::write(ctxt, version)?;
I16Be::write(ctxt, table.x_avg_char_width)?;
U16Be::write(ctxt, table.us_weight_class)?;
U16Be::write(ctxt, table.us_width_class)?;
U16Be::write(ctxt, table.fs_type)?;
I16Be::write(ctxt, table.y_subscript_x_size)?;
I16Be::write(ctxt, table.y_subscript_y_size)?;
I16Be::write(ctxt, table.y_subscript_x_offset)?;
I16Be::write(ctxt, table.y_subscript_y_offset)?;
I16Be::write(ctxt, table.y_superscript_x_size)?;
I16Be::write(ctxt, table.y_superscript_y_size)?;
I16Be::write(ctxt, table.y_superscript_x_offset)?;
I16Be::write(ctxt, table.y_superscript_y_offset)?;
I16Be::write(ctxt, table.y_strikeout_size)?;
I16Be::write(ctxt, table.y_strikeout_position)?;
I16Be::write(ctxt, table.s_family_class)?;
ctxt.write_bytes(&table.panose)?;
U32Be::write(ctxt, table.ul_unicode_range1)?;
U32Be::write(ctxt, table.ul_unicode_range2)?;
U32Be::write(ctxt, table.ul_unicode_range3)?;
U32Be::write(ctxt, table.ul_unicode_range4)?;
U32Be::write(ctxt, table.ach_vend_id)?;
U16Be::write(ctxt, table.fs_selection.bits())?;
U16Be::write(ctxt, table.us_first_char_index)?;
U16Be::write(ctxt, table.us_last_char_index)?;
if let Some(v0) = &table.version0 {
Version0::write(ctxt, v0)?;
}
if let Some(v1) = &table.version1 {
Version1::write(ctxt, v1)?;
}
if let Some(v2) = &table.version2to4 {
Version2to4::write(ctxt, v2)?;
}
if let Some(v5) = &table.version5 {
Version5::write(ctxt, v5)?;
}
Ok(())
}
}
impl WriteBinary<&Self> for Version0 {
type Output = ();
fn write<C: WriteContext>(ctxt: &mut C, table: &Self) -> Result<Self::Output, WriteError> {
I16Be::write(ctxt, table.s_typo_ascender)?;
I16Be::write(ctxt, table.s_typo_descender)?;
I16Be::write(ctxt, table.s_typo_line_gap)?;
U16Be::write(ctxt, table.us_win_ascent)?;
U16Be::write(ctxt, table.us_win_descent)?;
Ok(())
}
}
impl WriteBinary<&Self> for Version1 {
type Output = ();
fn write<C: WriteContext>(ctxt: &mut C, table: &Self) -> Result<Self::Output, WriteError> {
U32Be::write(ctxt, table.ul_code_page_range1)?;
U32Be::write(ctxt, table.ul_code_page_range2)?;
Ok(())
}
}
impl WriteBinary<&Self> for Version2to4 {
type Output = ();
fn write<C: WriteContext>(ctxt: &mut C, table: &Self) -> Result<Self::Output, WriteError> {
I16Be::write(ctxt, table.s_x_height)?;
I16Be::write(ctxt, table.s_cap_height)?;
U16Be::write(ctxt, table.us_default_char)?;
U16Be::write(ctxt, table.us_break_char)?;
U16Be::write(ctxt, table.us_max_context)?;
Ok(())
}
}
impl WriteBinary<&Self> for Version5 {
type Output = ();
fn write<C: WriteContext>(ctxt: &mut C, table: &Self) -> Result<Self::Output, WriteError> {
U16Be::write(ctxt, table.us_lower_optical_point_size)?;
U16Be::write(ctxt, table.us_upper_optical_point_size)?;
Ok(())
}
}
const UNICODE_RANGES: &[(u32, u32, u32)] = &[
(0x0000, 0x007F, 0), (0x0080, 0x00FF, 1), (0x0100, 0x017F, 2), (0x0180, 0x024F, 3), (0x0250, 0x02AF, 4), (0x02B0, 0x02FF, 5), (0x0300, 0x036F, 6), (0x0370, 0x03FF, 7), (0x0400, 0x04FF, 9), (0x0530, 0x058F, 10), (0x0590, 0x05FF, 11), (0x0600, 0x06FF, 12), (0x0700, 0x074F, 13), (0x0750, 0x077F, 14), (0x0780, 0x07BF, 15), (0x07C0, 0x07FF, 16), (0x0800, 0x083F, 17), (0x0840, 0x085F, 18), (0x0860, 0x086F, 19), (0x08A0, 0x08FF, 20), (0x0900, 0x097F, 21), (0x0980, 0x09FF, 22), (0x0A00, 0x0A7F, 23), (0x0A80, 0x0AFF, 24), (0x0B00, 0x0B7F, 25), (0x0B80, 0x0BFF, 26), (0x0C00, 0x0C7F, 27), (0x0C80, 0x0CFF, 28), (0x0D00, 0x0D7F, 29), (0x0D80, 0x0DFF, 30), (0x0E00, 0x0E7F, 31), (0x0E80, 0x0EFF, 32), (0x0F00, 0x0FFF, 33), (0x1000, 0x109F, 34), (0x10A0, 0x10FF, 35), (0x1100, 0x11FF, 36), (0x1E00, 0x1EFF, 37), (0x1F00, 0x1FFF, 38), ];
pub(crate) fn unicode_range_mask(ch: u32) -> u128 {
for &(start, end, bit) in UNICODE_RANGES.iter() {
if (start..=end).contains(&ch) {
return 1 << bit;
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "prince")]
fn test_read() {
use crate::binary::read::ReadScope;
use crate::tables::{FontTableProvider, OpenTypeFont};
use crate::tag;
use crate::tests::read_fixture;
let buffer = read_fixture("../../../tests/data/fonts/HardGothicNormal.ttf");
let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
let provider = opentype_file.table_provider(0).unwrap();
let os_2_data = provider.read_table_data(tag::OS_2).unwrap();
let os_2 = ReadScope::new(&os_2_data)
.read_dep::<Os2>(os_2_data.len())
.expect("unable to parse OS/2 table");
assert_eq!(os_2.version, 1);
assert!(os_2.version0.is_some());
assert!(os_2.version1.is_some());
assert!(os_2.version2to4.is_none());
assert!(os_2.version5.is_none());
}
#[test]
#[cfg(feature = "prince")]
fn test_write() {
use crate::binary::read::ReadScope;
use crate::binary::write::WriteBuffer;
use crate::tables::{FontTableProvider, OpenTypeFont};
use crate::tag;
use crate::tests::read_fixture;
let buffer = read_fixture("../../../tests/data/fonts/HardGothicNormal.ttf");
let opentype_file = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
let provider = opentype_file.table_provider(0).unwrap();
let os_2_data = provider.read_table_data(tag::OS_2).unwrap();
let os_2 = ReadScope::new(&os_2_data)
.read_dep::<Os2>(os_2_data.len())
.expect("unable to parse OS/2 table");
let mut out = WriteBuffer::new();
Os2::write(&mut out, &os_2).unwrap();
let written = out.into_inner();
assert_eq!(written.as_slice(), &*os_2_data);
}
#[test]
fn map_weight_class() {
assert_eq!(Os2::value_to_width_class(Fixed::from(0.)), 1);
assert_eq!(Os2::value_to_width_class(Fixed::from(1.)), 1);
assert_eq!(Os2::value_to_width_class(Fixed::from(50.)), 1);
assert_eq!(Os2::value_to_width_class(Fixed::from(51.)), 1);
assert_eq!(Os2::value_to_width_class(Fixed::from(60.)), 2);
assert_eq!(Os2::value_to_width_class(Fixed::from(150.)), 8);
assert_eq!(Os2::value_to_width_class(Fixed::from(300.)), 9);
}
}