mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-31 14:27:02 -06:00
In Shift-JIS, Big5 and GBK a double-byte character's trail byte can be 0x5C, the backslash (表 95 5C, ソ 83 5C, 許 B3 5C, 乗 81 5C). Written as its bytes right before U+ or M+, the file holds a backslash byte followed by U+, an escape for a reader that looks for escapes before it decodes the code page. CADCraft decodes first, so it read back right, but such a character there is now written as a \U+XXXX escape and the file reads the same either way. Elsewhere it stays as its bytes. ANSI_949 has no such trail byte. Suggested by austin62jake on #1693.
117 lines
5.4 KiB
Rust
117 lines
5.4 KiB
Rust
//! Pre-R2007 DXF text is written in the `$DWGCODEPAGE` code page: characters it has as their
|
|
//! bytes, the others as `\U+XXXX` escapes; every form reads back to the same text.
|
|
|
|
use cadcraft_dxf::{Tag, parse, write_ascii, write_ascii_in, write_binary_in};
|
|
|
|
fn file(code_page: &str, text: &str) -> Vec<Tag> {
|
|
vec![
|
|
Tag::s(0, "SECTION"),
|
|
Tag::s(2, "HEADER"),
|
|
Tag::s(9, "$ACADVER"),
|
|
Tag::s(1, "AC1015"),
|
|
Tag::s(9, "$DWGCODEPAGE"),
|
|
Tag::s(3, code_page),
|
|
Tag::s(0, "ENDSEC"),
|
|
Tag::s(0, "SECTION"),
|
|
Tag::s(2, "ENTITIES"),
|
|
Tag::s(0, "TEXT"),
|
|
Tag::s(1, text),
|
|
Tag::s(0, "ENDSEC"),
|
|
Tag::s(0, "EOF"),
|
|
]
|
|
}
|
|
|
|
fn contains(bytes: &[u8], part: &[u8]) -> bool {
|
|
bytes.windows(part.len()).any(|w| w == part)
|
|
}
|
|
|
|
#[test]
|
|
fn text_is_written_in_the_code_page() {
|
|
// (code page, text, the bytes written for it)
|
|
let cases: [(&str, &str, &[u8]); 6] = [
|
|
("ANSI_1252", "Ação € 图", b"A\xE7\xE3o \x80 \\U+56FE"),
|
|
("ANSI_1251", "Слой é", b"\xD1\xEB\xEE\xE9 \\U+00E9"),
|
|
("ANSI_932", "表図面 é", b"\x95\x5C\x90\x7D\x96\xCA \\U+00E9"),
|
|
("DOS850", "Größe €", b"Gr\x94\xE1e \\U+20AC"),
|
|
("ANSI_1252", "😀", b"\\U+D83D\\U+DE00"),
|
|
// A code page CADCraft doesn't know: everything non-ASCII is escaped.
|
|
("ANSI_1361", "Ação", b"A\\U+00E7\\U+00E3o"),
|
|
];
|
|
for (cp, text, raw) in cases {
|
|
let tags = file(cp, text);
|
|
for (bytes, what) in [(write_ascii_in(&tags, cp), "ASCII"), (write_binary_in(&tags, cp), "binary")] {
|
|
assert!(contains(&bytes, raw), "{cp} {what}: {:?}", String::from_utf8_lossy(&bytes));
|
|
assert_eq!(parse(&bytes).unwrap()[10].str(), text, "{cp} {what}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Without a known code page, and for `ASCII`, `UTF8` and `UNICODE` headers, the output is what
|
|
/// `write_ascii` writes (pure ASCII with escapes), line breaks included.
|
|
#[test]
|
|
fn unknown_ascii_and_utf8_code_pages_are_pure_ascii() {
|
|
for cp in ["EBCDIC", "ASCII", "ascii ", "UTF8", "UNICODE"] {
|
|
let tags = file(cp, "Ação\r\nfim");
|
|
let bytes = write_ascii_in(&tags, cp);
|
|
assert!(bytes.is_ascii(), "{cp}");
|
|
assert_eq!(bytes, write_ascii(&tags).into_bytes(), "{cp}");
|
|
assert!(contains(&bytes, b"A\\U+00E7\\U+00E3o fim"), "{cp}");
|
|
assert!(write_binary_in(&tags, cp).iter().all(u8::is_ascii), "{cp} binary");
|
|
}
|
|
}
|
|
|
|
/// Code page bytes that happen to form valid UTF-8 (`СІ` in ANSI_1251 is D1 B2, which is U+0472
|
|
/// in UTF-8) would be read back as UTF-8 from an ASCII file, so that file is written with
|
|
/// escapes. Binary DXF reads strings by the header's code page and keeps the bytes.
|
|
#[test]
|
|
fn code_page_bytes_that_are_valid_utf8_are_escaped_in_ascii() {
|
|
let tags = file("ANSI_1251", "СІ");
|
|
let ascii = write_ascii_in(&tags, "ANSI_1251");
|
|
assert!(ascii.is_ascii(), "{:?}", String::from_utf8_lossy(&ascii));
|
|
assert!(contains(&ascii, b"\\U+0421\\U+0406"));
|
|
assert_eq!(parse(&ascii).unwrap()[10].str(), "СІ");
|
|
let binary = write_binary_in(&tags, "ANSI_1251");
|
|
assert!(contains(&binary, b"\xD1\xB2"));
|
|
assert_eq!(parse(&binary).unwrap()[10].str(), "СІ");
|
|
// Bytes that are not valid UTF-8 stay as they are.
|
|
assert!(contains(&write_ascii_in(&file("ANSI_1251", "Слой"), "ANSI_1251"), b"\xD1\xEB\xEE\xE9"));
|
|
}
|
|
|
|
/// A literal `\U+` or `\M+` in the text (#2050) reads back as written alongside code page bytes,
|
|
/// in ASCII and binary, including an ASCII file that falls back to escapes because its bytes
|
|
/// would read as UTF-8.
|
|
#[test]
|
|
fn literal_escapes_survive_code_page_text() {
|
|
for (cp, text) in
|
|
[("ANSI_1252", "Ação \\U+00E9 \\M+183A4F"), ("ANSI_1251", "Слой \\U+0041"), ("ANSI_1251", "СІ \\U+0041"), ("ANSI_932", "表 \\M+1")]
|
|
{
|
|
let tags = file(cp, text);
|
|
for (bytes, what) in [(write_ascii_in(&tags, cp), "ASCII"), (write_binary_in(&tags, cp), "binary")] {
|
|
assert!(contains(&bytes, b"\\U+005C"), "{cp} {what}: {:?}", String::from_utf8_lossy(&bytes));
|
|
assert_eq!(parse(&bytes).unwrap()[10].str(), text, "{cp} {what}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Double-byte code pages whose trail byte can be 0x5C (`\\`): Shift-JIS `表` (95 5C) and `ソ`
|
|
/// (83 5C), Big5 `許` (B3 5C), GBK `乗` (81 5C). Right before `U+` or `M+`, such a character is
|
|
/// written as an escape, so even a reader that looks for escapes before it decodes the code page
|
|
/// reads it right. Elsewhere it stays as its bytes. Both forms read back as written.
|
|
#[test]
|
|
fn a_trail_byte_backslash_never_starts_an_escape() {
|
|
let cases: [(&str, &str, &[u8], &[u8]); 4] = [
|
|
("ANSI_932", "表U+0041 表示", b"\\U+8868U+0041 \x95\x5C\x8E\xA6", b"\x95\x5CU+"),
|
|
("ANSI_932", "ソM+1A2B", b"\\U+30BDM+1A2B", b"\x83\x5CM+"),
|
|
("ANSI_950", "許U+0041", b"\\U+8A31U+0041", b"\xB3\x5CU+"),
|
|
("ANSI_936", "乗U+0041", b"\\U+4E57U+0041", b"\x81\x5CU+"),
|
|
];
|
|
for (cp, text, written, never) in cases {
|
|
let tags = file(cp, text);
|
|
for (bytes, what) in [(write_ascii_in(&tags, cp), "ASCII"), (write_binary_in(&tags, cp), "binary")] {
|
|
assert!(contains(&bytes, written), "{cp} {what}: {:?}", String::from_utf8_lossy(&bytes));
|
|
assert!(!contains(&bytes, never), "{cp} {what}: a trail byte starts an escape");
|
|
assert_eq!(parse(&bytes).unwrap()[10].str(), text, "{cp} {what}");
|
|
}
|
|
}
|
|
}
|