1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! Image export (PNG/JPEG/WebP readback) and native `.darkly` save / open.
//!
//! Multi-blob outputs (`poll_save_result`) concatenate every byte buffer into
//! the single [`Response`] `bytes` side-channel; the JSON value carries the
//! lengths so the JS edge can slice them back out in order.
use serde::Deserialize;
use serde_json::json;
use crate::engine::protocol::{bad_payload, ProtocolError, RequestRegistration, Response};
use crate::engine::SavePurpose;
pub fn registrations() -> Vec<RequestRegistration> {
vec![
RequestRegistration {
kind: "start_export",
handle: |engine, _payload, _b| {
engine.start_export();
Ok(Response::empty())
},
},
RequestRegistration {
kind: "poll_export_result",
handle: |engine, _payload, _b| {
let Some(result) = engine.poll_export_result() else {
return Ok(Response::json(serde_json::Value::Null));
};
let value = json!({ "width": result.width, "height": result.height });
Ok(Response::binary(value, result.rgba))
},
},
RequestRegistration {
kind: "start_save_document",
handle: |engine, payload, _b| {
// A `snapshot` save (autosave to OPFS) must not clear the
// document's dirty flag; a file save does. Default to a file
// save when the flag is absent.
#[derive(Deserialize)]
struct Req {
#[serde(default)]
snapshot: bool,
}
let r: Req = serde_json::from_value(payload).map_err(bad_payload)?;
let purpose = if r.snapshot {
SavePurpose::Snapshot
} else {
SavePurpose::File
};
match engine.start_save_document(purpose) {
Ok(()) => Ok(Response::empty()),
Err(e) => Err(ProtocolError::engine(e.to_string())),
}
},
},
RequestRegistration {
kind: "poll_save_result",
handle: |engine, _payload, _b| {
let Some(bundle) = engine.poll_save_result() else {
return Ok(Response::json(serde_json::Value::Null));
};
// Pack: manifest ++ composite ++ blob0 ++ blob1 ++ ...
let mut bytes = Vec::new();
bytes.extend_from_slice(&bundle.manifest_json);
bytes.extend_from_slice(&bundle.composite_rgba);
let blobs: Vec<serde_json::Value> = bundle
.blobs
.iter()
.map(|b| {
bytes.extend_from_slice(&b.bytes);
json!({ "path": b.path, "len": b.bytes.len() })
})
.collect();
let value = json!({
"manifestLen": bundle.manifest_json.len(),
"compositeWidth": bundle.composite_width,
"compositeHeight": bundle.composite_height,
"compositeLen": bundle.composite_rgba.len(),
"blobs": blobs,
});
Ok(Response::binary(value, bytes))
},
},
RequestRegistration {
kind: "open_document",
handle: |engine, _payload, bytes| match engine.open_document(bytes) {
Ok(()) => Ok(Response::empty()),
// The structured LoadError JSON rides in the rejection message;
// the JS open caller `JSON.parse`s it for the LoadErrorToast.
Err(e) => Err(ProtocolError::engine(e.to_json().to_string())),
},
},
]
}