Files
vectorcraft/crates/eps/src/latin.rs
T
Dan Green 6ce1a622be Part of #868: legacy .ai type as text objects and hidden objects kept; legacy type read
Type is written as version 7 text objects (§15): point type, area type and type on a path, with
fonts by PostScript name re-encoded to Windows-1252, and each run's paint, size, scaling,
tracking, leading, alignment and indents; type 8-bit text can't hold (or all, with outlineText)
is outlines. The format hides only layers, so hidden objects go to a hidden layer above their
own (hiddenObjects "layer", the default), are left out ("omit") or show ("visible"). The .ai
reader reads legacy type (To … TO, TE, %AI3_BeginEncoding) and ignores Ar; the import fuzzer
gets legacy type tokens.
2026-10-16 01:36:24 +07:00

256 lines
6.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The 8-bit encoding of type in legacy `.ai` files: Windows-1252, its glyphs named as the
//! WinAnsiEncoding of the PDF specification names them (ISO 32000-1, Annex D.2). The writer
//! installs it with `TE` (version 7 specification §3.3.1) and encodes strings in it; the reader
//! decodes strings by the `TE` a file has (its glyph names, which may be another platform's).
/// The glyph names of codes 128–255 (`.notdef` where Windows-1252 has no character).
const HIGH: [&str; 128] = [
"Euro",
".notdef",
"quotesinglbase",
"florin",
"quotedblbase",
"ellipsis",
"dagger",
"daggerdbl",
"circumflex",
"perthousand",
"Scaron",
"guilsinglleft",
"OE",
".notdef",
"Zcaron",
".notdef",
".notdef",
"quoteleft",
"quoteright",
"quotedblleft",
"quotedblright",
"bullet",
"endash",
"emdash",
"tilde",
"trademark",
"scaron",
"guilsinglright",
"oe",
".notdef",
"zcaron",
"Ydieresis",
"space",
"exclamdown",
"cent",
"sterling",
"currency",
"yen",
"brokenbar",
"section",
"dieresis",
"copyright",
"ordfeminine",
"guillemotleft",
"logicalnot",
"hyphen",
"registered",
"macron",
"degree",
"plusminus",
"twosuperior",
"threesuperior",
"acute",
"mu",
"paragraph",
"periodcentered",
"cedilla",
"onesuperior",
"ordmasculine",
"guillemotright",
"onequarter",
"onehalf",
"threequarters",
"questiondown",
"Agrave",
"Aacute",
"Acircumflex",
"Atilde",
"Adieresis",
"Aring",
"AE",
"Ccedilla",
"Egrave",
"Eacute",
"Ecircumflex",
"Edieresis",
"Igrave",
"Iacute",
"Icircumflex",
"Idieresis",
"Eth",
"Ntilde",
"Ograve",
"Oacute",
"Ocircumflex",
"Otilde",
"Odieresis",
"multiply",
"Oslash",
"Ugrave",
"Uacute",
"Ucircumflex",
"Udieresis",
"Yacute",
"Thorn",
"germandbls",
"agrave",
"aacute",
"acircumflex",
"atilde",
"adieresis",
"aring",
"ae",
"ccedilla",
"egrave",
"eacute",
"ecircumflex",
"edieresis",
"igrave",
"iacute",
"icircumflex",
"idieresis",
"eth",
"ntilde",
"ograve",
"oacute",
"ocircumflex",
"otilde",
"odieresis",
"divide",
"oslash",
"ugrave",
"uacute",
"ucircumflex",
"udieresis",
"yacute",
"thorn",
"ydieresis",
];
/// Codes 128–159 of Windows-1252 (the others are Latin-1's); its five gaps are C1 controls.
const C1: [char; 32] = [
'€', '\u{81}', '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰', 'Š', '‹', 'Œ', '\u{8D}', 'Ž', '\u{8F}', '\u{90}', '‘', '’', '“', '”', '•', '–', '—', '˜',
'™', 'š', '›', 'œ', '\u{9D}', 'ž', 'Ÿ',
];
/// Glyph names other platforms' encodings use that Windows-1252 has no code for (Mac Roman's).
const OTHER_NAMES: &[(&str, char)] = &[
("notequal", '≠'),
("infinity", '∞'),
("lessequal", '≤'),
("greaterequal", '≥'),
("partialdiff", '∂'),
("summation", '∑'),
("product", '∏'),
("pi", 'π'),
("integral", '∫'),
("Omega", 'Ω'),
("radical", '√'),
("approxequal", '≈'),
("Delta", '∆'),
("lozenge", '◊'),
("fraction", '⁄'),
("fi", 'fi'),
("fl", 'fl'),
("dotlessi", 'ı'),
("breve", '˘'),
("dotaccent", '˙'),
("ring", '˚'),
("hungarumlaut", '˝'),
("ogonek", '˛'),
("caron", 'ˇ'),
("nbspace", '\u{A0}'),
("quotesingle", '\''),
("grave", '`'),
];
/// Character `b` of Windows-1252.
pub(crate) fn decode(b: u8) -> char {
match b {
0x80..=0x9F => C1.get(usize::from(b - 0x80)).copied().unwrap_or('?'),
_ => char::from(b),
}
}
/// The code of `c` in Windows-1252: the printable characters, tab and the line breaks; None for
/// the others (and the C1 controls).
pub(crate) fn encode(c: char) -> Option<u8> {
match c {
'\t' | '\n' | '\r' | ' '..='~' | '\u{A0}'..='\u{FF}' => u8::try_from(u32::from(c)).ok(),
_ => C1.iter().position(|&x| x == c && !x.is_control()).and_then(|i| u8::try_from(0x80 + i).ok()),
}
}
/// The `TE` that installs the encoding: its codes 39 and 96 as in ASCII (PostScript's standard
/// encoding has quotes there) and 128–255 by name.
pub(crate) fn te() -> String {
let mut s = String::from("[39/quotesingle 96/grave 128");
for (i, name) in HIGH.iter().enumerate() {
s.push('/');
s.push_str(name);
if i % 8 == 7 && i + 1 < HIGH.len() {
s.push('\n');
}
}
s.push_str("\nTE\n");
s
}
/// The character glyph `name` stands for at code `code` of an encoding: that code's Windows-1252
/// character when the name is the one this encoding gives it; else by the name.
pub(crate) fn glyph(code: u8, name: &str) -> Option<char> {
if code >= 0x80 && HIGH.get(usize::from(code - 0x80)) == Some(&name) && name != ".notdef" {
return Some(decode(code));
}
if let Some(i) = HIGH.iter().position(|n| *n == name && *n != ".notdef" && *n != "space") {
return u8::try_from(0x80 + i).ok().map(decode);
}
if let Some((_, c)) = OTHER_NAMES.iter().find(|(n, _)| *n == name) {
return Some(*c);
}
match name {
"space" => Some(' '),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_encodable_character_decodes_as_itself() {
for b in 0..=255u8 {
let c = decode(b);
if let Some(e) = encode(c) {
assert_eq!(e, b, "{c:?}");
}
}
assert_eq!(encode('é'), Some(0xE9));
assert_eq!(encode('€'), Some(0x80));
assert_eq!(encode('—'), Some(0x97));
assert_eq!(encode('Ω'), None);
assert_eq!(encode('\u{81}'), None);
}
#[test]
fn glyph_names_decode_by_code_and_by_name() {
assert_eq!(glyph(0xE9, "eacute"), Some('é'));
assert_eq!(glyph(0xA0, "space"), Some('\u{A0}'));
// Mac Roman puts é at 0x8E.
assert_eq!(glyph(0x8E, "eacute"), Some('é'));
assert_eq!(glyph(0xAD, "notequal"), Some('≠'));
assert_eq!(glyph(0x41, "nosuchglyph"), None);
assert!(te().starts_with("[39/quotesingle 96/grave 128/Euro/.notdef/"));
assert!(te().ends_with("/ydieresis\nTE\n"));
}
}