use std::collections::HashMap;
use super::DarklyEngine;
use crate::document::filter;
use crate::document::layer_kind::{self, IdMap};
use crate::document::{Document, Entity};
use crate::format::error::LoadError;
use crate::format::manifest::{Manifest, ManifestPixelRef, ManifestRequires};
use crate::format::unzip::unzip_entries;
use crate::gpu::blend_mode;
use crate::gpu::compositor::Compositor;
use crate::layer::{LayerId, LayerNode};
pub struct LoadDocument;
impl DarklyEngine {
pub fn open_document(&mut self, bytes: &[u8]) -> Result<(), LoadError> {
let entries = unzip_entries(bytes)?;
let manifest_bytes =
entries
.get("manifest.json")
.ok_or_else(|| LoadError::CorruptManifest {
reason: "archive missing manifest.json".to_string(),
})?;
let raw: serde_json::Value = serde_json::from_slice(manifest_bytes)?;
pre_check_container_version(&raw)?;
pre_check_requires_present(&raw)?;
let manifest: Manifest = serde_json::from_value(raw).map_err(LoadError::from)?;
pre_check_requires(self, &manifest.requires)?;
let (staging, id_map) = build_staging_document(&manifest)?;
install_staging(self, &manifest, staging, id_map, &entries);
Ok(())
}
}
fn pre_check_container_version(raw: &serde_json::Value) -> Result<(), LoadError> {
let found = raw
.get("container_version")
.and_then(serde_json::Value::as_u64)
.ok_or_else(|| LoadError::CorruptManifest {
reason: "manifest missing or malformed container_version".to_string(),
})?;
if found > crate::format::manifest::CONTAINER_VERSION as u64 {
return Err(LoadError::ContainerTooNew {
found: found as u32,
supported: crate::format::manifest::CONTAINER_VERSION,
});
}
Ok(())
}
fn pre_check_requires_present(raw: &serde_json::Value) -> Result<(), LoadError> {
if raw.get("requires").is_none() {
return Err(LoadError::CorruptManifest {
reason: "manifest missing required `requires` inventory".to_string(),
});
}
Ok(())
}
fn pre_check_requires(engine: &DarklyEngine, requires: &ManifestRequires) -> Result<(), LoadError> {
let mut missing = Vec::new();
let blend_registry = blend_mode::registry();
for id in &requires.blend_mode {
if blend_registry.get(id).is_none() {
missing.push(format!("blend_mode/{id}"));
}
}
let layer_kind_registry = layer_kind::registry();
for id in &requires.layer_kind {
if layer_kind_registry.get(id).is_none() {
missing.push(format!("layer_kind/{id}"));
}
}
let modifier_registry = filter::registry();
for id in &requires.modifier {
if modifier_registry.get(id).is_none() {
missing.push(format!("filter/{id}"));
}
}
let veil_registry = engine.compositor.veil_chain().registry();
for id in &requires.veil {
if !veil_registry.has(id) {
missing.push(format!("veil/{id}"));
}
}
if !missing.is_empty() {
missing.sort();
return Err(LoadError::UnsupportedFeatures { missing });
}
Ok(())
}
fn build_staging_document(manifest: &Manifest) -> Result<(Document, IdMap), LoadError> {
let mut doc = Document::new(manifest.canvas.width, manifest.canvas.height);
doc.name = manifest.name.clone();
doc.canvas_origin =
crate::coord::CanvasPoint::new(manifest.canvas.origin_x, manifest.canvas.origin_y);
let mut id_map: IdMap = HashMap::with_capacity(manifest.nodes.len() + manifest.modifiers.len());
let new_root = doc.root_id();
id_map.insert(manifest.root, new_root);
let layer_kind_registry = layer_kind::registry();
let modifier_registry = filter::registry();
for entry in &manifest.nodes {
if entry.id == manifest.root {
let reg = layer_kind_registry.get(&entry.type_id).ok_or_else(|| {
LoadError::CorruptManifest {
reason: format!(
"manifest root {} declares layer_kind/{} but registry is missing it \
— `requires` block lies",
entry.id, entry.type_id
),
}
})?;
let new_root_node = (reg.deserialize)(&entry.body, new_root)?;
if let Some(Entity::Node(slot)) = doc.entities.get_mut(new_root) {
*slot = new_root_node;
}
continue;
}
let reg =
layer_kind_registry
.get(&entry.type_id)
.ok_or_else(|| LoadError::CorruptManifest {
reason: format!(
"node {} declares layer_kind/{} but registry is missing it \
— `requires` block lies",
entry.id, entry.type_id
),
})?;
let mut new_id_opt: Option<LayerId> = None;
let mut deserialize_err: Option<LoadError> = None;
let new_id = doc.entities.insert_with_key(|key| {
new_id_opt = Some(key);
match (reg.deserialize)(&entry.body, key) {
Ok(node) => Entity::Node(node),
Err(e) => {
deserialize_err = Some(e);
Entity::Node(LayerNode::Group(crate::layer::LayerGroup::new(
key,
String::new(),
)))
}
}
});
if let Some(e) = deserialize_err {
doc.entities.remove(new_id);
return Err(e);
}
id_map.insert(
entry.id,
new_id_opt.expect("insert_with_key always invokes closure"),
);
}
for entry in &manifest.modifiers {
let reg =
modifier_registry
.get(&entry.type_id)
.ok_or_else(|| LoadError::CorruptManifest {
reason: format!(
"filter {} declares filter/{} but registry is missing it \
— `requires` block lies",
entry.id, entry.type_id
),
})?;
let mut new_id_opt: Option<LayerId> = None;
let mut deserialize_err: Option<LoadError> = None;
let new_id = doc.entities.insert_with_key(|key| {
new_id_opt = Some(key);
match (reg.deserialize)(&entry.body, key) {
Ok(m) => Entity::Filter(m),
Err(e) => {
deserialize_err = Some(e);
Entity::Filter(crate::document::Filter {
id: key,
common: crate::layer::NodeCommon::new(String::new()),
kind: crate::document::FilterKind::mask_with_bounds(
crate::coord::CanvasRect::from_xywh(0, 0, 0, 0),
),
})
}
}
});
if let Some(e) = deserialize_err {
doc.entities.remove(new_id);
return Err(e);
}
id_map.insert(
entry.id,
new_id_opt.expect("insert_with_key always invokes closure"),
);
}
let entity_ids: Vec<LayerId> = doc.entities.keys().collect();
for eid in entity_ids {
let type_id = match doc.entities.get(eid) {
Some(Entity::Node(n)) => Some(("node", n.type_id())),
Some(Entity::Filter(m)) => Some(("filter", m.type_id())),
None => None,
};
match type_id {
Some(("node", tid)) => {
let reg = layer_kind_registry
.get(tid)
.expect("layer kind type_id passed pass 1");
if let Some(Entity::Node(n)) = doc.entities.get_mut(eid) {
(reg.remap_ids)(n, &id_map);
}
}
Some(("filter", tid)) => {
let reg = modifier_registry
.get(tid)
.expect("filter type_id passed pass 1");
if let Some(Entity::Filter(m)) = doc.entities.get_mut(eid) {
(reg.remap_ids)(m, &id_map);
}
}
_ => {}
}
}
rebuild_parent_map(&mut doc);
if let Some(old_sel_id) = manifest.selection_id {
if let Some(new_id) = id_map.get(&old_sel_id) {
doc.selection = Some(*new_id);
}
}
Ok((doc, id_map))
}
fn rebuild_parent_map(doc: &mut Document) {
doc.parent.clear();
let node_ids: Vec<LayerId> = doc
.entities
.iter()
.filter_map(|(id, e)| matches!(e, Entity::Node(_)).then_some(id))
.collect();
for nid in node_ids {
let (children, filters) = match doc.entities.get(nid) {
Some(Entity::Node(LayerNode::Group(g))) => (g.children.clone(), g.filters.clone()),
Some(Entity::Node(n)) => (Vec::new(), n.filters().to_vec()),
_ => continue,
};
for c in children {
doc.parent.insert(c, nid);
}
for m in filters {
doc.parent.insert(m, nid);
}
}
}
fn install_staging(
engine: &mut DarklyEngine,
manifest: &Manifest,
staging: Document,
id_map: IdMap,
entries: &HashMap<String, Vec<u8>>,
) {
engine.readbacks.cancel(|_| true);
engine.doc = staging;
engine.compositor = Compositor::new(
&engine.gpu.device,
&engine.gpu.queue,
engine.gpu.surface_format(),
engine.doc.width,
engine.doc.height,
engine.doc.root_id(),
);
upload_loaded_pixels(engine, manifest, &id_map, entries);
engine.compositor.veil_chain_mut().resize(
&engine.gpu.device,
&engine.gpu.queue,
engine.doc.width,
engine.doc.height,
);
restore_veils(engine, manifest);
ensure_selection_state(engine);
engine.sync_compositor_layers();
upload_loaded_void_pixels(engine, entries);
engine.active_stroke_layer = None;
engine.isolated_node = None;
engine.floating = None;
engine.selection_overlay.clear();
engine.tool_overlay.clear();
engine.undo_stack = crate::undo::UndoStack::new(50);
engine.thumbnail_cache = super::ThumbnailCache::new();
engine.thumbnail_version = engine.thumbnail_version.wrapping_add(1);
engine.compositor.mark_dirty();
engine.compositor.mark_needs_present();
}
fn upload_loaded_pixels(
engine: &mut DarklyEngine,
manifest: &Manifest,
id_map: &IdMap,
entries: &HashMap<String, Vec<u8>>,
) {
use crate::format::manifest::texture_format_from_str;
fn extract_pixel_ref(body: &serde_json::Value) -> Option<ManifestPixelRef> {
body.get("pixels")
.and_then(|v| serde_json::from_value::<ManifestPixelRef>(v.clone()).ok())
}
for entry in &manifest.nodes {
if entry.type_id == crate::document::layer_kinds::void::TYPE_ID {
continue;
}
let Some(new_id) = id_map.get(&entry.id).copied() else {
continue;
};
let Some(pixels) = extract_pixel_ref(&entry.body) else {
continue;
};
let Some(format) = texture_format_from_str(&pixels.format) else {
continue;
};
match format {
wgpu::TextureFormat::Rgba8Unorm => {
engine.compositor.ensure_raster_layer(
&engine.gpu.device,
&engine.gpu.queue,
new_id,
pixels.bounds,
);
}
_ => {
engine.compositor.ensure_node_texture(
&engine.gpu.device,
&engine.gpu.queue,
new_id,
format,
pixels.bounds,
);
}
}
if let Some(bytes) = entries.get(&pixels.pixels) {
upload_to_node(engine, new_id, bytes);
}
}
let selection_old_id = manifest.selection_id;
for entry in &manifest.modifiers {
if Some(entry.id) == selection_old_id {
continue;
}
let Some(new_id) = id_map.get(&entry.id).copied() else {
continue;
};
let Some(pixels) = extract_pixel_ref(&entry.body) else {
continue;
};
let Some(format) = texture_format_from_str(&pixels.format) else {
continue;
};
engine.compositor.ensure_node_texture(
&engine.gpu.device,
&engine.gpu.queue,
new_id,
format,
pixels.bounds,
);
if let Some(bytes) = entries.get(&pixels.pixels) {
upload_to_node(engine, new_id, bytes);
}
}
}
fn upload_loaded_void_pixels(engine: &mut DarklyEngine, entries: &HashMap<String, Vec<u8>>) {
let restores: Vec<(LayerId, u32, u32, String)> = engine
.doc
.all_void_layers()
.into_iter()
.filter_map(|v| {
let frame = v.frame.as_ref()?;
let bounds = frame.bounds;
Some((v.id, bounds.width, bounds.height, frame.pixels.clone()))
})
.collect();
for (id, w, h, blob_key) in restores {
let Some(bytes) = entries.get(&blob_key) else {
continue;
};
engine.compositor.restore_void_pixels(
&engine.gpu.device,
&engine.gpu.queue,
id,
w,
h,
bytes,
);
}
}
fn upload_to_node(engine: &mut DarklyEngine, node_id: LayerId, bytes: &[u8]) {
let ok = engine
.compositor
.upload_node_pixels(&engine.gpu.queue, node_id, bytes);
if !ok {
log::error!("load: pixel upload failed for node {:?}", node_id.to_ffi());
}
}
fn restore_veils(engine: &mut DarklyEngine, manifest: &Manifest) {
engine.compositor.veil_chain_mut().clear_veils();
for veil in &manifest.veils {
let type_id = veil.instance.type_id.clone();
let params = veil.instance.params.clone();
if !engine.compositor.veil_chain().registry().has(&type_id) {
log::error!(
"load: veil '{type_id}' missing despite requires pre-check — \
registry drift?"
);
continue;
}
engine.add_veil(&type_id, ¶ms);
if !veil.visible {
let last = engine.compositor.veil_chain().count().saturating_sub(1);
engine.set_veil_visible(last, false);
}
}
}
fn ensure_selection_state(engine: &mut DarklyEngine) {
let id = engine.doc.ensure_selection_filter();
engine.compositor.ensure_selection_state(
&engine.gpu.device,
id,
engine.brush_pipelines.selection_bind_group_layout(),
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::manifest::ManifestEntry;
#[allow(unused_imports)]
use serde_json::json;
#[test]
fn rebuild_parent_map_derives_from_children_lists() {
let mut doc = Document::new(8, 8);
let g = doc.add_group(None);
let l = doc.add_raster_layer(Some(g));
let m = doc.add_mask_filter(l).expect("mask added");
doc.parent.clear();
rebuild_parent_map(&mut doc);
assert_eq!(doc.parent_of(l), Some(g));
assert_eq!(doc.parent_of(m), Some(l));
assert_eq!(doc.parent_of(g), Some(doc.root));
}
#[test]
fn staging_document_round_trips_one_of_each_kind() {
let root_id: u64 = 1;
let group_id: u64 = 2;
let raster_id: u64 = 3;
let mask_id: u64 = 4;
let manifest = Manifest {
format: crate::format::manifest::FORMAT_TAG.to_string(),
container_version: crate::format::manifest::CONTAINER_VERSION,
writer: crate::format::manifest::ManifestWriter::current(),
name: "Test".to_string(),
canvas: crate::format::manifest::ManifestCanvas {
width: 8,
height: 8,
origin_x: 0,
origin_y: 0,
},
requires: ManifestRequires {
layer_kind: vec!["group".to_string(), "raster".to_string()],
modifier: vec!["mask".to_string()],
blend_mode: vec!["normal".to_string()],
..Default::default()
},
composite: "composite.png".to_string(),
root: root_id,
nodes: vec![
ManifestEntry {
id: root_id,
type_id: "group".to_string(),
body: json!({
"name": "Root",
"visible": true,
"locked": false,
"opacity": 1.0,
"blend_mode": "normal",
"passthrough": true,
"collapsed": false,
"children": [group_id],
"modifiers": []
}),
},
ManifestEntry {
id: group_id,
type_id: "group".to_string(),
body: json!({
"name": "Group",
"visible": true,
"locked": false,
"opacity": 1.0,
"blend_mode": "normal",
"passthrough": true,
"collapsed": false,
"children": [raster_id],
"modifiers": []
}),
},
ManifestEntry {
id: raster_id,
type_id: "raster".to_string(),
body: json!({
"name": "Layer",
"visible": true,
"locked": false,
"opacity": 1.0,
"blend_mode": "normal",
"pixels": {
"format": "rgba8unorm",
"pixels": "layers/3.pixels",
"bounds": { "origin": { "x": 0, "y": 0 }, "width": 8, "height": 8 }
},
"modifiers": [mask_id]
}),
},
],
modifiers: vec![ManifestEntry {
id: mask_id,
type_id: "mask".to_string(),
body: json!({
"name": "Mask",
"visible": true,
"locked": false,
"pixels": {
"format": "r8unorm",
"pixels": "layers/4.mask.pixels",
"bounds": { "origin": { "x": 0, "y": 0 }, "width": 8, "height": 8 }
}
}),
}],
selection_id: None,
veils: Vec::new(),
};
let (doc, id_map) = build_staging_document(&manifest).expect("build staging doc");
assert_eq!(doc.width, 8);
assert_eq!(doc.height, 8);
let new_group = *id_map.get(&group_id).expect("group remap");
let new_raster = *id_map.get(&raster_id).expect("raster remap");
let new_mask = *id_map.get(&mask_id).expect("mask remap");
assert_eq!(doc.parent_of(new_group), Some(doc.root_id()));
assert_eq!(doc.parent_of(new_raster), Some(new_group));
assert_eq!(doc.parent_of(new_mask), Some(new_raster));
}
}