Attach Underlay ribbon menu, DWF/DGN attach dialog, DGN true colours

- Insert > Reference: the PDF Attach button becomes an Attach Underlay menu
  (PDF / DWF / DGN); its face runs the last one chosen.
- DWFATTACH, DGNATTACH and ATTACH on a .dwf/.dwfx/.dgn open the Attach
  dialog for that kind: sheets or models with thumbnails (one chosen),
  placement, path type, file details with the sheet / model size, and for
  DGN the conversion units (sub units offer 1 / sub units per master as the
  scale).
- DGN V8 true colours come from the ExtendedColors record packed in the
  non-model attributes stream; the colour word's high part numbers them.
- A DGN model's extent includes each text's box (its length along the
  baseline and 1.5 x its height), as the reference sizes a model.
- DWFx text uses the family named in its embedded (deobfuscated) font.
- DWF contrast pivots at 0.73 once colours are turned over for a dark
  background.
- Hairline strokes are drawn solid one pixel wide, and underlay rasters are
  made at or below their size on screen so those lines stay unbroken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-28 17:08:06 +03:00
1 parent fecbcd8138
commit 5b6cbff5b2
33 files changed
+745 -59

No files matched your search

+62 -6
View File
@@ -5,7 +5,7 @@ use super::*;
use crate::io::xref_model::Pathtype;
use crate::modules::insert::pdf_import::{self, PdfFileImport};
use crate::ui::window::pdf_dialogs::{
click_page, page_thumbs, LayerTarget, PdfAttachState, PdfDialogMsg, PdfImportFileState,
click_page, item_thumbs, page_thumbs, LayerTarget, PdfAttachState, PdfDialogMsg, PdfImportFileState,
RotationChoice, UnderlayLayersState,
};
@@ -16,6 +16,23 @@ fn page_size_text(path: &str, page: &str) -> String {
.unwrap_or_default()
}
/// The page's (PDF, inches) or the sheet's / model's size.
fn item_size_text(kind: codec::entities::UnderlayType, path: &str, item: &str) -> String {
if kind == codec::entities::UnderlayType::Pdf {
return page_size_text(path, item);
}
crate::scene::model::underlay_vector::sheet(kind, path, item)
.map(|s| format!("{:.4} × {:.4}", s.rect[2] - s.rect[0], s.rect[3] - s.rect[1]))
.unwrap_or_default()
}
fn sub_per_master(kind: codec::entities::UnderlayType, path: &str, item: &str) -> f64 {
crate::scene::model::underlay_vector::sheet(kind, path, item)
.map(|s| s.sub_per_master)
.filter(|v| *v > 0.0)
.unwrap_or(1.0)
}
fn file_stem(path: &str) -> String {
std::path::Path::new(&path.replace('\\', "/"))
.file_stem()
@@ -45,10 +62,18 @@ impl OpenCADStudio {
/// PDFATTACH after the file is chosen.
pub(in crate::app) fn open_pdf_attach_dialog(&mut self, path: &str) {
self.open_underlay_attach_dialog(codec::entities::UnderlayType::Pdf, path);
}
/// PDFATTACH / DWFATTACH / DGNATTACH after the file is chosen.
pub(in crate::app) fn open_underlay_attach_dialog(&mut self, kind: codec::entities::UnderlayType, path: &str) {
let i = self.active_tab;
let mut existing: Vec<(String, String)> = Vec::new();
for object in self.tabs[i].scene.document.objects.values() {
if let codec::objects::ObjectType::UnderlayDefinition(def) = object {
if def.underlay_type != kind {
continue;
}
let name = file_stem(&def.file_path);
if !existing.iter().any(|(n, _)| *n == name) {
existing.push((name, def.file_path.clone()));
@@ -56,6 +81,7 @@ impl OpenCADStudio {
}
}
let mut state = PdfAttachState::new(existing);
state.kind = kind;
self.load_pdf_attach_file(&mut state, path);
self.pdf_attach = Some(state);
self.active_modal = Some(crate::app::ModalKind::PdfAttach);
@@ -64,11 +90,14 @@ impl OpenCADStudio {
fn load_pdf_attach_file(&self, state: &mut PdfAttachState, path: &str) {
state.path = path.to_string();
state.name = file_stem(path);
state.pages = page_thumbs(path);
state.pages = item_thumbs(state.kind, path);
state.selected = vec![0];
state.anchor = 0;
state.found_in = crate::entities::underlay::display_path(path);
state.page_size = page_size_text(path, "1");
let first = state.page_labels().into_iter().next().unwrap_or_else(|| "1".to_string());
state.page_size = item_size_text(state.kind, path, &first);
state.sub_per_master = sub_per_master(state.kind, path, &first);
state.sub_units = false;
state.saved_path =
crate::entities::underlay::display_path(&self.pdf_stored_path(path, state.path_type.0));
}
@@ -206,7 +235,14 @@ impl OpenCADStudio {
let i = self.active_tab;
match message {
PdfDialogMsg::Help(which) => {
let kind = self.pdf_attach.as_ref().map(|s| s.kind);
let text = match which {
"attach" if kind == Some(codec::entities::UnderlayType::Dwf) => {
crate::t!("Attaches a DWF or DWFx file as an underlay.")
}
"attach" if kind == Some(codec::entities::UnderlayType::Dgn) => {
crate::t!("Attaches a DGN file as an underlay.")
}
"attach" => crate::t!("Attaches a PDF file as an underlay."),
"layers" => crate::t!("Turns the layers of a PDF underlay on or off."),
_ => crate::t!("Imports the geometry, fills, raster images and text of a PDF file as drawing objects."),
@@ -220,11 +256,22 @@ impl OpenCADStudio {
// ── Attach ────────────────────────────────────────────────────
PdfDialogMsg::AttachBrowse => {
let kind = self.pdf_attach.as_ref().map(|s| s.kind).unwrap_or(codec::entities::UnderlayType::Pdf);
if let Some(state) = &self.pdf_attach {
state.remember();
}
if kind != codec::entities::UnderlayType::Pdf {
return Task::done(Message::UnderlayAttachPick(kind));
}
return Task::done(Message::PdfAttachPick);
}
PdfDialogMsg::AttachSubUnits(sub) => {
if let Some(s) = self.pdf_attach.as_mut() {
s.sub_units = sub;
// The scale offered follows the units, as on the command line.
s.scale = if sub { format!("{}", 1.0 / s.sub_per_master) } else { "1.0000".to_string() };
}
}
PdfDialogMsg::AttachName(name) => {
let Some(mut state) = self.pdf_attach.take() else {
return Task::none();
@@ -237,9 +284,16 @@ impl OpenCADStudio {
PdfDialogMsg::AttachPage(page) => {
let (ctrl, shift) = (self.ctrl_down, self.shift_down);
if let Some(state) = self.pdf_attach.as_mut() {
click_page(&mut state.selected, &mut state.anchor, page, ctrl, shift);
if state.kind == codec::entities::UnderlayType::Pdf {
click_page(&mut state.selected, &mut state.anchor, page, ctrl, shift);
} else {
// One sheet or model at a time.
state.selected = vec![page];
state.anchor = page;
}
let first = state.page_labels().into_iter().next().unwrap_or_default();
state.page_size = page_size_text(&state.path, &first);
state.page_size = item_size_text(state.kind, &state.path, &first);
state.sub_per_master = sub_per_master(state.kind, &state.path, &first);
}
}
PdfDialogMsg::AttachPathType(choice) => {
@@ -475,12 +529,14 @@ impl OpenCADStudio {
}
self.close_active_modal();
let insunits = self.tabs[i].scene.document.header.insertion_units;
let mut command = crate::modules::insert::pdf_attach::PdfAttachCommand::from_dialog(
let mut command = crate::modules::insert::pdf_attach::PdfAttachCommand::from_kind_dialog(
state.kind,
&state.path,
&stored,
&state.page_labels(),
scale,
rotation,
state.sub_units,
insunits,
);
match insertion {
+19 -2
View File
@@ -402,8 +402,25 @@ impl OpenCADStudio {
} else {
codec::entities::UnderlayType::Dwf
};
let i = self.active_tab;
self.start_underlay_attach(i, kind, &path.to_string_lossy());
let path = path.to_string_lossy().into_owned();
match std::fs::read(&path) {
Ok(bytes) => {
crate::scene::model::pdf_raster::register_source(&path, std::sync::Arc::new(bytes));
crate::scene::model::underlay_vector::forget(&path);
if crate::scene::model::underlay_vector::item_names(kind, &path).is_some_and(|n| !n.is_empty()) {
self.open_underlay_attach_dialog(kind, &path);
} else {
self.command_line.push_error(&format!(
"{} not found.",
crate::entities::underlay::display_path(&path)
));
}
}
Err(_) => self.command_line.push_error(&format!(
"{} not found.",
crate::entities::underlay::display_path(&path)
)),
}
Task::none()
}
"png" | "jpg" | "jpeg" | "bmp" | "tif" | "tiff" => {
+5 -1
View File
@@ -24,7 +24,11 @@ impl OpenCADStudio {
Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(),
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
Some(K::BlockDefinition) => crate::t!("Block Definition").into_owned(),
Some(K::PdfAttach) => crate::t!("Attach PDF Underlay").into_owned(),
Some(K::PdfAttach) => match self.pdf_attach.as_ref().map(|s| s.kind) {
Some(codec::entities::UnderlayType::Dwf) => crate::t!("Attach DWF Underlay").into_owned(),
Some(codec::entities::UnderlayType::Dgn) => crate::t!("Attach DGN Underlay").into_owned(),
_ => crate::t!("Attach PDF Underlay").into_owned(),
},
Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(),
Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(),
Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(),