use crate::tables::{
DINGBATS_GLYPH_LIST, GLYPH_LIST, MAC_EXPERT_ENCODING, MAC_ROMAN_ENCODING, STANDARD_14_WIDTHS,
STANDARD_ENCODING, STANDARD_FONT_ALIASES, SYMBOL_ENCODING, WIN_ANSI_ENCODING, Widths,
ZAPF_DINGBATS_ENCODING,
};
fn lookup<'a>(table: &'a [(&'a str, &'a str)], name: &str) -> Option<&'a str> {
table
.binary_search_by(|(k, _)| k.as_bytes().cmp(name.as_bytes()))
.ok()
.and_then(|i| table.get(i))
.map(|(_, v)| *v)
.filter(|v| *v != "\0")
}
pub fn glyph_to_unicode(name: &str, dingbats: bool) -> Option<String> {
if dingbats && let Some(s) = lookup(DINGBATS_GLYPH_LIST, name) {
return Some(s.to_owned());
}
if let Some(s) = lookup(GLYPH_LIST, name) {
return Some(s.to_owned());
}
if let Some((base, _)) = name.split_once('.') {
if !base.is_empty() {
return glyph_to_unicode(base, dingbats);
}
return None;
}
if name.contains('_') {
return name
.split('_')
.map(|part| glyph_to_unicode(part, dingbats))
.collect::<Option<String>>();
}
let hex_chars = |hex: &str| -> Option<String> {
if hex.is_empty()
|| !hex.len().is_multiple_of(4)
|| !hex.bytes().all(|b| b.is_ascii_hexdigit())
{
return None;
}
let units: Vec<u16> = (0..hex.len() / 4)
.map(|i| u16::from_str_radix(hex.get(i * 4..i * 4 + 4).unwrap_or("0"), 16).unwrap_or(0))
.collect();
char::decode_utf16(units)
.collect::<Result<String, _>>()
.ok()
};
if let Some(hex) = name.strip_prefix("uni") {
return hex_chars(hex);
}
if let Some(hex) = name.strip_prefix('u')
&& (4..=6).contains(&hex.len())
&& hex.bytes().all(|b| b.is_ascii_hexdigit())
{
return u32::from_str_radix(hex, 16)
.ok()
.and_then(char::from_u32)
.map(String::from);
}
None
}
pub fn encoding_by_name(name: &[u8]) -> Option<&'static [&'static str; 256]> {
match name {
b"StandardEncoding" => Some(&STANDARD_ENCODING),
b"WinAnsiEncoding" => Some(&WIN_ANSI_ENCODING),
b"MacRomanEncoding" => Some(&MAC_ROMAN_ENCODING),
b"MacExpertEncoding" => Some(&MAC_EXPERT_ENCODING),
b"SymbolSetEncoding" => Some(&SYMBOL_ENCODING),
b"ZapfDingbatsEncoding" => Some(&ZAPF_DINGBATS_ENCODING),
_ => None,
}
}
pub fn standard_font(name: &str) -> Option<&'static str> {
let find = |n: &str| -> Option<&'static str> {
STANDARD_14_WIDTHS
.iter()
.find(|(k, _)| *k == n)
.map(|(k, _)| *k)
.or_else(|| lookup(STANDARD_FONT_ALIASES, n))
};
find(name).or_else(|| find(&name.replace(',', "-")))
}
pub fn standard_width(font: &str, glyph: &str) -> Option<f64> {
let (_, widths) = STANDARD_14_WIDTHS.iter().find(|(k, _)| *k == font)?;
match widths {
Widths::Fixed(w) => Some(f64::from(*w)),
Widths::Table(table) => table
.binary_search_by(|(k, _)| k.as_bytes().cmp(glyph.as_bytes()))
.ok()
.and_then(|i| table.get(i))
.map(|(_, w)| f64::from(*w)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn glyph_names() {
assert_eq!(glyph_to_unicode("A", false).as_deref(), Some("A"));
assert_eq!(glyph_to_unicode("eacute", false).as_deref(), Some("é"));
assert_eq!(glyph_to_unicode("Euro", false).as_deref(), Some("€"));
assert_eq!(glyph_to_unicode("uni0041", false).as_deref(), Some("A"));
assert_eq!(
glyph_to_unicode("uni00410042", false).as_deref(),
Some("AB")
);
assert_eq!(glyph_to_unicode("u1F600", false).as_deref(), Some("😀"));
assert_eq!(glyph_to_unicode("a.sc", false).as_deref(), Some("a"));
assert_eq!(glyph_to_unicode("T_h", false).as_deref(), Some("Th"));
assert_eq!(
glyph_to_unicode("f_f_i", false).as_deref(),
Some("\u{fb03}")
);
assert_eq!(glyph_to_unicode("a1", true).as_deref(), Some("✁"));
assert_eq!(glyph_to_unicode("g123", false), None);
assert_eq!(glyph_to_unicode(".notdef", false), None);
assert_eq!(glyph_to_unicode("uniZZZZ", false), None);
}
#[test]
fn encodings_and_standard_fonts() {
assert_eq!(encoding_by_name(b"WinAnsiEncoding").unwrap()[0x80], "Euro");
assert_eq!(encoding_by_name(b"StandardEncoding").unwrap()[0x41], "A");
assert!(encoding_by_name(b"Bogus").is_none());
assert_eq!(standard_font("Helvetica"), Some("Helvetica"));
assert_eq!(standard_font("Arial"), Some("Helvetica"));
assert_eq!(standard_font("Arial,Bold"), Some("Helvetica-Bold"));
assert_eq!(standard_font("NotAFont"), None);
assert_eq!(standard_width("Helvetica", "A"), Some(667.0));
assert_eq!(standard_width("Courier", "anything"), Some(600.0));
assert_eq!(standard_width("Helvetica", "nosuch"), None);
}
}