DXF: keep files that declare ASCII or UTF8 pure ASCII when saving

A pre-2007 file whose header says $DWGCODEPAGE ASCII (or UTF8/UNICODE, which
no pre-2007 reader expects) is still written with \U+ escapes only, instead of
Windows-1252 (or UTF-8) bytes the header doesn't declare. Review of #1693.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rodrigo Gressler
2026-10-19 22:25:35 -03:00
co-authored by Claude Opus 5.5
parent 28fe95fff0
commit 936f08db1f
4 changed files with 47 additions and 11 deletions
+13 -1
View File
@@ -69,12 +69,24 @@ pub fn decode_code_page(bytes: &[u8], code_page: &str) -> Option<String> {
decoder(code_page).map(|d| decode(bytes, d).into_owned())
}
/// The code page a pre-2007 file's text is written in, or `None` for pure ASCII with escapes:
/// when CADCraft doesn't know `code_page`, and for `ASCII` (the reader takes such files as
/// Windows-1252, but a writer must keep them ASCII) and `UTF8`/`UNICODE`, which no pre-2007
/// reader expects.
pub(crate) fn encoder(code_page: &str) -> Option<Decoder> {
if code_page.trim().eq_ignore_ascii_case("ASCII") {
return None;
}
decoder(code_page).filter(|d| !matches!(d, Decoder::Utf8))
}
/// Append the bytes of the non-ASCII character `c` in the code page `d`, and return true, when
/// the code page has it; otherwise append nothing and return false (the caller escapes it).
pub(crate) fn encode_char(c: char, d: Decoder, out: &mut Vec<u8>) -> bool {
let mut buf = [0; 4];
match d {
Decoder::Utf8 => out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes()),
// Not a pre-2007 code page ([`encoder`] never gives it): escaped.
Decoder::Utf8 => return false,
Decoder::Encoding(e) => {
let (bytes, _, unmappable) = e.encode(c.encode_utf8(&mut buf));
// A code page byte that is ASCII would read back as another character, and a NUL,
+4 -3
View File
@@ -418,9 +418,10 @@ pub fn write_ascii_as(tags: &[Tag], utf8: bool) -> String {
/// Write tags as pre-R2007 ASCII DXF with text in the code page `code_page` names (a
/// `$DWGCODEPAGE` value such as `ANSI_1252`), as the DXF Reference describes for those versions:
/// a character the code page has is written as its bytes, any other as a `\U+XXXX` escape. A
/// code page CADCraft doesn't know gives [`write_ascii`]'s pure ASCII output.
/// code page CADCraft doesn't know, `ASCII`, and `UTF8`/`UNICODE` (not a pre-2007 code page)
/// give [`write_ascii`]'s pure ASCII output.
pub fn write_ascii_in(tags: &[Tag], code_page: &str) -> Vec<u8> {
ascii(tags, Strings::Escaped(codepage::decoder(code_page)))
ascii(tags, Strings::Escaped(codepage::encoder(code_page)))
}
/// How a writer stores string values.
@@ -463,7 +464,7 @@ pub fn write_binary(tags: &[Tag], utf8: bool) -> Vec<u8> {
/// Write tags as pre-R2007 binary DXF with text in the code page `code_page` names, as
/// [`write_ascii_in`] does.
pub fn write_binary_in(tags: &[Tag], code_page: &str) -> Vec<u8> {
binary(tags, Strings::Escaped(codepage::decoder(code_page)))
binary(tags, Strings::Escaped(codepage::encoder(code_page)))
}
fn binary(tags: &[Tag], strings: Strings) -> Vec<u8> {
+11 -7
View File
@@ -46,12 +46,16 @@ fn text_is_written_in_the_code_page() {
}
}
/// Without a known code page the output is what `write_ascii` writes, line breaks included.
/// 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_code_page_is_pure_ascii() {
let tags = file("EBCDIC", "Ação\r\nfim");
let bytes = write_ascii_in(&tags, "EBCDIC");
assert!(bytes.is_ascii());
assert_eq!(bytes, write_ascii(&tags).into_bytes());
assert!(contains(&bytes, b"A\\U+00E7\\U+00E3o fim"));
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");
}
}
+19
View File
@@ -146,6 +146,25 @@ fn saving_writes_text_in_the_code_page() {
}
}
/// A file whose header declares `ASCII` keeps that code page and stays ASCII when saved: its
/// non-ASCII text is written as `\U+` escapes, not as Windows-1252 bytes.
#[test]
fn saving_an_ascii_file_keeps_it_ascii() {
use cadcraft_io::{SaveOptions, Version};
let d = cadcraft_io::read_dxf(&ascii_dxf("AC1015", "ASCII", b"El\\U+00E9trica", b"B", b"Eletrica \\U+00E9")).unwrap();
assert_eq!(names(&d), ("Elétrica".into(), "B".into(), "Eletrica é".into()));
for version in [Version::R2000, Version::R2004] {
let ascii = cadcraft_io::write_with(&d, "x.dxf", &SaveOptions { version: Some(version), binary: false }).unwrap();
assert!(ascii.is_ascii(), "{version}");
let binary = cadcraft_io::write_with(&d, "x.dxf", &SaveOptions { version: Some(version), binary: true }).unwrap();
for out in [&ascii, &binary] {
assert!(out.windows(14).any(|w| w == b"El\\U+00E9trica"), "{version}");
assert!(out.windows(5).any(|w| w == b"ASCII"), "{version}: the header keeps its code page");
assert_eq!(names(&cadcraft_io::read_dxf(out).unwrap()), ("Elétrica".into(), "B".into(), "Eletrica é".into()), "{version}");
}
}
}
/// A binary DXF: R2000 strings are in the code page, R2007+ strings UTF-8.
#[test]
fn binary_dxf_honours_the_code_page() {