mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-15 06:52:23 +00:00
* craft-dxf: the ASCII reader unescapes only text that holds a backslash Only a string with a backslash can hold a \U+ or \M+ escape; every other string moves into the tag as it is instead of being copied. The EOF check reads the value before it moves. Ported from storytold/cadcraft#1833. * craft-dxf: write_ascii_in_page writes the code page without the UTF-8 fallback write_ascii_in falls back to pure ASCII when the code page bytes happen to be valid UTF-8, for readers that guess UTF-8. A reader that decodes a pre-2007 DXF strictly by its $DWGCODEPAGE needs the code page bytes. Ported from storytold/cadcraft#2851. * craft-dxf: behaviour-preserving cleanups Exact group-code mappings in one place, documented errors on the fallible public parsers, and simpler option, result and formatting idioms. No change to parsing, writing, numeric conversion, encoding or the public API. Ported from storytold/cadcraft#2571. * craft-dxf: read R12 and earlier binary DXF (1-byte group codes) Binary DXF written before R13 stores each group code in one byte, with 255 escaping a 2-byte code. The reader only knew the R13+ layout, where codes are two bytes, and also treated 255 as an escape there. The layout is now told from the first tag; 255 is an escape only in the 1-byte layout and an ordinary code in R13+. Ported from storytold/cadcraft#1499. * craft-dxf: section_slices, record_slices and Tag::str_ref borrow instead of copying sections() and records() copy every tag out of the parsed list, so a reader that keeps the list holds a large file's tags two or three times. The new functions return slices of the list. sections() and records() now build their copies from them, so there is one implementation of each split. Ported from storytold/cadcraft#1708. storytold/cadcraft#1588 solves the same problem by changing Section itself; this keeps the existing API. * craft-dxf: reals beyond 1e16 or below 1e-10 are written in exponent form; string_groups format_real wrote f64::MAX as 309 digits. Values outside 1e-10..1e16 are now written as AutoCAD writes them (1.0E+20) and read back exactly. string_groups splits text into the 250-character groups long strings are stored in (MAX_STRING_CHARS). Ported from storytold/cadcraft#1790. * craft-dxf: DOS 720/737/775/855/857/860/861/863/864/865/869 and ISO 8859-15 code pages; cheaper unescape and hex $DWGCODEPAGE values DOS720, 737, 775, 855, 857, 860, 861, 863, 864, 865, 869 and ISO8859_15 had no decoder and fell back to Windows-1252. ISO 8859-15 decodes through encoding_rs, the DOS pages through tables checked against the Unicode Consortium mapping files (dos_tables.rs). DOS 864's byte 0x25 is U+066A, so a writer escapes '%' there. U+FFFD is never written as a table byte. A string without an escape moves into its tag without a copy, cstr finds its terminator with one scan, and binary chunks are hex-encoded from a table. Ported from storytold/cadcraft#1271. Its group-code-255 change is carried by the R12 binary port: the 1-byte layout is detected only when the escaped code is one that needs the escape, so an R13+ file starting with code 255 still reads. * craft-dxf: Korean Johab (ANSI_1361) text and \M+4 escapes decode Johab had no decoder: files declaring ANSI_1361 were read as Windows-1252 and \M+4 escapes stayed literal. Hangul syllables are composed from the three 5-bit jamo indices (KS X 1001 annex 3); symbols and Hanja map onto KS X 1001 rows decoded as EUC-KR. Read only: a writer escapes instead. Ported from storytold/cadcraft#1911 (its DOS code pages are covered by the tables from storytold/cadcraft#1271). --------- Co-authored-by: mo <131036780+mbolling@users.noreply.github.com> Co-authored-by: ffishman <20211677+ffishman@users.noreply.github.com> Co-authored-by: Cédric Legrand <cedric@legrand-tech.fr> Co-authored-by: valentinevg812-eng <valentinevg812@gmail.com> Co-authored-by: Mads Holten Rasmussen <mads.h.rasmussen@gmail.com> Co-authored-by: LastSkywalker <maksim@sky-tehnol.uk> Co-authored-by: vangel76 <vangel1976@googlemail.com> Co-authored-by: Francesco BV <francesco.esposito@bvitalia.net>
118 lines
4.3 KiB
Rust
118 lines
4.3 KiB
Rust
//! The DXF/DWG file versions CADCraft writes (DXF Reference, HEADER `$ACADVER`).
|
||
|
||
/// A file version CADCraft can save: the `AutoCAD` release that introduced the format. Each format
|
||
/// also serves the releases after it (2018 is still `AutoCAD`'s current format).
|
||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||
pub enum Version {
|
||
/// `AutoCAD` 2000/2002 (AC1015).
|
||
R2000,
|
||
/// `AutoCAD` 2004–2006 (AC1018).
|
||
R2004,
|
||
/// `AutoCAD` 2007–2009 (AC1021): the first version whose DXF text is UTF-8.
|
||
R2007,
|
||
/// `AutoCAD` 2010–2012 (AC1024).
|
||
R2010,
|
||
/// `AutoCAD` 2013–2017 (AC1027).
|
||
R2013,
|
||
/// `AutoCAD` 2018 and later (AC1032), the default.
|
||
#[default]
|
||
R2018,
|
||
}
|
||
|
||
impl Version {
|
||
/// Every version, oldest first.
|
||
pub const ALL: [Version; 6] = [Version::R2000, Version::R2004, Version::R2007, Version::R2010, Version::R2013, Version::R2018];
|
||
|
||
/// The `$ACADVER` code ("AC1032").
|
||
pub fn code(self) -> &'static str {
|
||
match self {
|
||
Version::R2000 => "AC1015",
|
||
Version::R2004 => "AC1018",
|
||
Version::R2007 => "AC1021",
|
||
Version::R2010 => "AC1024",
|
||
Version::R2013 => "AC1027",
|
||
Version::R2018 => "AC1032",
|
||
}
|
||
}
|
||
|
||
/// The release year that names the version ("2018").
|
||
pub fn year(self) -> &'static str {
|
||
match self {
|
||
Version::R2000 => "2000",
|
||
Version::R2004 => "2004",
|
||
Version::R2007 => "2007",
|
||
Version::R2010 => "2010",
|
||
Version::R2013 => "2013",
|
||
Version::R2018 => "2018",
|
||
}
|
||
}
|
||
|
||
/// A version named by its year ("2018", "R2018") or its `$ACADVER` code ("AC1032"), any case.
|
||
pub fn parse(s: &str) -> Option<Version> {
|
||
let s = s.trim();
|
||
let year = s.strip_prefix(['R', 'r']).unwrap_or(s);
|
||
Version::ALL.into_iter().find(|v| v.year() == year || v.code().eq_ignore_ascii_case(s))
|
||
}
|
||
|
||
/// The version to save a file of `$ACADVER` `code` in: the same version when CADCraft writes
|
||
/// it, else the nearest one it does (R2000 for older files, 2018 for newer ones); `None` when
|
||
/// the code isn't a version code at all.
|
||
pub fn for_acadver(code: &str) -> Option<Version> {
|
||
let code = code.trim();
|
||
if let Some(v) = Version::ALL.into_iter().find(|v| v.code().eq_ignore_ascii_case(code)) {
|
||
return Some(v);
|
||
}
|
||
code.get(..2).filter(|p| p.eq_ignore_ascii_case("AC"))?;
|
||
let n = code.get(2..).filter(|d| d.bytes().all(|b| b.is_ascii_digit())).and_then(|d| d.parse::<u32>().ok())?;
|
||
Some(match n {
|
||
..1018 => Version::R2000,
|
||
1018..1021 => Version::R2004,
|
||
1021..1024 => Version::R2007,
|
||
1024..1027 => Version::R2010,
|
||
1027..1032 => Version::R2013,
|
||
_ => Version::R2018,
|
||
})
|
||
}
|
||
|
||
/// Whether text in this version's DXF is UTF-8 (2007 and later). Older versions keep text in
|
||
/// the `$DWGCODEPAGE` code page, which CADCraft avoids by escaping non-ASCII as `\U+XXXX`.
|
||
pub fn utf8(self) -> bool {
|
||
self >= Version::R2007
|
||
}
|
||
}
|
||
|
||
impl std::fmt::Display for Version {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
f.write_str(self.year())
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn parses_years_and_codes() {
|
||
assert_eq!(Version::parse("2018"), Some(Version::R2018));
|
||
assert_eq!(Version::parse(" r2004 "), Some(Version::R2004));
|
||
assert_eq!(Version::parse("ac1021"), Some(Version::R2007));
|
||
assert_eq!(Version::parse("2005"), None);
|
||
assert_eq!(Version::parse(""), None);
|
||
for v in Version::ALL {
|
||
assert_eq!(Version::parse(v.code()), Some(v));
|
||
assert_eq!(Version::parse(v.year()), Some(v));
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn file_versions_map_to_the_nearest_writable_one() {
|
||
assert_eq!(Version::for_acadver("AC1009"), Some(Version::R2000));
|
||
assert_eq!(Version::for_acadver("AC1014"), Some(Version::R2000));
|
||
assert_eq!(Version::for_acadver("AC1024"), Some(Version::R2010));
|
||
assert_eq!(Version::for_acadver("AC1035"), Some(Version::R2018));
|
||
assert_eq!(Version::for_acadver("MC0.0"), None);
|
||
assert_eq!(Version::for_acadver("AC"), None);
|
||
assert_eq!(Version::for_acadver("AC99999999999999999999"), None);
|
||
}
|
||
}
|