mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-09 12:00:50 -06:00
Keep a file's own paper size and plot rotation when saving DXF/DWG
A media named wider than tall is stored two ways: AutoCAD writes a portrait size and a quarter turn (ISO_A4_(297.00_x_210.00_MM) as 210 x 297, 73 = 1), ZWCAD the media's own size and no turn (420 x 297, 73 = 0). Deriving the groups from the name alone turned the AutoCAD form into the ZWCAD one. The reader now keeps groups 44, 45 and 73 as read (PageSetup::file_paper, not serialized), and the writer puts them back while they still describe the page setup's size and orientation; page setups made or changed in CADCraft are derived from the media name as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
589e9113bc
commit
42503a8ecd
@@ -799,6 +799,13 @@ pub struct PageSetup {
|
||||
/// The layout's paper units are millimetres (plot paper units 1 in DXF) whatever the
|
||||
/// drawing's MEASUREMENT says; otherwise paper units follow MEASUREMENT (#1058).
|
||||
pub paper_mm: bool,
|
||||
/// Paper size (DXF groups 44, 45) and plot rotation (group 73) as the file stored them.
|
||||
/// Saving writes them back while they still describe `width_mm`, `height_mm` and
|
||||
/// `landscape`, so each program's convention for media defined wider than tall survives a
|
||||
/// round trip: AutoCAD stores a portrait size and a quarter turn, ZWCAD the media's own size
|
||||
/// and no turn (#1463). `None` for page setups made in CADCraft.
|
||||
#[serde(skip)]
|
||||
pub file_paper: Option<(f64, f64, i64)>,
|
||||
}
|
||||
|
||||
impl Default for PageSetup {
|
||||
@@ -820,6 +827,7 @@ impl Default for PageSetup {
|
||||
show_plot_styles: false,
|
||||
plot_transparency: false,
|
||||
paper_mm: false,
|
||||
file_paper: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +241,7 @@ pub(super) fn page_setup(t: &T) -> PageSetup {
|
||||
let rotated = matches!(t.i(73), Some(1 | 3));
|
||||
if w > 0.0 && h > 0.0 {
|
||||
p.landscape = rotated != (w > h);
|
||||
p.file_paper = Some((w, h, t.i(73).unwrap_or(0)));
|
||||
} else if t.i(73).is_some() {
|
||||
p.landscape = rotated;
|
||||
}
|
||||
|
||||
@@ -1124,11 +1124,20 @@ pub(super) fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec<Ta
|
||||
}
|
||||
|
||||
/// Paper size (groups 44, 45) and plot rotation (group 73) for a LAYOUT object. Page setups
|
||||
/// keep a portrait size and a landscape flag. A media whose name makes it wider than tall, such
|
||||
/// as `ISO_full_bleed_A3_(420.00_x_297.00_MM)`, gets its size in that order and the rotation
|
||||
/// relative to it, so readers that take the media's size from its name agree with groups 44 and
|
||||
/// 45 (#1463). Other media are written as before.
|
||||
/// keep a portrait size and a landscape flag. The values read from the file are written back
|
||||
/// as they were while they still describe that size and orientation, whatever convention the
|
||||
/// program that wrote them follows. Otherwise a media whose name makes it wider than tall,
|
||||
/// such as `ISO_full_bleed_A3_(420.00_x_297.00_MM)`, gets its size in that order and the
|
||||
/// rotation relative to it, so readers that take the media's size from its name agree with
|
||||
/// groups 44 and 45 (#1463). Other media are written as before.
|
||||
fn plot_paper(page: &PageSetup) -> (f64, f64, i64) {
|
||||
if let Some((w, h, rotation)) = page.file_paper {
|
||||
let same = |a: f64, b: f64| (a - b).abs() < 1e-6;
|
||||
let landscape = matches!(rotation, 1 | 3) != (w > h);
|
||||
if same(w.min(h), page.width_mm) && same(w.max(h), page.height_mm) && landscape == page.landscape {
|
||||
return (w, h, rotation);
|
||||
}
|
||||
}
|
||||
if media_is_landscape(&page.paper) {
|
||||
let (short, long) = (page.width_mm.min(page.height_mm), page.width_mm.max(page.height_mm));
|
||||
(long, short, i64::from(!page.landscape))
|
||||
|
||||
@@ -576,6 +576,58 @@ fn landscape_media_keeps_its_size_order_and_rotation() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A file's own groups 44, 45 and 73 are written back unchanged, in DXF and DWG, whether they
|
||||
/// follow ZWCAD's convention (the media's size, no turn) or AutoCAD's (a portrait size and a
|
||||
/// quarter turn) for a media named wider than tall. Once the page setup changes, they no
|
||||
/// longer describe it and the size and rotation are derived again.
|
||||
#[test]
|
||||
fn a_files_own_paper_groups_are_written_back() {
|
||||
let set = |dxf: &str, (w, h, r): (f64, f64, i64)| -> String {
|
||||
let mut lines: Vec<String> = dxf.lines().map(String::from).collect();
|
||||
let name = (0..lines.len() - 3)
|
||||
.find(|&i| {
|
||||
lines[i].trim() == "100" && lines[i + 1].trim() == "AcDbLayout" && lines[i + 2].trim() == "1" && lines[i + 3].trim() == "Layout1"
|
||||
})
|
||||
.unwrap();
|
||||
let start = (0..name).rev().find(|&i| lines[i].trim() == "100" && lines[i + 1].trim() == "AcDbPlotSettings").unwrap();
|
||||
for i in (start..name).step_by(2) {
|
||||
match lines[i].trim() {
|
||||
"44" => lines[i + 1] = w.to_string(),
|
||||
"45" => lines[i + 1] = h.to_string(),
|
||||
"73" => lines[i + 1] = r.to_string(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
lines.join("\n")
|
||||
};
|
||||
// (media, groups as the program wrote them, portrait size and landscape CADCraft reads)
|
||||
let cases = [
|
||||
("ISO full bleed A3 (420.00 x 297.00 MM)", (420.0, 297.0, 0), (297.0, 420.0, true)),
|
||||
("ISO A4 (297.00 x 210.00 MM)", (210.0, 297.0, 1), (210.0, 297.0, true)),
|
||||
("ISO A4 (297.00 x 210.00 MM)", (210.0, 297.0, 0), (210.0, 297.0, false)),
|
||||
];
|
||||
for (media, groups, (pw, ph, landscape)) in cases {
|
||||
let mut d = sample();
|
||||
d.layouts[0].page.paper = media.into();
|
||||
let file = set(&write_dxf(&d), groups);
|
||||
let r = read_dxf(file.as_bytes()).unwrap();
|
||||
let p = &r.layout("Layout1").unwrap().page;
|
||||
assert_eq!((p.width_mm, p.height_mm, p.landscape), (pw, ph, landscape), "{media} {groups:?} read");
|
||||
assert_eq!(plot_paper_groups(&write_dxf(&r), "Layout1"), groups, "{media} {groups:?} DXF");
|
||||
let dwg = write(&r, "t.dwg").unwrap();
|
||||
let back = String::from_utf8_lossy(&cadcraft_dwg::dwg_to_dxf(&dwg).unwrap()).into_owned();
|
||||
assert_eq!(plot_paper_groups(&back, "Layout1"), groups, "{media} {groups:?} DWG");
|
||||
let r = read(&dwg, "t.dwg").unwrap();
|
||||
assert_eq!(plot_paper_groups(&write_dxf(&r), "Layout1"), groups, "{media} {groups:?} DWG read and saved again");
|
||||
}
|
||||
// Turned in CADCraft after reading: the stored groups no longer apply.
|
||||
let mut d = sample();
|
||||
d.layouts[0].page.paper = "ISO full bleed A3 (420.00 x 297.00 MM)".into();
|
||||
let mut r = read_dxf(set(&write_dxf(&d), (420.0, 297.0, 0)).as_bytes()).unwrap();
|
||||
r.layouts.iter_mut().find(|l| l.name == "Layout1").unwrap().page.landscape = false;
|
||||
assert_eq!(plot_paper_groups(&write_dxf(&r), "Layout1"), (420.0, 297.0, 1), "portrait after the change");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portrait_media_is_written_as_before() {
|
||||
let mut d = sample();
|
||||
|
||||
@@ -100,7 +100,9 @@ fn layout_page_setup_and_paper_view_round_trip() {
|
||||
let r = round_trip(&d);
|
||||
for (a, b) in d.layouts.iter().zip(&r.layouts) {
|
||||
assert_eq!(a.name, b.name);
|
||||
assert_eq!(a.page, b.page, "{}", a.name);
|
||||
// The groups 44, 45 and 73 the file stored are provenance, not a setting.
|
||||
let read = cadcraft_doc::PageSetup { file_paper: None, ..b.page.clone() };
|
||||
assert_eq!(a.page, read, "{}", a.name);
|
||||
assert_eq!(a.view, b.view, "{}", a.name);
|
||||
}
|
||||
assert_eq!(r.layouts.len(), d.layouts.len());
|
||||
|
||||
Reference in New Issue
Block a user