Merge pull request #1309 from ghost/embed-print-fonts

This commit is contained in:
Hakan Seven committed 2026-09-17 06:57:42 +03:00
commit 03f4e7cb86
29 files changed
+1833 -68

No files matched your search

+47
View File
@@ -67,7 +67,54 @@ pub(super) const NAMES: &[&str] = &[
"undo",
"redo",
];
/// Parse the `at` placement point for `embed_image`: `[x,y]` or `[x,y,z]`.
fn embed_point(req: &Value) -> Result<acadrust::types::Vector3, Value> {
let values = req["at"]
.as_array()
.filter(|v| (2..=3).contains(&v.len()))
.ok_or_else(|| failure("invalid_point", "Expected at:[x,y] or [x,y,z]"))?;
let mut p = [0.0_f64; 3];
for (i, v) in values.iter().enumerate() {
p[i] = v
.as_f64()
.filter(|v| v.is_finite())
.ok_or_else(|| failure("invalid_point", "Expected finite coordinates"))?;
}
Ok(acadrust::types::Vector3::new(p[0], p[1], p[2]))
}
impl OpenCADStudio {
/// `embed_image` — pack the picture at `path` into an OLE2FRAME placed
/// with its lower-left corner at `at` (default width ≈ pixel_width/100,
/// like the interactive command's Enter answer) and commit it as one
/// undo step. The drawing stays self-contained: no external file to lose.
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn control_embed_image(&mut self, req: &Value) -> Result<Task<Message>, Value> {
let i = self.active_tab;
let path = string(req, "path")?;
let image = crate::io::ole_embed::EmbeddedImage::from_file(std::path::Path::new(&path))
.map_err(|e| failure("embed_failed", e))?;
let at = embed_point(req)?;
let default_width = (image.pixel_width as f64 / 100.0).max(1.0);
let width = req["width"].as_f64().filter(|w| *w > 0.0).unwrap_or(default_width);
self.push_undo_snapshot(i, "IMAGEEMBED");
let handle = crate::io::ole_embed::add_embedded_image(
&mut self.tabs[i].scene.document,
&image,
at,
width,
)
.map_err(|e| failure("embed_failed", e))?;
self.post_ref_op(i);
self.set_control_result(json!({
"handle": format!("{:X}", handle.value()),
"kind": "Ole2Frame",
"path": path,
"width": width,
}));
Ok(Task::none())
}
pub(super) fn control_properties(&mut self) -> Value {
self.refresh_properties();
json!({"ok":true,"sections":self.tabs[self.active_tab].properties.sections.iter().map(|s|json!({"title":s.title,"properties":s.props.iter().map(|p|{
+37
View File
@@ -781,6 +781,8 @@ impl OpenCADStudio {
"set_properties" => self.control_set_record_properties(req)?,
"action" => self.control_ui_action(req)?,
#[cfg(not(target_arch = "wasm32"))]
"embed_image" => self.control_embed_image(req)?,
#[cfg(not(target_arch = "wasm32"))]
"save" => {
let path = req["path"]
.as_str()
@@ -1387,4 +1389,39 @@ mod tests {
);
let _ = std::fs::remove_file(path);
}
#[test]
fn embed_image_op_places_an_ole2frame_and_undoes_it() {
let mut app = OpenCADStudio::new_for_test();
request(&mut app, json!({"op":"new"}));
let img = image::RgbaImage::from_pixel(40, 30, image::Rgba([9, 9, 9, 255]));
let path = std::env::temp_dir().join(format!("ocs-embed-{}.png", session_id()));
img.save(&path).unwrap();
let response = request(
&mut app,
json!({"op":"embed_image","path":path.to_string_lossy(),"at":[0,0,0],"width":20}),
);
assert_eq!(response["status"], "completed", "{response}");
let handle = response["result"]["handle"].as_str().unwrap().to_string();
assert_eq!(response["result"]["kind"], "Ole2Frame");
let ole_count = |app: &OpenCADStudio| {
app.tabs[app.active_tab]
.scene
.document
.entities()
.filter(|e| matches!(e, acadrust::EntityType::Ole2Frame(_)))
.count()
};
assert_eq!(ole_count(&app), 1);
// The entity must be queryable like any other and undoable as one step.
let queried =
app.automation_op(json!({"op":"entities","type":"OLE2FRAME"}).to_string().as_str());
assert_eq!(queried["total"], 1, "{queried}");
request(&mut app, json!({"op":"undo"}));
assert_eq!(ole_count(&app), 0);
let _ = std::fs::remove_file(path);
let _ = handle;
}
}