pub mod format12;
pub mod format4;
use crate::ctx::Context;
use crate::data::CharacterMap;
use bytes::{BufMut, Bytes, BytesMut};
use itertools::Itertools;
use lazy_static::lazy_static;
use std::cmp;
use std::collections::HashMap;
use std::fmt;
use std::mem::size_of;
const VERSION_FIELD_SIZE: usize = size_of::<u16>();
const NUM_TABLES_FIELD_SIZE: usize = size_of::<u16>();
const PLATFORM_ID_FIELD_SIZE: usize = size_of::<u16>();
const ENCODING_ID_FIELD_SIZE: usize = size_of::<u16>();
const SUBTABLE_OFFSET_FIELD_SIZE: usize = size_of::<u32>();
const ENCODING_RECORD_SIZE: usize =
PLATFORM_ID_FIELD_SIZE + ENCODING_ID_FIELD_SIZE + SUBTABLE_OFFSET_FIELD_SIZE;
const CONSTANT_SIZE: usize = VERSION_FIELD_SIZE + NUM_TABLES_FIELD_SIZE;
const MAX_BMP_SCALER: char = '\u{FFFF}';
lazy_static! {
static ref DEFAULT_BMP_RECORDS: Vec<EncodingRecord> = vec![
EncodingRecord {
encoding: Encoding::Unicode(UnicodeEncoding::Bmp),
format: RecordFormat::Format4,
},
EncodingRecord {
encoding: Encoding::Windows(WindowsEncoding::Bmp),
format: RecordFormat::Format4,
},
];
static ref DEFAULT_FULL_RECORDS: Vec<EncodingRecord> = vec![
EncodingRecord {
encoding: Encoding::Unicode(UnicodeEncoding::Bmp),
format: RecordFormat::Format4,
},
EncodingRecord {
encoding: Encoding::Unicode(UnicodeEncoding::Full),
format: RecordFormat::Format12,
},
EncodingRecord {
encoding: Encoding::Windows(WindowsEncoding::Bmp),
format: RecordFormat::Format4,
},
EncodingRecord {
encoding: Encoding::Windows(WindowsEncoding::Full),
format: RecordFormat::Format12,
},
];
}
pub fn compile(map: &CharacterMap, ctx: &Context) -> Bytes {
let records = ctx.cmap_encoding_records.as_ref().unwrap_or_else(|| {
let exceeds_bmp = map.keys().last().map_or(false, |&x| x > MAX_BMP_SCALER);
if exceeds_bmp {
&DEFAULT_FULL_RECORDS
} else {
&DEFAULT_BMP_RECORDS
}
});
let mut subtables: HashMap<RecordFormat, Bytes> = records
.iter()
.map(|x| x.format)
.unique()
.map(|format| {
let subtable = match format {
RecordFormat::Format4 => format4::compile(map, ctx),
RecordFormat::Format12 => format12::compile(map),
};
(format, subtable)
})
.collect();
let fixed_size = CONSTANT_SIZE + (records.len() * ENCODING_RECORD_SIZE);
let length = fixed_size + subtables.values().map(Bytes::len).sum::<usize>();
let mut buf = BytesMut::with_capacity(length);
let version: u16 = 0;
buf.put_u16(version);
let num_tables: u16 = records.len() as u16;
buf.put_u16(num_tables);
let mut subtable_offsets: HashMap<RecordFormat, u32> = HashMap::new();
let mut subtable_offset: u32 = fixed_size as u32;
for record in records {
let platform_id = record.encoding.platform_id();
buf.put_u16(platform_id);
let encoding_id = record.encoding.encoding_id();
buf.put_u16(encoding_id);
if let Some(&offset) = subtable_offsets.get(&record.format) {
buf.put_u32(offset);
} else {
buf.put_u32(subtable_offset);
subtable_offsets.insert(record.format, subtable_offset);
subtable_offset += subtables[&record.format].len() as u32;
}
}
for format in records.iter().map(|x| x.format).unique() {
let subtable = subtables.get_mut(&format).unwrap();
buf.put(subtable);
}
assert_eq!(length, buf.len());
buf.freeze()
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct EncodingRecord {
pub encoding: Encoding,
pub format: RecordFormat,
}
impl cmp::PartialOrd for EncodingRecord {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.encoding.partial_cmp(&other.encoding)
}
}
impl cmp::Ord for EncodingRecord {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.encoding.cmp(&other.encoding)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ReadError {
InvalidSyntax,
InvalidPlatform,
InvalidEncoding,
InvalidFormat,
UnsupportedPlatform,
UnsupportedEncoding,
UnsupportedFormat,
}
impl fmt::Display for ReadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::InvalidSyntax => "The syntax is invalid.",
Self::InvalidPlatform => "The platform id can not be parsed.",
Self::InvalidEncoding => "The encoding id can not be parsed.",
Self::InvalidFormat => "The format id can not be parsed.",
Self::UnsupportedPlatform => "The platform id is not supported by Informa.",
Self::UnsupportedEncoding => "The encoding id is not supported by Informa.",
Self::UnsupportedFormat => "The format is not supported by Informa.",
};
write!(f, "{}", message)
}
}
impl std::error::Error for ReadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl EncodingRecord {
pub fn from_code(code: &str) -> Result<EncodingRecord, ReadError> {
let (selection, format) = code
.split("=")
.next_tuple::<(&str, &str)>()
.ok_or(ReadError::InvalidSyntax)?;
let (platform, encoding) = selection
.split("/")
.next_tuple::<(&str, &str)>()
.ok_or(ReadError::InvalidSyntax)?;
let platform_id = platform
.parse::<u16>()
.ok()
.ok_or(ReadError::InvalidPlatform)?;
let encoding_id = encoding
.parse::<u16>()
.ok()
.ok_or(ReadError::InvalidEncoding)?;
let format_id = format.parse::<u16>().ok().ok_or(ReadError::InvalidFormat)?;
let encoding = Encoding::from_ids(platform_id, encoding_id)?;
let format = RecordFormat::from_id(format_id).ok_or(ReadError::UnsupportedFormat)?;
Ok(EncodingRecord { encoding, format })
}
}
impl fmt::Display for EncodingRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}", self.encoding, self.format)
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Encoding {
Unicode(UnicodeEncoding),
Windows(WindowsEncoding),
}
impl Encoding {
pub fn from_ids(platform_id: u16, encoding_id: u16) -> Result<Self, ReadError> {
match platform_id {
0 => {
let encoding =
UnicodeEncoding::from_id(encoding_id).ok_or(ReadError::UnsupportedEncoding)?;
Ok(Encoding::Unicode(encoding))
}
3 => {
let encoding =
WindowsEncoding::from_id(encoding_id).ok_or(ReadError::UnsupportedEncoding)?;
Ok(Encoding::Windows(encoding))
}
_ => Err(ReadError::UnsupportedPlatform)?,
}
}
pub fn platform_id(&self) -> u16 {
match self {
Self::Unicode(_) => 0,
Self::Windows(_) => 3,
}
}
pub fn encoding_id(&self) -> u16 {
match self {
Self::Unicode(encoding) => encoding.id(),
Self::Windows(encoding) => encoding.id(),
}
}
}
impl fmt::Display for Encoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.platform_id(), self.encoding_id())
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
pub enum UnicodeEncoding {
Bmp,
Full,
}
impl UnicodeEncoding {
pub fn from_id(id: u16) -> Option<Self> {
match id {
3 => Some(Self::Bmp),
4 => Some(Self::Full),
_ => None,
}
}
pub fn id(&self) -> u16 {
match self {
Self::Bmp => 3,
Self::Full => 4,
}
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
pub enum WindowsEncoding {
Bmp,
Full,
}
impl WindowsEncoding {
pub fn from_id(id: u16) -> Option<Self> {
match id {
1 => Some(Self::Bmp),
10 => Some(Self::Full),
_ => None,
}
}
pub fn id(&self) -> u16 {
match self {
Self::Bmp => 1,
Self::Full => 10,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum RecordFormat {
Format4,
Format12,
}
impl RecordFormat {
pub fn from_id(id: u16) -> Option<Self> {
match id {
4 => Some(Self::Format4),
12 => Some(Self::Format12),
_ => None,
}
}
pub fn id(&self) -> u16 {
match self {
Self::Format4 => 4,
Self::Format12 => 12,
}
}
}
impl fmt::Display for RecordFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.id())
}
}