From 1bd1c5cfa2e4e50dbb80a52ec1c2888320d56a53 Mon Sep 17 00:00:00 2001 From: Valentin Klein Date: Sat, 10 Oct 2026 21:33:56 +0200 Subject: [PATCH] io: only the first CADCraft xdata string marks the PAPERVIEW viewport (#253) dxf_ext::is_paper_view (#225) matched any 1000 string in the CADCraft xdata, but later strings are layer names (VPFROZEN, VPCOLORS). A real viewport with a frozen or recoloured layer named PAPERVIEW was therefore dropped on read. Only the first string, where the writer puts the marker, counts now. Regression test in tests/paperview_marker.rs. Co-authored-by: Claude Opus 5.5 --- crates/io/src/dxf_ext.rs | 6 +++-- crates/io/tests/paperview_marker.rs | 40 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 crates/io/tests/paperview_marker.rs diff --git a/crates/io/src/dxf_ext.rs b/crates/io/src/dxf_ext.rs index 054dc325a..c6bc3dda2 100644 --- a/crates/io/src/dxf_ext.rs +++ b/crates/io/src/dxf_ext.rs @@ -310,9 +310,11 @@ pub(crate) fn read_dstyle(tags: &[Tag], styles: &HashMap, blocks /// CADCraft keeps that viewport implicit, so the reader drops it. pub(crate) const PAPER_VIEW: &str = "PAPERVIEW"; -/// True for a VIEWPORT record carrying the [`PAPER_VIEW`] marker. +/// True for a VIEWPORT record carrying the [`PAPER_VIEW`] marker as the first string of its CADCraft +/// xdata, where the writer puts it. Later strings are layer names (VPFROZEN, VPCOLORS), so a layer +/// called PAPERVIEW does not count. pub(crate) fn is_paper_view(tags: &[Tag]) -> bool { - xdata(tags, APP).iter().any(|t| t.code == 1000 && t.str().trim().eq_ignore_ascii_case(PAPER_VIEW)) + xdata(tags, APP).iter().find(|t| t.code == 1000).is_some_and(|t| t.str().trim().eq_ignore_ascii_case(PAPER_VIEW)) } /// Snap kinds in CADCraft's ASSOC xdata. diff --git a/crates/io/tests/paperview_marker.rs b/crates/io/tests/paperview_marker.rs new file mode 100644 index 000000000..eecc3dc1d --- /dev/null +++ b/crates/io/tests/paperview_marker.rs @@ -0,0 +1,40 @@ +//! The DXF reader drops the paper-space viewport the writer adds (marked `1000 PAPERVIEW` in +//! CADCraft xdata) but keeps a real viewport whose frozen or recoloured layer is named PAPERVIEW. + +use cadcraft_color::Color; +use cadcraft_doc::*; +use cadcraft_geom::{Vec2, Vec3}; + +fn viewport(id: u32, frozen_layers: Vec, layer_colors: Vec<(String, Color)>) -> EntityKind { + EntityKind::Viewport(Viewport { + center: Vec3::new(50.0, 40.0, 0.0), + width: 80.0, + height: 60.0, + view_center: Vec2::new(10.0, 10.0), + view_height: 50.0, + id, + locked: false, + frozen_layers, + layer_colors, + }) +} + +fn viewports(l: &Layout) -> Vec { + l.entities.iter().filter_map(|e| if let EntityKind::Viewport(v) = &e.kind { Some(v.clone()) } else { None }).collect() +} + +#[test] +fn viewport_with_a_layer_named_paperview_survives_dxf_roundtrip() { + let mut d = Drawing::new_metric(); + d.ensure_layer("PAPERVIEW"); + let sheet = Space::Paper(d.layouts[0].name.clone()); + d.add(&sheet, Common::default(), viewport(2, vec!["PAPERVIEW".into()], vec![])).unwrap(); + d.add(&sheet, Common::default(), viewport(3, vec![], vec![("PAPERVIEW".into(), Color::Index(1))])).unwrap(); + + let text = cadcraft_io::write_dxf(&d); + let back = cadcraft_io::read_dxf(text.as_bytes()).unwrap(); + let vps = viewports(&back.layouts[0]); + assert_eq!(vps.iter().map(|v| v.id).collect::>(), [2, 3], "only the added sheet viewport is dropped"); + assert_eq!(vps[0].frozen_layers, ["PAPERVIEW"]); + assert_eq!(vps[1].layer_colors, [("PAPERVIEW".to_string(), Color::Index(1))]); +}