use pdfrum_font::{Charset, charset_from_unicode};
use pdfrum_object::{Dict, Name, Object, names as obj_names};
use crate::names;
pub const SUBSTITUTABLE_CHARSETS: &[Charset] = &[Charset::Hebrew];
#[must_use]
pub fn font_charset(font: &pdfrum_font::Font) -> Charset {
font.subst().map_or(Charset::Ansi, |subst| subst.charset)
}
#[must_use]
pub fn da_font_writes(font: &pdfrum_font::Font, da_charset: Charset, code: u32) -> bool {
let wanted = charset_from_unicode(code);
if !(da_charset == Charset::Symbol || wanted == da_charset) {
return false;
}
char::from_u32(code).is_some_and(|ch| font.char_code_from_unicode(ch).is_some())
}
#[must_use]
#[cfg(test)]
pub(crate) fn charset_code(charset: Charset, code: u32) -> Option<u8> {
if code < 0x80 {
return u8::try_from(code).ok();
}
let table = charset_unicodes(charset)?;
let index = table.iter().position(|&u| u32::from(u) == code)?;
u8::try_from(0x80 + index).ok()
}
#[must_use]
pub(crate) fn substitute_encode(font: &pdfrum_font::Font, code: u32) -> Vec<u8> {
let mut out = Vec::new();
let byte = char::from_u32(code)
.and_then(|ch| font.char_code_from_unicode(ch))
.unwrap_or(pdfrum_font::CharCode(code & 0xff));
font.append_char(&mut out, byte);
out
}
#[must_use]
pub fn substitute_width(font: &pdfrum_font::Font, code: u32) -> i32 {
let charcode = char::from_u32(code)
.and_then(|ch| font.char_code_from_unicode(ch))
.unwrap_or(pdfrum_font::CharCode(code & 0xff));
#[allow(clippy::cast_possible_truncation)]
{
font.char_width(charcode) as i32
}
}
#[must_use]
pub(crate) fn substitute_font_dict(charset: Charset) -> Option<Dict> {
let table = charset_unicodes(charset)?;
let mut differences: Vec<Object> = Vec::with_capacity(table.len() + 1);
differences.push(Object::Int(0x80));
for &unicode in table {
let name =
pdfrum_font::adobe_name_from_unicode(unicode).unwrap_or_else(|| ".notdef".to_owned());
differences.push(Object::Name(Name::from(name.as_str())));
}
let encoding = Dict::from_pairs([
(
names::BASE_ENCODING.clone(),
Object::Name(obj_names::WIN_ANSI_ENCODING.clone()),
),
(
names::DIFFERENCES.clone(),
Object::Array(differences.into_iter().collect()),
),
]);
Some(Dict::from_pairs([
(obj_names::TYPE.clone(), Object::Name(names::FONT.clone())),
(
obj_names::SUBTYPE.clone(),
Object::Name(names::TRUE_TYPE.clone()),
),
(
names::BASE_FONT.clone(),
Object::Name(Name::from(SUBSTITUTE_BASE_FONT)),
),
(names::ENCODING.clone(), Object::Dict(encoding)),
(
names::FONT_DESCRIPTOR.clone(),
Object::Dict(Dict::from_pairs([(
names::FLAGS.clone(),
Object::Int(i64::from(NON_SYMBOLIC)),
)])),
),
]))
}
const NON_SYMBOLIC: i32 = 1 << 5;
const SUBSTITUTE_BASE_FONT: &str = "Times-Roman";
#[must_use]
pub(crate) fn substitute_alias(charset: Charset) -> Vec<u8> {
format!("_{:02X}", charset_byte(charset)).into_bytes()
}
fn charset_byte(charset: Charset) -> u8 {
match charset {
Charset::Ansi => 0,
Charset::Default => 1,
Charset::Symbol => 2,
Charset::ShiftJis => 128,
Charset::Hangul => 129,
Charset::Johab => 130,
Charset::ChineseSimplified => 134,
Charset::ChineseTraditional => 136,
Charset::Greek => 161,
Charset::Turkish => 162,
Charset::Vietnamese => 163,
Charset::Hebrew => 177,
Charset::Arabic => 178,
Charset::Baltic => 186,
Charset::Cyrillic => 204,
Charset::Thai => 222,
Charset::EasternEuropean => 238,
Charset::Oem => 255,
}
}
fn charset_unicodes(charset: Charset) -> Option<&'static [u16; 128]> {
match charset {
Charset::Thai => Some(&THAI_UNICODES),
Charset::EasternEuropean => Some(&EASTERN_EUROPEAN_UNICODES),
Charset::Cyrillic => Some(&CYRILLIC_UNICODES),
Charset::Greek => Some(&GREEK_UNICODES),
Charset::Turkish => Some(&TURKISH_UNICODES),
Charset::Hebrew => Some(&HEBREW_UNICODES),
Charset::Arabic => Some(&ARABIC_UNICODES),
Charset::Baltic => Some(&BALTIC_UNICODES),
Charset::Ansi
| Charset::Default
| Charset::Symbol
| Charset::ShiftJis
| Charset::Hangul
| Charset::Johab
| Charset::ChineseSimplified
| Charset::ChineseTraditional
| Charset::Vietnamese
| Charset::Oem => None,
}
}
static HEBREW_UNICODES: [u16; 128] = [
0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0000, 0x2039,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x02DC, 0x2122, 0x0000, 0x203A, 0x0000, 0x0000, 0x0000, 0x0000, 0x00A0, 0x00A1, 0x00A2, 0x00A3,
0x20AA, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00D7, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00F7, 0x00BB,
0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7,
0x05B8, 0x05B9, 0x0000, 0x05BB, 0x05BC, 0x05BD, 0x05BE, 0x05BF, 0x05C0, 0x05C1, 0x05C2, 0x05C3,
0x05F0, 0x05F1, 0x05F2, 0x05F3, 0x05F4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB,
0x05DC, 0x05DD, 0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7,
0x05E8, 0x05E9, 0x05EA, 0x0000, 0x0000, 0x200E, 0x200F, 0x0000,
];
static THAI_UNICODES: [u16; 128] = [
0x20AC, 0x0000, 0x0000, 0x0000, 0x0000, 0x2026, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00A0, 0x0E01, 0x0E02, 0x0E03,
0x0E04, 0x0E05, 0x0E06, 0x0E07, 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F,
0x0E10, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17, 0x0E18, 0x0E19, 0x0E1A, 0x0E1B,
0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F, 0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27,
0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F, 0x0E30, 0x0E31, 0x0E32, 0x0E33,
0x0E34, 0x0E35, 0x0E36, 0x0E37, 0x0E38, 0x0E39, 0x0E3A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0E3F,
0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47, 0x0E48, 0x0E49, 0x0E4A, 0x0E4B,
0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F, 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57,
0x0E58, 0x0E59, 0x0E5A, 0x0E5B, 0x0000, 0x0000, 0x0000, 0x0000,
];
static EASTERN_EUROPEAN_UNICODES: [u16; 128] = [
0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0160, 0x2039,
0x015A, 0x0164, 0x017D, 0x0179, 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x0000, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A, 0x00A0, 0x02C7, 0x02D8, 0x0141,
0x00A4, 0x0104, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x015E, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x017B,
0x00B0, 0x00B1, 0x02DB, 0x0142, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x0105, 0x015F, 0x00BB,
0x013D, 0x02DD, 0x013E, 0x017C, 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7,
0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, 0x0110, 0x0143, 0x0147, 0x00D3,
0x00D4, 0x0150, 0x00D6, 0x00D7, 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF,
0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, 0x010D, 0x00E9, 0x0119, 0x00EB,
0x011B, 0x00ED, 0x00EE, 0x010F, 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7,
0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9,
];
static CYRILLIC_UNICODES: [u16; 128] = [
0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039,
0x040A, 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x0000, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408,
0x00A4, 0x0490, 0x00A6, 0x00A7, 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407,
0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB,
0x0458, 0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, 0x0420, 0x0421, 0x0422, 0x0423,
0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B,
0x043C, 0x043D, 0x043E, 0x043F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
];
static GREEK_UNICODES: [u16; 128] = [
0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0000, 0x2039,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x0000, 0x2122, 0x0000, 0x203A, 0x0000, 0x0000, 0x0000, 0x0000, 0x00A0, 0x0385, 0x0386, 0x00A3,
0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x0000, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x2015,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x0384, 0x00B5, 0x00B6, 0x00B7, 0x0388, 0x0389, 0x038A, 0x00BB,
0x038C, 0x00BD, 0x038E, 0x038F, 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, 0x03A1, 0x0000, 0x03A3,
0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF,
0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB,
0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x0000,
];
static TURKISH_UNICODES: [u16; 128] = [
0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039,
0x0152, 0x0000, 0x0000, 0x0000, 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0000, 0x0000, 0x0178, 0x00A0, 0x00A1, 0x00A2, 0x00A3,
0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB,
0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x011E, 0x00D1, 0x00D2, 0x00D3,
0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0130, 0x015E, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB,
0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF,
];
static ARABIC_UNICODES: [u16; 128] = [
0x20AC, 0x067E, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0679, 0x2039,
0x0152, 0x0686, 0x0698, 0x0688, 0x06AF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x06A9, 0x2122, 0x0691, 0x203A, 0x0153, 0x200C, 0x200D, 0x06BA, 0x00A0, 0x060C, 0x00A2, 0x00A3,
0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x06BE, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x061B, 0x00BB,
0x00BC, 0x00BD, 0x00BE, 0x061F, 0x06C1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627,
0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631, 0x0632, 0x0633,
0x0634, 0x0635, 0x0636, 0x00D7, 0x0637, 0x0638, 0x0639, 0x063A, 0x0640, 0x0641, 0x0642, 0x0643,
0x00E0, 0x0644, 0x00E2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB,
0x0649, 0x064A, 0x00EE, 0x00EF, 0x064B, 0x064C, 0x064D, 0x064E, 0x00F4, 0x064F, 0x0650, 0x00F7,
0x0651, 0x00F9, 0x0652, 0x00FB, 0x00FC, 0x200E, 0x200F, 0x06D2,
];
static BALTIC_UNICODES: [u16; 128] = [
0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0000, 0x2039,
0x0000, 0x00A8, 0x02C7, 0x00B8, 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x0000, 0x2122, 0x0000, 0x203A, 0x0000, 0x00AF, 0x02DB, 0x0000, 0x00A0, 0x0000, 0x00A2, 0x00A3,
0x00A4, 0x0000, 0x00A6, 0x00A7, 0x00D8, 0x00A9, 0x0156, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00C6,
0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00F8, 0x00B9, 0x0157, 0x00BB,
0x00BC, 0x00BD, 0x00BE, 0x00E6, 0x0104, 0x012E, 0x0100, 0x0106, 0x00C4, 0x00C5, 0x0118, 0x0112,
0x010C, 0x00C9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012A, 0x013B, 0x0160, 0x0143, 0x0145, 0x00D3,
0x014C, 0x00D5, 0x00D6, 0x00D7, 0x0172, 0x0141, 0x015A, 0x016A, 0x00DC, 0x017B, 0x017D, 0x00DF,
0x0105, 0x012F, 0x0101, 0x0107, 0x00E4, 0x00E5, 0x0119, 0x0113, 0x010D, 0x00E9, 0x017A, 0x0117,
0x0123, 0x0137, 0x012B, 0x013C, 0x0161, 0x0144, 0x0146, 0x00F3, 0x014D, 0x00F5, 0x00F6, 0x00F7,
0x0173, 0x0142, 0x015B, 0x016B, 0x00FC, 0x017C, 0x017E, 0x02D9,
];
#[cfg(test)]
mod tests {
use super::{
Charset, charset_code, charset_from_unicode, substitute_alias, substitute_font_dict,
};
#[test]
fn a_hebrew_letter_is_written_as_its_code_page_1255_byte() {
assert_eq!(charset_code(Charset::Hebrew, 0x05D0), Some(0xE0));
assert_eq!(charset_code(Charset::Hebrew, 0x05D1), Some(0xE1));
assert_eq!(charset_code(Charset::Hebrew, 0x05EA), Some(0xFA));
assert_eq!(charset_code(Charset::Hebrew, 0x05D7), Some(0xE7));
assert_eq!(charset_code(Charset::Hebrew, 0x05E8), Some(0xF8));
}
#[test]
fn ascii_is_its_own_byte_under_every_charset() {
for code in [0x20_u32, 0x2E, 0x41, 0x7E] {
assert_eq!(
charset_code(Charset::Hebrew, code),
u8::try_from(code).ok(),
"{code:#x}"
);
}
}
#[test]
fn a_code_point_the_charset_does_not_carry_has_no_byte() {
assert_eq!(charset_code(Charset::Hebrew, 0x0627), None);
assert_eq!(charset_code(Charset::Arabic, 0x0627), Some(0xC7));
assert_eq!(charset_code(Charset::ShiftJis, 0x3042), None);
assert_eq!(charset_code(Charset::Cyrillic, 0x0410), Some(0xC0));
}
#[test]
fn hebrew_code_points_take_the_hebrew_charset_and_ascii_does_not() {
assert_eq!(charset_from_unicode(0x05D1), Charset::Hebrew);
assert_eq!(charset_from_unicode(0x2E), Charset::Ansi);
assert_eq!(charset_from_unicode(0x20), Charset::Ansi);
}
#[test]
fn the_substitute_is_filed_under_the_charset_alone() {
assert_eq!(substitute_alias(Charset::Hebrew), b"_B1".to_vec());
assert_eq!(substitute_alias(Charset::Ansi), b"_00".to_vec());
}
#[test]
fn the_substitute_dict_is_a_truetype_with_a_differences_encoding() {
let dict = substitute_font_dict(Charset::Hebrew).expect("a Hebrew substitute");
assert_eq!(
dict.name(pdfrum_object::names::SUBTYPE)
.map(|name| name.as_bytes().to_vec()),
Some(b"TrueType".to_vec())
);
let encoding = dict
.dict(crate::names::ENCODING, &NoResolve)
.expect("an encoding dictionary");
assert_eq!(
encoding
.name(crate::names::BASE_ENCODING)
.map(|name| name.as_bytes().to_vec()),
Some(b"WinAnsiEncoding".to_vec())
);
let differences = encoding
.array(crate::names::DIFFERENCES, &NoResolve)
.expect("a differences array");
assert_eq!(differences.len(), 129);
assert_eq!(differences.int_at(0), Some(128));
let name_at = |code: usize| {
differences
.name_at(code - 0x80 + 1)
.map(|name| name.as_bytes().to_vec())
};
assert_eq!(name_at(0xE0), Some(b"afii57664".to_vec()));
assert_eq!(name_at(0x81), Some(b".notdef".to_vec()));
}
#[test]
fn a_charset_with_no_table_has_no_substitute_dictionary() {
assert!(substitute_font_dict(Charset::ShiftJis).is_none());
assert!(substitute_font_dict(Charset::Ansi).is_none());
assert!(substitute_font_dict(Charset::Cyrillic).is_some());
}
#[test]
fn the_substitute_declares_itself_non_symbolic() {
let dict = substitute_font_dict(Charset::Hebrew).expect("a Hebrew substitute");
let flags = dict
.dict(crate::names::FONT_DESCRIPTOR, &NoResolve)
.and_then(|desc| desc.int(crate::names::FLAGS, &NoResolve))
.expect("a descriptor with flags");
assert_eq!(
flags & i64::from(super::NON_SYMBOLIC),
i64::from(super::NON_SYMBOLIC),
"flags {flags:#x}"
);
}
#[test]
fn the_loaded_substitute_round_trips_a_hebrew_letter_through_its_own_encoding() {
let cache = pdfrum_font::FontCache::new();
let options = pdfrum_font::SubstitutionOptions::default();
let (limits, mut diags) = (
pdfrum_common::Limits::default(),
pdfrum_common::Diagnostics::default(),
);
let dict = substitute_font_dict(Charset::Hebrew).expect("a Hebrew substitute");
let font = pdfrum_font::load_with_options(
&dict, &NoResolve, &cache, &options, &limits, &mut diags,
)
.expect("the substitute loads");
for (code, byte) in [(0x05D0_u32, 0xE0_u8), (0x05D1, 0xE1), (0x05EA, 0xFA)] {
assert_eq!(
super::substitute_encode(&font, code),
vec![byte],
"U+{code:04X}"
);
assert_eq!(
font.unicode_from_charcode(pdfrum_font::CharCode(u32::from(byte)))
.first()
.copied(),
char::from_u32(code),
"byte {byte:#04X}"
);
}
}
#[test]
fn a_latin_fonts_charset_admits_ascii_and_refuses_hebrew() {
let cache = pdfrum_font::FontCache::new();
let latin = pdfrum_font::Font::load_standard(pdfrum_font::StandardFont::Helvetica, &cache);
let charset = super::font_charset(&latin);
assert_eq!(charset, Charset::Ansi);
assert!(super::da_font_writes(&latin, charset, u32::from(b'A')));
assert!(super::da_font_writes(&latin, charset, u32::from(b'.')));
assert!(
!super::da_font_writes(&latin, charset, 0x05D1),
"a Latin font never writes Hebrew, whatever its cmap says"
);
}
use pdfrum_object::NoResolve;
}
#[cfg(test)]
mod charset_tables {
use super::{
ARABIC_UNICODES, BALTIC_UNICODES, CYRILLIC_UNICODES, Charset, EASTERN_EUROPEAN_UNICODES,
GREEK_UNICODES, HEBREW_UNICODES, THAI_UNICODES, TURKISH_UNICODES, charset_unicodes,
};
fn digest(row: &[u16; 128]) -> u32 {
let mut hash: u32 = 0x811c_9dc5;
for value in row {
for byte in [(value >> 8) as u8, (value & 0xff) as u8] {
hash ^= u32::from(byte);
hash = hash.wrapping_mul(0x0100_0193);
}
}
hash
}
fn filled(row: &[u16; 128]) -> usize {
row.iter().filter(|&&value| value != 0).count()
}
fn at(row: &[u16; 128], code: u8) -> u16 {
assert!(code >= 0x80, "{code:#04x} is not in a row's high half");
row.get(usize::from(code) - 0x80).copied().unwrap_or(0)
}
fn ascending_run(row: &[u16; 128], first_code: u8, last_code: u8, first_unicode: u16) {
assert!(last_code >= first_code);
for code in first_code..=last_code {
let want = first_unicode + u16::from(code - first_code);
assert_eq!(
at(row, code),
want,
"code {code:#04x} holds {:#06x}, want {want:#06x}",
at(row, code)
);
}
}
#[test]
fn the_thai_row_is_code_page_874() {
assert_eq!(digest(&THAI_UNICODES), 0xD944_E0EA);
assert_eq!(filled(&THAI_UNICODES), 97);
ascending_run(&THAI_UNICODES, 0xA1, 0xDA, 0x0E01);
ascending_run(&THAI_UNICODES, 0xDF, 0xFB, 0x0E3F);
assert_eq!(at(&THAI_UNICODES, 0x80), 0x20AC, "the euro is code 0x80");
assert_eq!(charset_unicodes(Charset::Thai), Some(&THAI_UNICODES));
}
#[test]
fn the_eastern_european_row_is_code_page_1250() {
assert_eq!(digest(&EASTERN_EUROPEAN_UNICODES), 0xE280_F11C);
assert_eq!(filled(&EASTERN_EUROPEAN_UNICODES), 123);
assert_eq!(at(&EASTERN_EUROPEAN_UNICODES, 0x80), 0x20AC);
assert_eq!(at(&EASTERN_EUROPEAN_UNICODES, 0x8A), 0x0160);
assert_eq!(at(&EASTERN_EUROPEAN_UNICODES, 0x9A), 0x0161);
assert_eq!(at(&EASTERN_EUROPEAN_UNICODES, 0xFF), 0x02D9);
assert_eq!(
charset_unicodes(Charset::EasternEuropean),
Some(&EASTERN_EUROPEAN_UNICODES)
);
}
#[test]
fn the_cyrillic_row_is_code_page_1251() {
assert_eq!(digest(&CYRILLIC_UNICODES), 0x0EEC_041F);
assert_eq!(filled(&CYRILLIC_UNICODES), 127);
ascending_run(&CYRILLIC_UNICODES, 0xC0, 0xFF, 0x0410);
assert_eq!(at(&CYRILLIC_UNICODES, 0x80), 0x0402);
assert_eq!(
charset_unicodes(Charset::Cyrillic),
Some(&CYRILLIC_UNICODES)
);
}
#[test]
fn the_greek_row_is_code_page_1253() {
assert_eq!(digest(&GREEK_UNICODES), 0x779F_0A12);
assert_eq!(filled(&GREEK_UNICODES), 111);
ascending_run(&GREEK_UNICODES, 0xBE, 0xD1, 0x038E);
ascending_run(&GREEK_UNICODES, 0xD3, 0xFE, 0x03A3);
assert_eq!(at(&GREEK_UNICODES, 0xD2), 0, "U+03A2 is unassigned");
assert_eq!(charset_unicodes(Charset::Greek), Some(&GREEK_UNICODES));
}
#[test]
fn the_turkish_row_is_code_page_1254() {
assert_eq!(digest(&TURKISH_UNICODES), 0x77EA_3BEA);
assert_eq!(filled(&TURKISH_UNICODES), 121);
ascending_run(&TURKISH_UNICODES, 0xA0, 0xCF, 0x00A0);
ascending_run(&TURKISH_UNICODES, 0xDF, 0xEF, 0x00DF);
assert_eq!(at(&TURKISH_UNICODES, 0xD0), 0x011E);
assert_eq!(at(&TURKISH_UNICODES, 0xDD), 0x0130);
assert_eq!(at(&TURKISH_UNICODES, 0xDE), 0x015E);
assert_eq!(at(&TURKISH_UNICODES, 0xF0), 0x011F);
assert_eq!(at(&TURKISH_UNICODES, 0xFD), 0x0131);
assert_eq!(at(&TURKISH_UNICODES, 0xFE), 0x015F);
assert_eq!(charset_unicodes(Charset::Turkish), Some(&TURKISH_UNICODES));
}
#[test]
fn the_hebrew_row_is_code_page_1255() {
assert_eq!(digest(&HEBREW_UNICODES), 0xC0B9_3BB7);
assert_eq!(filled(&HEBREW_UNICODES), 105);
ascending_run(&HEBREW_UNICODES, 0xE0, 0xFA, 0x05D0);
ascending_run(&HEBREW_UNICODES, 0xC0, 0xC9, 0x05B0);
ascending_run(&HEBREW_UNICODES, 0xCB, 0xD3, 0x05BB);
assert_eq!(at(&HEBREW_UNICODES, 0xCA), 0);
assert_eq!(at(&HEBREW_UNICODES, 0xA4), 0x20AA);
assert_eq!(charset_unicodes(Charset::Hebrew), Some(&HEBREW_UNICODES));
}
#[test]
fn the_arabic_row_is_code_page_1256() {
assert_eq!(digest(&ARABIC_UNICODES), 0x0EBA_9A66);
assert_eq!(filled(&ARABIC_UNICODES), 128, "1256 encodes every code");
ascending_run(&ARABIC_UNICODES, 0xC1, 0xD6, 0x0621);
assert_eq!(at(&ARABIC_UNICODES, 0x81), 0x067E);
assert_eq!(at(&ARABIC_UNICODES, 0xFF), 0x06D2);
assert_eq!(charset_unicodes(Charset::Arabic), Some(&ARABIC_UNICODES));
}
#[test]
fn the_baltic_row_is_code_page_1257() {
assert_eq!(digest(&BALTIC_UNICODES), 0xA6AF_0B36);
assert_eq!(filled(&BALTIC_UNICODES), 116);
assert_eq!(at(&BALTIC_UNICODES, 0x80), 0x20AC);
assert_eq!(at(&BALTIC_UNICODES, 0xFF), 0x02D9);
assert_eq!(at(&BALTIC_UNICODES, 0xA1), 0);
assert_eq!(at(&BALTIC_UNICODES, 0xA5), 0);
assert_eq!(at(&BALTIC_UNICODES, 0xFF), 0x02D9);
assert_eq!(charset_unicodes(Charset::Baltic), Some(&BALTIC_UNICODES));
}
#[test]
fn exactly_eight_charsets_have_a_table() {
let with = [
Charset::Thai,
Charset::EasternEuropean,
Charset::Cyrillic,
Charset::Greek,
Charset::Turkish,
Charset::Hebrew,
Charset::Arabic,
Charset::Baltic,
];
for charset in with {
assert!(charset_unicodes(charset).is_some(), "{charset:?}");
}
for charset in [
Charset::Ansi,
Charset::Default,
Charset::Symbol,
Charset::ShiftJis,
Charset::Hangul,
Charset::Johab,
Charset::ChineseSimplified,
Charset::ChineseTraditional,
Charset::Vietnamese,
Charset::Oem,
] {
assert!(charset_unicodes(charset).is_none(), "{charset:?}");
}
}
#[test]
fn the_eight_rows_are_eight_distinct_tables() {
let rows = [
&THAI_UNICODES,
&EASTERN_EUROPEAN_UNICODES,
&CYRILLIC_UNICODES,
&GREEK_UNICODES,
&TURKISH_UNICODES,
&HEBREW_UNICODES,
&ARABIC_UNICODES,
&BALTIC_UNICODES,
];
for (first, one) in rows.iter().enumerate() {
for (second, other) in rows.iter().enumerate().skip(first + 1) {
assert_ne!(digest(one), digest(other), "rows {first} and {second}");
}
}
}
}