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:
ffishman
2026-10-28 18:46:19 -03:00
co-authored by Claude Opus 5.5
parent 589e9113bc
commit 42503a8ecd
5 changed files with 77 additions and 5 deletions
+8
View File
@@ -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,
}
}
}
+1
View File
@@ -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;
}
+13 -4
View File
@@ -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))
+52
View File
@@ -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());