Skip to main content

csp_format/
edit.rs

1//! Editing an opened .clip (R12): layer pixels, layer properties, adding
2//! and removing layers, and the stored preview.
3//!
4//! Each change is made in the in-memory database and external chunks, so
5//! [`ClipFile::save`] writes a consistent file: new external chunks are
6//! listed in `ExternalChunk` with their offsets, and replaced chunks are
7//! dropped. Layer pixels are written to every mipmap level (each level is
8//! the one above halved with a 2×2 box, as CSP makes them).
9//!
10//! New rows get their `MainId` from `ElemScheme.MaxIndex` (the largest id
11//! handed out so far), and new layers are built like the ones CSP makes:
12//! a mipmap of canvas-sized levels halved until the canvas fits in 128
13//! pixels (at least 4 levels), and a 512 × 512 thumbnail offscreen. As in
14//! CSP's own files, offscreens that hold nothing yet name an external
15//! chunk that is not stored.
16
17use std::hash::{BuildHasher, Hasher};
18
19use rusqlite::OptionalExtension;
20use rusqlite::types::Value;
21
22use crate::clip::ClipFile;
23use crate::database::{has_column, quote, value_as_text};
24use crate::error::{Error, Result, bail};
25use crate::image::RgbaImage;
26use crate::model::{LAYER_TYPE_PAPER, LAYER_TYPE_RASTER, LAYER_TYPE_ROOT, LayerKind};
27use crate::offscreen::{Attribute, InitColor, Parameter, PixelKind, encode};
28use crate::render::{BlendMode, render_like_preview};
29
30/// 16 random bytes shaped as a version 4 UUID (CSP's ids are UUIDs).
31pub fn new_uuid() -> [u8; 16] {
32    let mut bytes = [0u8; 16];
33    for half in bytes.chunks_mut(8) {
34        let mut hasher = std::collections::hash_map::RandomState::new().build_hasher();
35        hasher.write_u128(
36            std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos(),
37        );
38        half.copy_from_slice(&hasher.finish().to_be_bytes());
39    }
40    bytes[6] = (bytes[6] & 0x0f) | 0x40;
41    bytes[8] = (bytes[8] & 0x3f) | 0x80;
42    bytes
43}
44
45/// A new external chunk id: "extrnlid" and a UUID as 32 upper-case hex digits.
46pub fn new_external_id() -> String {
47    let mut text = String::from("extrnlid");
48    for byte in new_uuid() {
49        text.push_str(&format!("{byte:02X}"));
50    }
51    text
52}
53
54impl ClipFile {
55    /// Give the saved file a new random id (see [`ClipFile::set_file_id`]).
56    pub fn renew_file_id(&mut self) {
57        self.set_file_id(new_uuid());
58    }
59
60    /// Replace a raster layer's pixels. `image` is placed with its top-left
61    /// at canvas position `position`; pixels outside the layer's offscreen
62    /// are dropped. Only colour layers are supported.
63    pub fn set_layer_pixels(&mut self, layer_id: i64, image: &RgbaImage, position: (i64, i64)) -> Result<()> {
64        let layer = self.layers.get(layer_id).ok_or_else(|| Error::format(format!("No layer {layer_id}")))?.clone();
65        let mipmap = self
66            .mipmap(layer.render_mipmap)?
67            .ok_or_else(|| Error::format(format!("Layer {layer_id} has no render mipmap")))?;
68        let origin = layer.pixel_origin();
69        let mut level_image: Option<RgbaImage> = None;
70        for level in &mipmap.levels {
71            let row = self
72                .row("Offscreen", level.offscreen)?
73                .ok_or_else(|| Error::format(format!("No Offscreen {}", level.offscreen)))?;
74            let mut attribute = Attribute::parse(row.blob("Attribute").unwrap_or_default())?;
75            let parameter = attribute.parameter()?;
76            if parameter.kind() != PixelKind::Color {
77                bail!("Layer {layer_id} is not a colour layer ({:?})", parameter.kind());
78            }
79            let pixels = match level_image.take() {
80                None => {
81                    let mut full = RgbaImage::new(parameter.width, parameter.height);
82                    full.paste(image, position.0 - origin.0, position.1 - origin.1);
83                    full
84                }
85                Some(above) => {
86                    let mut halved = above.halve();
87                    if (halved.width, halved.height) != (parameter.width, parameter.height) {
88                        halved = halved.resize_area(parameter.width, parameter.height);
89                    }
90                    halved
91                }
92            };
93            let data = encode(&parameter, Some(&pixels), None)?;
94            attribute.set_block_sizes(&data.record_sizes());
95            let old = row.get("BlockData").and_then(value_as_text).filter(|id| !id.is_empty());
96            let id = new_external_id();
97            if let Some(old) = &old {
98                self.remove_external(old);
99            }
100            self.set_external(&id, data.to_bytes());
101            self.database().execute(
102                "UPDATE Offscreen SET Attribute = ?1, BlockData = ?2 WHERE MainId = ?3",
103                (attribute.to_bytes(), Value::Blob(id.into_bytes()), level.offscreen),
104            )?;
105            level_image = Some(pixels);
106        }
107        Ok(())
108    }
109
110    /// Set one `Layer` column.
111    pub fn set_layer_value(&mut self, layer_id: i64, column: &str, value: Value) -> Result<()> {
112        if !crate::database::has_column(self.database(), "Layer", column)? {
113            bail!("Layer has no column {column}");
114        }
115        let sql = format!("UPDATE Layer SET {} = ?1 WHERE MainId = ?2", crate::database::quote(column));
116        if self.database().execute(&sql, (value, layer_id))? == 0 {
117            bail!("No layer {layer_id}");
118        }
119        self.reload()
120    }
121
122    /// Renames a layer (`LayerName`).
123    pub fn set_layer_name(&mut self, layer_id: i64, name: &str) -> Result<()> {
124        self.set_layer_value(layer_id, "LayerName", Value::Text(name.to_owned()))
125    }
126
127    /// Opacity 0.0..=1.0 (stored as 0..=256).
128    pub fn set_layer_opacity(&mut self, layer_id: i64, opacity: f64) -> Result<()> {
129        let stored = (opacity.clamp(0.0, 1.0) * crate::model::OPACITY_FULL as f64).round() as i64;
130        self.set_layer_value(layer_id, "LayerOpacity", Value::Integer(stored))
131    }
132
133    /// Sets the blend mode (`LayerComposite`).
134    pub fn set_layer_blend(&mut self, layer_id: i64, mode: BlendMode) -> Result<()> {
135        self.set_layer_value(layer_id, "LayerComposite", Value::Integer(mode.code()))
136    }
137
138    /// Shows or hides a layer (`LayerVisibility` bit 0; the other bits are kept).
139    pub fn set_layer_visible(&mut self, layer_id: i64, visible: bool) -> Result<()> {
140        let layer = self.layers.get(layer_id).ok_or_else(|| Error::format(format!("No layer {layer_id}")))?;
141        let value = (layer.row.int("LayerVisibility") & !1) | visible as i64;
142        self.set_layer_value(layer_id, "LayerVisibility", Value::Integer(value))
143    }
144
145    /// Clips the layer to the layer below, or not (`LayerClip`).
146    pub fn set_layer_clipping(&mut self, layer_id: i64, clipping: bool) -> Result<()> {
147        self.set_layer_value(layer_id, "LayerClip", Value::Integer(clipping as i64))
148    }
149
150    /// Re-render `CanvasPreview` from the layers (call after editing pixels
151    /// so file browsers show the new content).
152    pub fn update_preview(&mut self) -> Result<bool> {
153        let Some((image, _, _)) = render_like_preview(self)? else { return Ok(false) };
154        let png = image.to_png()?;
155        self.database().execute(
156            "UPDATE CanvasPreview SET ImageData = ?1, ImageWidth = ?2, ImageHeight = ?3",
157            (png, image.width as i64, image.height as i64),
158        )?;
159        Ok(true)
160    }
161}
162
163/// The kinds of layer [`ClipFile`] can add.
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum NewLayer {
166    /// A raster (pixel) layer.
167    Raster,
168    /// A folder.
169    Folder,
170    /// The paper (用紙) in an RGB colour.
171    Paper([u8; 3]),
172    /// The root folder (only for new files).
173    Root,
174}
175
176/// Size of the thumbnail offscreen CSP gives every layer.
177const THUMBNAIL_SIZE: u32 = 512;
178
179/// CSP's `LightTableInfo` for a new layer (an empty list).
180const LIGHT_TABLE_INFO: [u8; 15] = [1, 1, 1, 8, b't', b'y', b'p', b'e', b'n', b'a', b'm', b'e', 6, 0, 0];
181
182/// The `MonochromeFillInfo` CSP gives the paper.
183fn monochrome_fill_info() -> Vec<u8> {
184    let name = "MonochromeSetting";
185    let mut data = Vec::new();
186    data.extend(0u32.to_be_bytes());
187    data.extend(1u32.to_be_bytes());
188    data.extend((name.len() as u32).to_be_bytes());
189    for unit in name.encode_utf16() {
190        data.extend(unit.to_be_bytes());
191    }
192    data.extend([0u8; 12]);
193    data.extend(1u32.to_be_bytes());
194    let total = data.len() as u32;
195    data[..4].copy_from_slice(&total.to_be_bytes());
196    data
197}
198
199/// A colour channel as CSP stores it in 32-bit columns (the top byte is
200/// what readers use).
201fn color_word(value: u8) -> i64 {
202    value as i64 * 0x0101_0101
203}
204
205/// A `LayerUuid`: 32 hex digits grouped 10-4-4-4-10, as CSP writes them.
206pub(crate) fn layer_uuid() -> String {
207    let hex: String = new_uuid().iter().map(|b| format!("{b:02x}")).collect();
208    format!("{}-{}-{}-{}-{}", &hex[..10], &hex[10..14], &hex[14..18], &hex[18..22], &hex[22..])
209}
210
211/// Sizes of a new layer's mipmap levels: the canvas halved (rounded
212/// down) until it fits in 128 pixels, with at least 4 levels.
213pub fn mipmap_sizes(width: u32, height: u32) -> Vec<(u32, u32)> {
214    let mut sizes = vec![(width.max(1), height.max(1))];
215    loop {
216        let (w, h) = *sizes.last().expect("one level");
217        if sizes.len() >= 4 && w.max(h) <= 128 {
218            break;
219        }
220        sizes.push(((w / 2).max(1), (h / 2).max(1)));
221    }
222    sizes
223}
224
225impl ClipFile {
226    /// The next `MainId` for `table`: one above both `ElemScheme.MaxIndex`
227    /// and the largest id in the table; `MaxIndex` is moved up to it.
228    pub fn next_main_id(&self, table: &str) -> Result<i64> {
229        let database = self.database();
230        let max_index: Option<i64> = database
231            .query_row("SELECT MaxIndex FROM ElemScheme WHERE TableName = ?1", [table], |row| row.get(0))
232            .optional()?
233            .flatten();
234        let largest: i64 =
235            database
236                .query_row(&format!("SELECT COALESCE(MAX(MainId), 0) FROM {}", quote(table)), [], |row| row.get(0))?;
237        let id = max_index.unwrap_or(0).max(largest) + 1;
238        database.execute("UPDATE ElemScheme SET MaxIndex = ?1 WHERE TableName = ?2", (id, table))?;
239        Ok(id)
240    }
241
242    /// Insert a row; columns the table does not have are left out.
243    fn insert_row(&self, table: &str, values: &[(&str, Value)]) -> Result<()> {
244        let mut names = Vec::new();
245        let mut kept = Vec::new();
246        for (name, value) in values {
247            if has_column(self.database(), table, name)? {
248                names.push(quote(name));
249                kept.push(value.clone());
250            }
251        }
252        let marks: Vec<String> = (1..=kept.len()).map(|i| format!("?{i}")).collect();
253        let sql = format!("INSERT INTO {} ({}) VALUES ({})", quote(table), names.join(", "), marks.join(", "));
254        self.database().execute(&sql, rusqlite::params_from_iter(kept))?;
255        Ok(())
256    }
257
258    /// A colour offscreen of `width × height`. With `store`, its (empty)
259    /// block data is saved as a new external chunk; without, it only names one.
260    fn new_offscreen(&mut self, layer_id: i64, size: (u32, u32), init: Option<&InitColor>, store: bool) -> Result<i64> {
261        let id = self.next_main_id("Offscreen")?;
262        let parameter = Parameter::new(PixelKind::Color, size.0, size.1);
263        let mut attribute = Attribute::new(&parameter, init);
264        let external = new_external_id();
265        if store {
266            let data = encode(&parameter, Some(&RgbaImage::new(size.0, size.1)), None)?;
267            attribute.set_block_sizes(&data.record_sizes());
268            self.set_external(&external, data.to_bytes());
269        }
270        self.insert_row(
271            "Offscreen",
272            &[
273                ("MainId", Value::Integer(id)),
274                ("CanvasId", Value::Integer(self.canvas.main_id)),
275                ("LayerId", Value::Integer(layer_id)),
276                ("Attribute", Value::Blob(attribute.to_bytes())),
277                ("BlockData", Value::Blob(external.into_bytes())),
278            ],
279        )?;
280        Ok(id)
281    }
282
283    /// A render mipmap for a new layer. Only a raster layer's base level
284    /// stores blocks (all empty): CSP reports an empty raster layer whose base
285    /// offscreen has no block data as damaged, while folders, paper and the
286    /// smaller levels go without.
287    pub(crate) fn new_mipmap(&mut self, layer_id: i64, init: Option<&InitColor>, store_base: bool) -> Result<i64> {
288        self.new_mipmap_sized(layer_id, (self.canvas.width_px, self.canvas.height_px), init, store_base)
289    }
290
291    /// [`ClipFile::new_mipmap`] with a base level of `size` instead of the
292    /// canvas size.
293    pub(crate) fn new_mipmap_sized(
294        &mut self,
295        layer_id: i64,
296        size: (u32, u32),
297        init: Option<&InitColor>,
298        store_base: bool,
299    ) -> Result<i64> {
300        let sizes = mipmap_sizes(size.0, size.1);
301        let mipmap = self.next_main_id("Mipmap")?;
302        let infos = sizes.iter().map(|_| self.next_main_id("MipmapInfo")).collect::<Result<Vec<_>>>()?;
303        for (level, size) in sizes.iter().enumerate() {
304            let offscreen = self.new_offscreen(layer_id, *size, init, store_base && level == 0)?;
305            self.insert_row(
306                "MipmapInfo",
307                &[
308                    ("MainId", Value::Integer(infos[level])),
309                    ("CanvasId", Value::Integer(self.canvas.main_id)),
310                    ("LayerId", Value::Integer(layer_id)),
311                    ("ThisScale", Value::Real(100.0 / (1u64 << level) as f64)),
312                    ("Offscreen", Value::Integer(offscreen)),
313                    ("NextIndex", Value::Integer(infos.get(level + 1).copied().unwrap_or(0))),
314                ],
315            )?;
316        }
317        self.insert_row(
318            "Mipmap",
319            &[
320                ("MainId", Value::Integer(mipmap)),
321                ("CanvasId", Value::Integer(self.canvas.main_id)),
322                ("LayerId", Value::Integer(layer_id)),
323                ("MipmapCount", Value::Integer(sizes.len() as i64)),
324                ("BaseMipmapInfo", Value::Integer(infos[0])),
325            ],
326        )?;
327        Ok(mipmap)
328    }
329
330    /// A layer thumbnail that CSP redraws when it opens the file.
331    pub(crate) fn new_thumbnail(&mut self, layer_id: i64) -> Result<i64> {
332        let id = self.next_main_id("LayerThumbnail")?;
333        let offscreen = self.new_offscreen(layer_id, (THUMBNAIL_SIZE, THUMBNAIL_SIZE), None, true)?;
334        let int = Value::Integer;
335        let mut values = vec![("MainId", int(id)), ("CanvasId", int(self.canvas.main_id)), ("LayerId", int(layer_id))];
336        for column in [
337            "ThumbnailSmallerNeedRefresh",
338            "ThumbnailSmallNeedRefresh",
339            "ThumbnailMiddleNeedRefresh",
340            "ThumbnailLargeNeedRefresh",
341            "ThumbnailLargerNeedRefresh",
342            "ThumbnailMiddle2xNeedRefresh",
343            "ThumbnailLarger2xNeedRefresh",
344            "ThumbnailSmallerNeedRefresh1",
345            "ThumbnailSmallNeedRefresh1",
346            "ThumbnailMiddleNeedRefresh1",
347            "ThumbnailLargeNeedRefresh1",
348            "ThumbnailLargerNeedRefresh1",
349            "ThumbnailMiddle2xNeedRefresh1",
350            "ThumbnailLarger2xNeedRefresh1",
351        ] {
352            values.push((column, int(5)));
353        }
354        values.extend([
355            ("ThumbnailDrewMode", int(0)),
356            ("ThumbnailFixMode", int(-1)),
357            ("ThumbnailCanvasWidth", int(self.canvas.width_px as i64)),
358            ("ThumbnailCanvasHeight", int(self.canvas.height_px as i64)),
359            ("ThumbnailUseDrawColor", int(0)),
360            ("ThumbnailMainColorRed", int(0)),
361            ("ThumbnailMainColorGreen", int(0)),
362            ("ThumbnailMainColorBlue", int(0)),
363            ("ThumbnailSubColorRed", int(0)),
364            ("ThumbnailSubColorGreen", int(0)),
365            ("ThumbnailSubColorBlue", int(0)),
366            ("ThumbnailOffscreen", int(offscreen)),
367            ("ThumbnailColorTypeIndex", int(0)),
368            ("ThumbnailColorTypeBlack", int(1)),
369            ("ThumbnailColorTypeWhite", int(1)),
370            ("ThumbnailPrewviewColorTypeIndex", int(0)),
371            ("ThumbnailPrewviewColorTypeBlack", int(1)),
372            ("ThumbnailPrewviewColorTypeWhite", int(1)),
373            ("ThumbnailPrewviewColorTypeOpacity", int(1)),
374            ("ThumbnailPrewviewColorTypeImage", int(0x8000_0000)),
375            ("ThumbnailPrewviewColorTypeAlpha", int(0x8000_0000)),
376            ("ThumbnailPrewviewMaskBinarize", int(0)),
377            ("ThumbnailPrewviewMaskThreshold", int(0x8000_0000)),
378            ("ThumbnailDrewUseCanvasAspect0", int(1)),
379            ("ThumbnailDrewUseCanvasAspect1", int(1)),
380        ]);
381        self.insert_row("LayerThumbnail", &values)?;
382        Ok(id)
383    }
384
385    /// Add a layer on top of the children of folder `parent` and return
386    /// its id. Raster layers start empty (see [`ClipFile::set_layer_pixels`]).
387    pub fn add_layer(&mut self, parent: i64, kind: NewLayer, name: &str) -> Result<i64> {
388        let id = self.next_main_id("Layer")?;
389        self.insert_layer_row(id, kind, name)?;
390        if kind != NewLayer::Root {
391            self.link_on_top(parent, id)?;
392        }
393        if kind == NewLayer::Raster {
394            self.database().execute("UPDATE Canvas SET CanvasCurrentLayer = ?1", [id])?;
395        }
396        self.reload()?;
397        Ok(id)
398    }
399
400    /// Add a raster layer on top of the root folder's children holding
401    /// `image` with its top-left at canvas position `position`.
402    pub fn add_raster_layer(&mut self, name: &str, image: &RgbaImage, position: (i64, i64)) -> Result<i64> {
403        let id = self.add_layer(self.canvas.root_folder, NewLayer::Raster, name)?;
404        self.set_layer_pixels(id, image, position)?;
405        Ok(id)
406    }
407
408    pub(crate) fn insert_layer_row(&mut self, id: i64, kind: NewLayer, name: &str) -> Result<()> {
409        let paper_init = InitColor { do_initialize: true, alpha: 0xffff_ffff, image_values: vec![0; 4] };
410        let init = matches!(kind, NewLayer::Paper(_)).then_some(&paper_init);
411        let mipmap = self.new_mipmap(id, init, kind == NewLayer::Raster)?;
412        let thumbnail = self.new_thumbnail(id)?;
413        let int = Value::Integer;
414        let (layer_type, folder, masking) = match kind {
415            NewLayer::Raster => (LAYER_TYPE_RASTER, 0, 0),
416            NewLayer::Folder => (0, 1, 32),
417            NewLayer::Paper(_) => (LAYER_TYPE_PAPER, 0, 0),
418            NewLayer::Root => (LAYER_TYPE_ROOT, 1, 32),
419        };
420        let mut values = vec![
421            ("MainId", int(id)),
422            ("CanvasId", int(self.canvas.main_id)),
423            ("LayerName", Value::Text(name.to_owned())),
424            ("LayerType", int(layer_type)),
425            ("LayerLock", int(0)),
426            ("LayerClip", int(0)),
427            ("LayerMasking", int(masking)),
428        ];
429        for column in [
430            "LayerOffsetX",
431            "LayerOffsetY",
432            "LayerRenderOffscrOffsetX",
433            "LayerRenderOffscrOffsetY",
434            "LayerMaskOffsetX",
435            "LayerMaskOffsetY",
436            "LayerMaskOffscrOffsetX",
437            "LayerMaskOffscrOffsetY",
438        ] {
439            values.push((column, int(0)));
440        }
441        values.extend([
442            ("LayerOpacity", int(crate::model::OPACITY_FULL)),
443            ("LayerComposite", int(0)),
444            ("LayerUsePaletteColor", int(0)),
445            ("LayerNoticeablePaletteColor", int(0)),
446            ("LayerPaletteRed", int(0)),
447            ("LayerPaletteGreen", int(0)),
448            ("LayerPaletteBlue", int(0)),
449            ("LayerFolder", int(folder)),
450            ("LayerVisibility", int(1)),
451            ("LayerSelect", int(0)),
452            ("LayerNextIndex", int(0)),
453            ("LayerFirstChildIndex", int(0)),
454            ("LayerUuid", Value::Text(layer_uuid())),
455            ("LayerRenderMipmap", int(mipmap)),
456            ("LayerLayerMaskMipmap", int(0)),
457            ("LayerRenderThumbnail", int(thumbnail)),
458            ("LayerLayerMaskThumbnail", int(0)),
459        ]);
460        if kind != NewLayer::Root {
461            values.push(("LightTableInfo", Value::Blob(LIGHT_TABLE_INFO.to_vec())));
462        }
463        if kind == NewLayer::Raster {
464            values.extend([
465                ("LayerColorTypeIndex", int(0)),
466                ("LayerColorTypeBlackChecked", int(1)),
467                ("LayerColorTypeWhiteChecked", int(1)),
468            ]);
469        }
470        if let NewLayer::Paper(color) = kind {
471            values.extend([
472                ("DrawColorMainRed", int(color_word(color[0]))),
473                ("DrawColorMainGreen", int(color_word(color[1]))),
474                ("DrawColorMainBlue", int(color_word(color[2]))),
475                ("DrawColorEnable", int(1)),
476                ("DrawToRenderOffscreenType", int(61)),
477                ("SpecialRenderType", int(20)),
478                ("DrawToRenderMipmapType", int(31)),
479                ("MoveOffsetAndExpandType", int(31)),
480                ("FixOffsetAndExpandType", int(31)),
481                ("RenderBoundForLayerMoveType", int(31)),
482                ("SetRenderThumbnailInfoType", int(31)),
483                ("DrawRenderThumbnailType", int(31)),
484                ("MonochromeFillInfo", Value::Blob(monochrome_fill_info())),
485            ]);
486        }
487        self.insert_row("Layer", &values)
488    }
489
490    /// Make `id` the last (top) child of `parent`.
491    pub(crate) fn link_on_top(&mut self, parent: i64, id: i64) -> Result<()> {
492        let database = self.database();
493        let first: i64 = database
494            .query_row("SELECT LayerFirstChildIndex FROM Layer WHERE MainId = ?1", [parent], |row| row.get(0))
495            .optional()?
496            .ok_or_else(|| Error::format(format!("No layer {parent}")))?;
497        if first == 0 {
498            database.execute("UPDATE Layer SET LayerFirstChildIndex = ?1 WHERE MainId = ?2", (id, parent))?;
499            return Ok(());
500        }
501        let mut last = first;
502        loop {
503            let next: i64 =
504                database.query_row("SELECT LayerNextIndex FROM Layer WHERE MainId = ?1", [last], |row| row.get(0))?;
505            if next == 0 || next == id {
506                break;
507            }
508            last = next;
509        }
510        database.execute("UPDATE Layer SET LayerNextIndex = ?1 WHERE MainId = ?2", (id, last))?;
511        Ok(())
512    }
513
514    /// Remove a layer (a folder with everything in it): its rows, mipmaps,
515    /// thumbnails, offscreens and their external chunks. The root folder
516    /// cannot be removed.
517    pub fn remove_layer(&mut self, layer_id: i64) -> Result<()> {
518        let layer = self.layers.get(layer_id).ok_or_else(|| Error::format(format!("No layer {layer_id}")))?.clone();
519        if layer.kind == LayerKind::Root {
520            bail!("The root folder cannot be removed");
521        }
522        // Unlink: whoever points at the layer points past it.
523        let next = layer.next;
524        let database = self.database();
525        database
526            .execute("UPDATE Layer SET LayerFirstChildIndex = ?1 WHERE LayerFirstChildIndex = ?2", (next, layer_id))?;
527        database.execute("UPDATE Layer SET LayerNextIndex = ?1 WHERE LayerNextIndex = ?2", (next, layer_id))?;
528        let mut doomed = vec![layer_id];
529        let mut child = layer.first_child;
530        let mut stack = Vec::new();
531        while child != 0 || !stack.is_empty() {
532            if child == 0 {
533                child = stack.pop().unwrap_or(0);
534                continue;
535            }
536            doomed.push(child);
537            let (first, next): (i64, i64) = database.query_row(
538                "SELECT LayerFirstChildIndex, LayerNextIndex FROM Layer WHERE MainId = ?1",
539                [child],
540                |row| Ok((row.get(0)?, row.get(1)?)),
541            )?;
542            stack.push(next);
543            child = first;
544        }
545        for id in doomed {
546            let mut externals = Vec::new();
547            {
548                let mut statement = self.database().prepare("SELECT BlockData FROM Offscreen WHERE LayerId = ?1")?;
549                let rows = statement.query_map([id], |row| row.get::<_, Value>(0))?;
550                for value in rows {
551                    if let Some(text) = value_as_text(&value?) {
552                        externals.push(text);
553                    }
554                }
555            }
556            for external in externals {
557                self.remove_external(&external);
558            }
559            for table in ["Offscreen", "MipmapInfo", "Mipmap", "LayerThumbnail"] {
560                if crate::database::has_table(self.database(), table)? {
561                    self.database().execute(&format!("DELETE FROM {} WHERE LayerId = ?1", quote(table)), [id])?;
562                }
563            }
564            self.database().execute("DELETE FROM Layer WHERE MainId = ?1", [id])?;
565        }
566        let current: i64 = self.database().query_row("SELECT CanvasCurrentLayer FROM Canvas", [], |row| row.get(0))?;
567        if self
568            .database()
569            .query_row("SELECT COUNT(*) FROM Layer WHERE MainId = ?1", [current], |row| row.get::<_, i64>(0))?
570            == 0
571        {
572            self.database().execute("UPDATE Canvas SET CanvasCurrentLayer = 0", [])?;
573        }
574        self.reload()
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use super::*;
581
582    #[test]
583    fn external_ids_have_csp_shape() {
584        let id = new_external_id();
585        assert!(crate::container::is_external_id(&id), "{id}");
586        assert_ne!(id, new_external_id());
587    }
588}