use crate::decode::LeadingBytes;
use crate::ids::{CidCoding, CidSet, CodingScheme};
type ByteRange = (u8, u8);
struct Row {
stem: &'static [u8],
charset: CidSet,
coding: CidCoding,
scheme: CodingScheme,
leading: &'static [ByteRange],
}
const ROWS: &[Row] = &[
row(b"GB-EUC", CidSet::Gb1, CidCoding::Gb, &[(0xa1, 0xfe)]),
row(b"GBpc-EUC", CidSet::Gb1, CidCoding::Gb, &[(0xa1, 0xfc)]),
row(b"GBK-EUC", CidSet::Gb1, CidCoding::Gb, &[(0x81, 0xfe)]),
row(b"GBKp-EUC", CidSet::Gb1, CidCoding::Gb, &[(0x81, 0xfe)]),
row(b"GBK2K-EUC", CidSet::Gb1, CidCoding::Gb, &[(0x81, 0xfe)]),
row(b"GBK2K", CidSet::Gb1, CidCoding::Gb, &[(0x81, 0xfe)]),
two(b"UniGB-UCS2", CidSet::Gb1, CidCoding::Ucs2),
two(b"UniGB-UTF16", CidSet::Gb1, CidCoding::Utf16),
row(b"B5pc", CidSet::Cns1, CidCoding::Big5, &[(0xa1, 0xfc)]),
row(b"HKscs-B5", CidSet::Cns1, CidCoding::Big5, &[(0x88, 0xfe)]),
row(b"ETen-B5", CidSet::Cns1, CidCoding::Big5, &[(0xa1, 0xfe)]),
row(b"ETenms-B5", CidSet::Cns1, CidCoding::Big5, &[(0xa1, 0xfe)]),
two(b"UniCNS-UCS2", CidSet::Cns1, CidCoding::Ucs2),
two(b"UniCNS-UTF16", CidSet::Cns1, CidCoding::Utf16),
rksj(b"83pv-RKSJ"),
rksj(b"90ms-RKSJ"),
rksj(b"90msp-RKSJ"),
rksj(b"90pv-RKSJ"),
rksj(b"Add-RKSJ"),
row(
b"EUC",
CidSet::Japan1,
CidCoding::Jis,
&[(0x8e, 0x8e), (0xa1, 0xfe)],
),
Row {
stem: b"H",
charset: CidSet::Japan1,
coding: CidCoding::Jis,
scheme: CodingScheme::TwoBytes,
leading: &[(0x21, 0x7e)],
},
Row {
stem: b"V",
charset: CidSet::Japan1,
coding: CidCoding::Jis,
scheme: CodingScheme::TwoBytes,
leading: &[(0x21, 0x7e)],
},
rksj(b"Ext-RKSJ"),
two(b"UniJIS-UCS2", CidSet::Japan1, CidCoding::Ucs2),
two(b"UniJIS-UCS2-HW", CidSet::Japan1, CidCoding::Ucs2),
two(b"UniJIS-UTF16", CidSet::Japan1, CidCoding::Utf16),
row(
b"KSC-EUC",
CidSet::Korea1,
CidCoding::Korea,
&[(0xa1, 0xfe)],
),
row(
b"KSCms-UHC",
CidSet::Korea1,
CidCoding::Korea,
&[(0x81, 0xfe)],
),
row(
b"KSCms-UHC-HW",
CidSet::Korea1,
CidCoding::Korea,
&[(0x81, 0xfe)],
),
row(
b"KSCpc-EUC",
CidSet::Korea1,
CidCoding::Korea,
&[(0xa1, 0xfd)],
),
two(b"UniKS-UCS2", CidSet::Korea1, CidCoding::Ucs2),
two(b"UniKS-UTF16", CidSet::Korea1, CidCoding::Utf16),
];
const fn row(
stem: &'static [u8],
charset: CidSet,
coding: CidCoding,
leading: &'static [ByteRange],
) -> Row {
Row {
stem,
charset,
coding,
scheme: CodingScheme::MixedTwoBytes,
leading,
}
}
const fn two(stem: &'static [u8], charset: CidSet, coding: CidCoding) -> Row {
Row {
stem,
charset,
coding,
scheme: CodingScheme::TwoBytes,
leading: &[],
}
}
const fn rksj(stem: &'static [u8]) -> Row {
row(
stem,
CidSet::Japan1,
CidCoding::Jis,
&[(0x81, 0x9f), (0xe0, 0xfc)],
)
}
pub(crate) struct Resolved {
pub(crate) charset: CidSet,
pub(crate) coding: CidCoding,
pub(crate) scheme: CodingScheme,
pub(crate) leading: Option<Box<LeadingBytes>>,
}
fn decoder_row(name: &[u8]) -> Option<&'static Row> {
let stem = if name.len() > 2 {
name.get(..name.len() - 2)?
} else {
name
};
ROWS.iter().find(|r| r.stem == stem)
}
fn leading_set(row: &Row) -> Box<LeadingBytes> {
let mut set = Box::new(LeadingBytes::none());
for &(first, last) in row.leading {
set.add(first, last);
}
set
}
pub(crate) fn resolve(name: &[u8]) -> Option<Resolved> {
let row = decoder_row(name)?;
Some(Resolved {
charset: row.charset,
coding: row.coding,
scheme: row.scheme,
leading: (row.scheme == CodingScheme::MixedTwoBytes).then(|| leading_set(row)),
})
}
pub(crate) fn is_identity(name: &[u8]) -> bool {
name == b"Identity-H" || name == b"Identity-V"
}
pub(crate) fn is_vertical(name: &[u8]) -> bool {
name.last() == Some(&b'V')
}
pub(crate) fn strip_slash(name: &[u8]) -> &[u8] {
name.strip_prefix(b"/").unwrap_or(name)
}
#[cfg(test)]
mod tests {
use super::{ROWS, decoder_row, is_identity, is_vertical, resolve, strip_slash};
use crate::ids::{CidCoding, CidSet, CodingScheme};
#[test]
fn the_table_is_the_c_plus_plus_table() {
assert_eq!(ROWS.len(), 32);
let mut stems: Vec<&[u8]> = ROWS.iter().map(|r| r.stem).collect();
stems.sort_unstable();
let before = stems.len();
stems.dedup();
assert_eq!(before, stems.len(), "stems must be unique");
}
#[test]
fn name_resolution_strips_the_last_two_bytes() {
for (name, stem) in [
(&b"GB-EUC-H"[..], &b"GB-EUC"[..]),
(b"GB-EUC-V", b"GB-EUC"),
(b"UniJIS-UCS2-HW-H", b"UniJIS-UCS2-HW"),
(b"GBK2K-EUC-H", b"GBK2K-EUC"),
(b"GBK2K-H", b"GBK2K"),
(b"GB-EUCXY", b"GB-EUC"),
(b"GB-EUC\0\0", b"GB-EUC"),
(b"UniJIS-UCS2XX", b"UniJIS-UCS2"),
] {
let row = decoder_row(name).unwrap_or_else(|| panic!("{name:?} must resolve"));
assert_eq!(row.stem, stem, "{name:?}");
}
}
#[test]
fn a_truncation_that_lands_between_rows_matches_nothing() {
for name in [
&b"UniJIS-UCS2-HW"[..],
b"GB-EUC-XY",
b"GB-EUC-",
b"UniJIS-UCS2-",
] {
assert!(decoder_row(name).is_none(), "{name:?} must not resolve");
}
}
#[test]
fn short_names_are_not_truncated() {
assert_eq!(decoder_row(b"H").map(|r| r.stem), Some(&b"H"[..]));
assert_eq!(decoder_row(b"V").map(|r| r.stem), Some(&b"V"[..]));
assert!(decoder_row(b"").is_none());
assert!(decoder_row(b"X").is_none());
assert!(decoder_row(b"XY").is_none());
assert_eq!(decoder_row(b"Hxy").map(|r| r.stem), Some(&b"H"[..]));
}
#[test]
fn leading_bytes_load_only_for_mixed_two_byte_rows() {
let gb = resolve(b"GB-EUC-H").unwrap();
assert_eq!(gb.scheme, CodingScheme::MixedTwoBytes);
let set = gb.leading.unwrap();
assert!(!set.contains(0x40));
assert!(set.contains(0xa1));
assert!(set.contains(0xfe));
assert!(!set.contains(0xff));
assert!(!set.contains(0xa0));
let h = resolve(b"H").unwrap();
assert_eq!(h.scheme, CodingScheme::TwoBytes);
assert!(h.leading.is_none());
}
#[test]
fn two_disjoint_leading_ranges_both_load() {
let euc = resolve(b"EUC-H").unwrap();
let set = euc.leading.unwrap();
assert!(set.contains(0x8e));
assert!(!set.contains(0x8f));
assert!(set.contains(0xa1));
assert!(set.contains(0xfe));
let rksj = resolve(b"90ms-RKSJ-H").unwrap();
let set = rksj.leading.unwrap();
assert!(set.contains(0x81) && set.contains(0x9f) && !set.contains(0xa0));
assert!(set.contains(0xe0) && set.contains(0xfc) && !set.contains(0xfd));
}
#[test]
fn charsets_and_codings_come_from_the_row() {
let r = resolve(b"UniKS-UTF16-H").unwrap();
assert_eq!(r.charset, CidSet::Korea1);
assert_eq!(r.coding, CidCoding::Utf16);
assert_eq!(r.scheme, CodingScheme::TwoBytes);
}
#[test]
fn identity_and_verticality_read_the_unstripped_name() {
assert!(is_identity(b"Identity-H"));
assert!(is_identity(b"Identity-V"));
assert!(!is_identity(b"Identity"));
assert!(!is_identity(b"/Identity-H"));
assert!(is_vertical(b"Identity-V"));
assert!(is_vertical(b"GB-EUC-V"));
assert!(is_vertical(b"V"));
assert!(is_vertical(b"NonsenseV"));
assert!(!is_vertical(b"Identity-H"));
assert!(!is_vertical(b""));
}
#[test]
fn one_leading_slash_is_removed() {
assert_eq!(strip_slash(b"/Identity-H"), b"Identity-H");
assert_eq!(strip_slash(b"Identity-H"), b"Identity-H");
assert_eq!(strip_slash(b"//X"), b"/X");
assert_eq!(strip_slash(b""), b"");
}
}