feat: embed images (OLE2FRAME), print pipeline fixes, plot prefs, community fonts

- IMAGEEMBED: pack raster images into OLE2FRAME entities so a .dwg is
  self-contained; 'Embed Image' entry in the XREF attach menu; control/MCP
  op embed_image for external tools; dxf_name for OLE2FRAME queries.
- Scene: populate_images_from_document now rebuilds OLE2FRAME and PDF
  underlay models too (they used to vanish after any XREF op).
- Printing: add PRINTER_ENUM_NETWORK pass when listing printers; GDI
  spooler fallback (hayro raster at 300 DPI + StretchDIBits) for hosts
  whose PDF handler registers no print verb; DEVMODE orientation with a
  90-degree raster rotation fallback for v4 drivers; fix the fit-scale
  precedence bug that printed blank pages.
- PDF export dialogs on Windows use the async file dialog so the native
  overwrite confirmation no longer deadlocks the UI thread.
- Plot dialog: persist the chosen printer (saved on selection) and the
  picked plot window across sessions; page setups with no device no
  longer reset the output device.
- Community fonts: detect unresolvable .shx references on open and offer
  a download from the repository's fonts/ folder (contribution guide in
  fonts/README.md); custom font source URL setting for intranet font
  servers; downloaded fonts resolve from the per-user fonts directory.
This commit is contained in:
huanld committed 2026-09-16 16:42:21 +07:00
1 parent 1450fdeedf
commit 28efbffd57
27 files changed
+1767 -66

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
// Missing-fonts prompt — shown after a drawing opens with `.shx` fonts this
// machine doesn't have. Offers to fetch them from the community repository
// (see `fonts/README.md` in the repo root) instead of rendering with a
// mismatched substitute.
use crate::app::Message;
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
use iced::{Element, Fill, Length, Shrink, Theme};
fn muted_style(theme: &Theme) -> text::Style {
text::Style {
color: Some(theme.palette().background.weak.text),
}
}
/// The prompt: the missing-font list, a download action, and a skip.
pub fn view_window<'a>(
fonts: &'a [String],
font_source: &'a str,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let content_width = if matches!(sizing.width, Length::Fill) {
Fill
} else {
Shrink
};
let content_height = if matches!(sizing.height, Length::Fill) {
Fill
} else {
Shrink
};
let listed: Vec<Element<'a, Message>> = fonts
.iter()
.map(|name| text(format!(" {name}")).size(12).into())
.collect();
let actions = row![
Space::new().width(content_width),
button(text(crate::t!("Skip")).size(12))
.on_press(Message::MissingFontsDismiss)
.style(button::secondary)
.padding([6, 14]),
button(text(crate::t!("Download")).size(12))
.on_press(Message::MissingFontsDownload)
.style(button::primary)
.padding([6, 14]),
]
.spacing(8);
container(
column![
text(crate::t!("Missing fonts")).size(20),
text(crate::t!(
"This drawing uses fonts that are not installed on this machine. Download them from the OpenCADStudio community font repository?"
))
.size(11)
.style(muted_style),
container(scrollable(column(listed).spacing(2)).height(content_height))
.padding([10, 12])
.width(content_width)
.height(content_height)
.style(container::bordered_box),
column![
text(crate::t!("Font source — leave empty for the community repository, or point at any folder (e.g. your company's font server):"))
.size(10)
.style(muted_style),
text_input(
crate::t!("https://server/fonts or a GitHub raw folder").as_ref(),
font_source,
)
.on_input(Message::MissingFontsSourceChanged)
.size(11)
.padding([4, 8])
.width(content_width),
]
.spacing(3),
actions,
]
.spacing(10)
.width(sizing.width)
.height(sizing.height),
)
.padding([12, 16])
.into()
}
+1
View File
@@ -14,6 +14,7 @@ pub mod shortcuts;
pub mod layers;
pub mod update_notice;
pub mod open_progress;
pub mod missing_fonts;
pub mod recovery;
pub mod options;
pub mod attribute_editor;
+6
View File
@@ -160,6 +160,10 @@ pub struct PlotDialogState {
pub upside_down: bool,
pub copies: String,
pub area: String,
/// The picked world-space plot window (x0, y0, x1, y1) backing
/// `area == "Window"`. Persisted so reopening the dialog — or the app —
/// keeps the same window instead of reporting an empty plot area.
pub window: Option<(f64, f64, f64, f64)>,
pub center: bool,
pub offset_x: String,
pub offset_y: String,
@@ -219,6 +223,7 @@ impl Default for PlotDialogState {
upside_down: false,
copies: "1".into(),
area: "Window".into(),
window: None,
center: true,
offset_x: "0.0".into(),
offset_y: "0.0".into(),
@@ -263,6 +268,7 @@ impl PlotDialogState {
self.upside_down = o.upside_down;
self.copies = o.copies.clone();
self.area = o.area.clone();
self.window = o.window;
self.center = o.center;
self.offset_x = o.offset_x.clone();
self.offset_y = o.offset_y.clone();
+5
View File
@@ -619,6 +619,11 @@ impl XrefManagerPanel {
Some(Message::ImagePick),
None,
),
menu_item(
crate::t!("Embed Image (in drawing)").into_owned(),
Some(Message::ImageEmbedPick),
None,
),
menu_item(
crate::t!("Attach PDF").into_owned(),
Some(Message::PdfAttachPick),