use std::convert::TryFrom;
use crate::parser::{Stream, FromData, SafeStream, LazyArray};
use crate::{Font, TableName, Result, Error};
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub enum PlatformId {
Unicode,
Macintosh,
Iso,
Windows,
Custom,
}
impl TryFrom<u16> for PlatformId {
type Error = &'static str;
#[inline]
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
match value {
0 => Ok(PlatformId::Unicode),
1 => Ok(PlatformId::Macintosh),
2 => Ok(PlatformId::Iso),
3 => Ok(PlatformId::Windows),
4 => Ok(PlatformId::Custom),
_ => Err("invalid id"),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub enum NameId {
CopyrightNotice,
Family,
Subfamily,
UniqueID,
FullName,
Version,
PostScriptName,
Trademark,
Manufacturer,
Designer,
Description,
VendorUrl,
DesignerUrl,
License,
LicenseUrl,
TypographicFamily,
TypographicSubfamily,
CompatibleFull,
SampleText,
PostScriptCID,
WWSFamily,
WWSSubfamily,
LightBackgroundPalette,
DarkBackgroundPalette,
VariationsPostScriptNamePrefix,
}
impl TryFrom<u16> for NameId {
type Error = &'static str;
#[inline]
fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
match value {
0 => Ok(NameId::CopyrightNotice),
1 => Ok(NameId::Family),
2 => Ok(NameId::Subfamily),
3 => Ok(NameId::UniqueID),
4 => Ok(NameId::FullName),
5 => Ok(NameId::Version),
6 => Ok(NameId::PostScriptName),
7 => Ok(NameId::Trademark),
8 => Ok(NameId::Manufacturer),
9 => Ok(NameId::Designer),
10 => Ok(NameId::Description),
11 => Ok(NameId::VendorUrl),
12 => Ok(NameId::DesignerUrl),
13 => Ok(NameId::License),
14 => Ok(NameId::LicenseUrl),
16 => Ok(NameId::TypographicFamily),
17 => Ok(NameId::TypographicSubfamily),
18 => Ok(NameId::CompatibleFull),
19 => Ok(NameId::SampleText),
20 => Ok(NameId::PostScriptCID),
21 => Ok(NameId::WWSFamily),
22 => Ok(NameId::WWSSubfamily),
23 => Ok(NameId::LightBackgroundPalette),
24 => Ok(NameId::DarkBackgroundPalette),
25 => Ok(NameId::VariationsPostScriptNamePrefix),
_ => Err("invalid id"),
}
}
}
#[derive(Clone, Copy)]
pub struct Name<'a> {
pub name: &'a [u8],
pub platform_id: PlatformId,
pub encoding_id: u16,
pub language_id: u16,
pub name_id: NameId,
}
impl<'a> Name<'a> {
#[inline(never)]
pub fn to_string(&self) -> Option<String> {
if self.is_supported_encoding() {
self.name_from_utf16_be()
} else {
None
}
}
#[inline]
fn is_supported_encoding(&self) -> bool {
const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1;
match self.platform_id {
PlatformId::Unicode => true,
PlatformId::Windows if self.encoding_id == WINDOWS_UNICODE_BMP_ENCODING_ID => true,
_ => false,
}
}
#[inline(never)]
fn name_from_utf16_be(&self) -> Option<String> {
let mut name: Vec<u16> = Vec::new();
for c in LazyArray::new(self.name) {
name.push(c);
}
String::from_utf16(&name).ok()
}
}
impl<'a> std::fmt::Debug for Name<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let name = self.to_string();
f.debug_struct("Name")
.field("name", &name.as_ref().map(std::ops::Deref::deref)
.unwrap_or("unsupported encoding"))
.field("platform_id", &self.platform_id)
.field("encoding_id", &self.encoding_id)
.field("language_id", &self.language_id)
.field("name_id", &self.name_id)
.finish()
}
}
#[derive(Clone, Copy)]
struct NameRecord {
platform_id: u16,
encoding_id: u16,
language_id: u16,
name_id: u16,
length: u16,
offset: u16,
}
impl FromData for NameRecord {
#[inline]
fn parse(s: &mut SafeStream) -> Self {
NameRecord {
platform_id: s.read(),
encoding_id: s.read(),
language_id: s.read(),
name_id: s.read(),
length: s.read(),
offset: s.read(),
}
}
}
#[derive(Clone, Copy)]
#[allow(missing_debug_implementations)]
pub struct Names<'a> {
names: LazyArray<'a, NameRecord>,
storage: &'a [u8],
index: u16,
}
impl<'a> Iterator for Names<'a> {
type Item = Name<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.index as usize == self.names.len() {
return None;
}
let index = self.index;
self.index += 1;
let name = self.names.get(index)?;
let platform_id = match PlatformId::try_from(name.platform_id) {
Ok(v) => v,
Err(_) => return self.next(),
};
let name_id = match NameId::try_from(name.name_id) {
Ok(v) => v,
Err(_) => return self.next(),
};
let start = name.offset as usize;
let end = start + name.length as usize;
let data = match self.storage.get(start..end) {
Some(data) => data,
None => return self.next(),
};
Some(Name {
name: data,
platform_id,
encoding_id: name.encoding_id,
language_id: name.language_id,
name_id,
})
}
}
impl<'a> Font<'a> {
pub fn names(&self) -> Names {
match self._names() {
Ok(v) => v,
Err(_) => Names { names: LazyArray::new(&[]), storage: &[], index: 0 },
}
}
#[inline(never)]
fn _names(&self) -> Result<Names> {
const LANG_TAG_RECORD_SIZE: u16 = 4;
let data = self.name.ok_or_else(|| Error::TableMissing(TableName::Naming))?;
let mut s = Stream::new(data);
let format: u16 = s.read()?;
let count: u16 = s.read()?;
s.skip::<u16>();
let names = s.read_array(count)?;
if format == 0 {
Ok(Names {
names,
storage: s.tail()?,
index: 0,
})
} else if format == 1 {
let lang_tag_count: u16 = s.read()?;
let lang_tag_len = lang_tag_count
.checked_mul(LANG_TAG_RECORD_SIZE)
.ok_or_else(|| Error::NotATrueType)?;
s.skip_len(lang_tag_len);
Ok(Names {
names,
storage: s.tail()?,
index: 0,
})
} else {
Err(Error::NotATrueType)
}
}
pub fn family_name(&self) -> Option<String> {
let name = self.names()
.find(|name| name.name_id == NameId::TypographicFamily && name.is_supported_encoding())
.and_then(|name| name.to_string());
match name {
Some(name) => return Some(name),
None => {}
}
self.names()
.find(|name| name.name_id == NameId::Family && name.is_supported_encoding())
.and_then(|name| name.to_string())
}
pub fn post_script_name(&self) -> Option<String> {
self.names()
.find(|name| name.name_id == NameId::PostScriptName && name.is_supported_encoding())
.and_then(|name| name.to_string())
}
}