Skip to main content

acadrust/
layer_state.rs

1//! Native ACAD layer-state storage.
2//!
3//! ACAD keeps named layer states as XRecords in the
4//! `ACAD_LAYERSTATES` dictionary attached to the layer table's extension
5//! dictionary.  This module exposes that object graph as semantic Rust data
6//! and provides capture, restore, rename, and delete operations.
7
8use std::ops::{BitOr, BitOrAssign};
9
10use crate::objects::{
11    Dictionary, DictionaryCloningFlags, ObjectType, XRecord, XRecordEntry, XRecordValue,
12};
13use crate::tables::normalize_name;
14use crate::types::{Color, Handle, LineWeight, Transparency};
15use crate::CadDocument;
16
17const LAYER_STATES_DICTIONARY: &str = "ACAD_LAYERSTATES";
18
19/// Layer properties selected for restoration from a named state.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct LayerStateMask(u32);
23
24impl LayerStateMask {
25    pub const NONE: Self = Self(0);
26    pub const ON: Self = Self(0x0001);
27    pub const FROZEN: Self = Self(0x0002);
28    pub const LOCKED: Self = Self(0x0004);
29    pub const PLOT: Self = Self(0x0008);
30    pub const NEW_VIEWPORT: Self = Self(0x0010);
31    pub const COLOR: Self = Self(0x0020);
32    pub const LINE_TYPE: Self = Self(0x0040);
33    pub const LINE_WEIGHT: Self = Self(0x0080);
34    pub const PLOT_STYLE: Self = Self(0x0100);
35    pub const CURRENT_VIEWPORT: Self = Self(0x0200);
36    pub const TRANSPARENCY: Self = Self(0x0400);
37    pub const LAST_RESTORED: Self = Self(0x1_0000);
38
39    /// Properties represented by [`crate::tables::Layer`].
40    pub const SUPPORTED: Self = Self(
41        Self::ON.0
42            | Self::FROZEN.0
43            | Self::LOCKED.0
44            | Self::PLOT.0
45            | Self::NEW_VIEWPORT.0
46            | Self::COLOR.0
47            | Self::LINE_TYPE.0
48            | Self::LINE_WEIGHT.0
49            | Self::PLOT_STYLE.0
50            | Self::TRANSPARENCY.0,
51    );
52
53    /// All standard ACAD layer-state properties.
54    pub const ALL: Self = Self(0x07ff);
55
56    pub const fn from_bits(bits: u32) -> Self {
57        Self(bits)
58    }
59
60    pub const fn bits(self) -> u32 {
61        self.0
62    }
63
64    pub const fn contains(self, other: Self) -> bool {
65        (self.0 & other.0) == other.0
66    }
67}
68
69impl BitOr for LayerStateMask {
70    type Output = Self;
71
72    fn bitor(self, rhs: Self) -> Self::Output {
73        Self(self.0 | rhs.0)
74    }
75}
76
77impl BitOrAssign for LayerStateMask {
78    fn bitor_assign(&mut self, rhs: Self) {
79        self.0 |= rhs.0;
80    }
81}
82
83/// Saved properties for one layer.
84#[derive(Debug, Clone, PartialEq)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86pub struct LayerStateLayer {
87    pub layer_handle: Handle,
88    pub layer_name: String,
89    pub off: bool,
90    pub frozen: bool,
91    pub locked: bool,
92    pub plottable: bool,
93    pub new_viewport_frozen: bool,
94    pub color: Color,
95    #[cfg_attr(feature = "serde", serde(default))]
96    pub color_name: Option<String>,
97    #[cfg_attr(feature = "serde", serde(default))]
98    pub book_name: Option<String>,
99    pub line_type: String,
100    pub line_weight: LineWeight,
101    pub plot_style: String,
102    pub transparency: Option<Transparency>,
103}
104
105/// A named layer state stored in a drawing.
106#[derive(Debug, Clone, PartialEq)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
108pub struct LayerState {
109    pub name: String,
110    pub description: String,
111    pub mask: LayerStateMask,
112    pub hidden: bool,
113    pub current_layer: String,
114    pub layers: Vec<LayerStateLayer>,
115}
116
117impl CadDocument {
118    pub(crate) fn rename_layer_state_references(&mut self, old_key: &str, new_name: &str) {
119        let Some(dictionary_handle) = self.layer_states_dictionary_handle() else {
120            return;
121        };
122        let handles = match self.objects.get(&dictionary_handle) {
123            Some(ObjectType::Dictionary(dictionary)) => dictionary
124                .entries
125                .iter()
126                .map(|(_, handle)| *handle)
127                .collect::<Vec<_>>(),
128            _ => return,
129        };
130        for handle in handles {
131            let Some(ObjectType::XRecord(xrecord)) = self.objects.get_mut(&handle) else {
132                continue;
133            };
134            let header_end = xrecord
135                .entries
136                .iter()
137                .position(|entry| entry.code == 330)
138                .unwrap_or(xrecord.entries.len());
139            let Some(entry) = xrecord.entries[..header_end].iter_mut().find(|entry| {
140                entry.code == 302
141                    && entry
142                        .value
143                        .as_string()
144                        .is_some_and(|name| normalize_name(name) == old_key)
145            }) else {
146                continue;
147            };
148            entry.value = XRecordValue::String(new_name.to_string());
149            xrecord.raw_data.clear();
150            xrecord.raw_dwg_data = None;
151        }
152    }
153
154    /// Return every native layer state stored in the drawing.
155    pub fn layer_states(&self) -> Vec<LayerState> {
156        let Some(dictionary_handle) = self.layer_states_dictionary_handle() else {
157            return Vec::new();
158        };
159        let Some(ObjectType::Dictionary(dictionary)) = self.objects.get(&dictionary_handle) else {
160            return Vec::new();
161        };
162
163        dictionary
164            .entries
165            .iter()
166            .filter_map(|(name, handle)| {
167                let ObjectType::XRecord(xrecord) = self.objects.get(handle)? else {
168                    return None;
169                };
170                Some(self.decode_layer_state(name, xrecord))
171            })
172            .collect()
173    }
174
175    /// Return one native layer state by name (case-insensitive).
176    pub fn layer_state(&self, name: &str) -> Option<LayerState> {
177        self.layer_states()
178            .into_iter()
179            .find(|state| state.name.eq_ignore_ascii_case(name))
180    }
181
182    /// Capture the current layer table and store it under `name`.
183    ///
184    /// Existing states with the same name are updated in place.
185    pub fn capture_layer_state(
186        &mut self,
187        name: impl Into<String>,
188        description: impl Into<String>,
189    ) -> Handle {
190        let name = name.into();
191        let current_layer = if self.header.current_layer_name.is_empty() {
192            self.layers
193                .iter()
194                .find(|layer| layer.handle == self.header.current_layer_handle)
195                .map(|layer| layer.name.clone())
196                .unwrap_or_else(|| "0".to_string())
197        } else {
198            self.header.current_layer_name.clone()
199        };
200        let layers = self
201            .layers
202            .iter()
203            .map(|layer| LayerStateLayer {
204                layer_handle: layer.handle,
205                layer_name: layer.name.clone(),
206                off: layer.flags.off,
207                frozen: layer.flags.frozen,
208                locked: layer.flags.locked,
209                plottable: layer.is_plottable,
210                new_viewport_frozen: layer.flags.frozen_in_new_viewport,
211                color: layer.color,
212                color_name: layer.color_name.clone(),
213                book_name: layer.book_name.clone(),
214                line_type: layer.line_type.clone(),
215                line_weight: layer.line_weight,
216                plot_style: if layer.plot_style.is_empty() {
217                    format!("Color_{}", layer.color.approximate_index())
218                } else {
219                    layer.plot_style.clone()
220                },
221                transparency: Some(layer.transparency),
222            })
223            .collect();
224
225        self.store_layer_state(LayerState {
226            name,
227            description: description.into(),
228            mask: LayerStateMask::SUPPORTED,
229            hidden: false,
230            current_layer,
231            layers,
232        })
233    }
234
235    /// Store a semantic layer state in the native ACAD dictionary/XRecord form.
236    ///
237    /// Existing states with the same name are updated in place.
238    pub fn store_layer_state(&mut self, state: LayerState) -> Handle {
239        let dictionary_handle = self.ensure_layer_states_dictionary();
240        let existing_handle = self
241            .dictionary_entry(dictionary_handle, &state.name)
242            .map(|(_, handle)| handle);
243        let xrecord_handle = existing_handle.unwrap_or_else(|| self.allocate_handle());
244        let entries = self.encode_layer_state(&state);
245
246        let mut xrecord = match self.objects.remove(&xrecord_handle) {
247            Some(ObjectType::XRecord(xrecord)) => xrecord,
248            _ => XRecord::named(&state.name),
249        };
250        xrecord.handle = xrecord_handle;
251        xrecord.owner = dictionary_handle;
252        xrecord.reactors = vec![dictionary_handle];
253        xrecord.name = state.name.clone();
254        xrecord.cloning_flags = DictionaryCloningFlags::KeepExisting;
255        xrecord.entries = entries;
256        xrecord.raw_data.clear();
257        xrecord.raw_dwg_data = None;
258        xrecord.raw_dwg_handle_bits = 0;
259        xrecord.raw_dwg_version = None;
260        self.objects
261            .insert(xrecord_handle, ObjectType::XRecord(xrecord));
262
263        if let Some(ObjectType::Dictionary(dictionary)) = self.objects.get_mut(&dictionary_handle) {
264            if let Some((key, handle)) = dictionary
265                .entries
266                .iter_mut()
267                .find(|(key, _)| key.eq_ignore_ascii_case(&state.name))
268            {
269                *key = state.name;
270                *handle = xrecord_handle;
271            } else {
272                dictionary.entries.push((state.name, xrecord_handle));
273            }
274        }
275
276        xrecord_handle
277    }
278
279    /// Restore the selected properties of a named state.
280    pub fn restore_layer_state(&mut self, name: &str) -> Option<usize> {
281        let state = self.layer_state(name)?;
282        let mut restored = 0usize;
283
284        for saved in &state.layers {
285            let layer_name = if !saved.layer_handle.is_null() {
286                self.layers
287                    .iter()
288                    .find(|layer| layer.handle == saved.layer_handle)
289                    .map(|layer| layer.name.clone())
290            } else {
291                None
292            }
293            .unwrap_or_else(|| saved.layer_name.clone());
294            let layer = self.layers.get_mut(&layer_name);
295
296            let Some(layer) = layer else {
297                continue;
298            };
299            if state.mask.contains(LayerStateMask::ON) {
300                layer.flags.off = saved.off;
301            }
302            if state.mask.contains(LayerStateMask::FROZEN) {
303                layer.flags.frozen = saved.frozen;
304            }
305            if state.mask.contains(LayerStateMask::LOCKED) {
306                layer.flags.locked = saved.locked;
307            }
308            if state.mask.contains(LayerStateMask::PLOT) {
309                layer.is_plottable = saved.plottable;
310            }
311            if state.mask.contains(LayerStateMask::NEW_VIEWPORT) {
312                layer.flags.frozen_in_new_viewport = saved.new_viewport_frozen;
313            }
314            if state.mask.contains(LayerStateMask::COLOR) {
315                layer.color = saved.color;
316                layer.color_name.clone_from(&saved.color_name);
317                layer.book_name.clone_from(&saved.book_name);
318            }
319            if state.mask.contains(LayerStateMask::LINE_TYPE) && !saved.line_type.is_empty() {
320                layer.line_type.clone_from(&saved.line_type);
321            }
322            if state.mask.contains(LayerStateMask::LINE_WEIGHT) {
323                layer.line_weight = saved.line_weight;
324            }
325            if state.mask.contains(LayerStateMask::PLOT_STYLE) {
326                layer.plot_style.clone_from(&saved.plot_style);
327            }
328            if state.mask.contains(LayerStateMask::TRANSPARENCY) {
329                if let Some(transparency) = saved.transparency {
330                    layer.transparency = transparency;
331                }
332            }
333            restored += 1;
334        }
335
336        if let Some(layer) = self.layers.get(&state.current_layer) {
337            self.header.current_layer_handle = layer.handle;
338            self.header.current_layer_name.clone_from(&layer.name);
339        }
340
341        Some(restored)
342    }
343
344    /// Rename a native layer state. Returns `false` when the source is missing
345    /// or the destination already exists.
346    pub fn rename_layer_state(&mut self, old_name: &str, new_name: &str) -> bool {
347        let Some(dictionary_handle) = self.layer_states_dictionary_handle() else {
348            return false;
349        };
350        if self.dictionary_entry(dictionary_handle, new_name).is_some()
351            && !old_name.eq_ignore_ascii_case(new_name)
352        {
353            return false;
354        }
355        let Some(ObjectType::Dictionary(dictionary)) = self.objects.get_mut(&dictionary_handle)
356        else {
357            return false;
358        };
359        let Some((key, handle)) = dictionary
360            .entries
361            .iter_mut()
362            .find(|(key, _)| key.eq_ignore_ascii_case(old_name))
363        else {
364            return false;
365        };
366        *key = new_name.to_string();
367        let xrecord_handle = *handle;
368        if let Some(ObjectType::XRecord(xrecord)) = self.objects.get_mut(&xrecord_handle) {
369            xrecord.name = new_name.to_string();
370        }
371        true
372    }
373
374    /// Delete a native layer state.
375    pub fn delete_layer_state(&mut self, name: &str) -> bool {
376        let Some(dictionary_handle) = self.layer_states_dictionary_handle() else {
377            return false;
378        };
379        let Some(ObjectType::Dictionary(dictionary)) = self.objects.get_mut(&dictionary_handle)
380        else {
381            return false;
382        };
383        let Some(index) = dictionary
384            .entries
385            .iter()
386            .position(|(key, _)| key.eq_ignore_ascii_case(name))
387        else {
388            return false;
389        };
390        let (_, handle) = dictionary.entries.remove(index);
391        self.objects.remove(&handle);
392        true
393    }
394
395    fn layer_extension_dictionary_handle(&self) -> Option<Handle> {
396        let table_handle = self.layers.handle();
397        self.xdic_by_handle
398            .get(&table_handle)
399            .copied()
400            .filter(|handle| self.objects.contains_key(handle))
401            .or_else(|| {
402                self.objects
403                    .iter()
404                    .find_map(|(handle, object)| match object {
405                        ObjectType::Dictionary(dictionary) if dictionary.owner == table_handle => {
406                            Some(*handle)
407                        }
408                        _ => None,
409                    })
410            })
411    }
412
413    fn layer_states_dictionary_handle(&self) -> Option<Handle> {
414        let extension_handle = self.layer_extension_dictionary_handle()?;
415        let Some(ObjectType::Dictionary(extension)) = self.objects.get(&extension_handle) else {
416            return None;
417        };
418        extension
419            .entries
420            .iter()
421            .find(|(key, _)| {
422                key.eq_ignore_ascii_case(LAYER_STATES_DICTIONARY)
423                    || key.eq_ignore_ascii_case("ACAD_LAYERSTATE")
424            })
425            .map(|(_, handle)| *handle)
426    }
427
428    fn ensure_layer_states_dictionary(&mut self) -> Handle {
429        let table_handle = self.layers.handle();
430        let extension_handle = self.layer_extension_dictionary_handle().unwrap_or_else(|| {
431            let handle = self.allocate_handle();
432            let mut dictionary = Dictionary::new();
433            dictionary.handle = handle;
434            dictionary.owner = table_handle;
435            dictionary.hard_owner = true;
436            self.objects
437                .insert(handle, ObjectType::Dictionary(dictionary));
438            handle
439        });
440        self.xdic_by_handle.insert(table_handle, extension_handle);
441
442        if let Some(handle) = self.layer_states_dictionary_handle() {
443            if matches!(self.objects.get(&handle), Some(ObjectType::Dictionary(_))) {
444                return handle;
445            }
446        }
447
448        let dictionary_handle = self.allocate_handle();
449        let mut dictionary = Dictionary::new();
450        dictionary.handle = dictionary_handle;
451        dictionary.owner = extension_handle;
452        dictionary.reactors = vec![extension_handle];
453        self.objects
454            .insert(dictionary_handle, ObjectType::Dictionary(dictionary));
455
456        if let Some(ObjectType::Dictionary(extension)) = self.objects.get_mut(&extension_handle) {
457            if let Some((key, handle)) = extension.entries.iter_mut().find(|(key, _)| {
458                key.eq_ignore_ascii_case(LAYER_STATES_DICTIONARY)
459                    || key.eq_ignore_ascii_case("ACAD_LAYERSTATE")
460            }) {
461                *key = LAYER_STATES_DICTIONARY.to_string();
462                *handle = dictionary_handle;
463            } else {
464                extension
465                    .entries
466                    .push((LAYER_STATES_DICTIONARY.to_string(), dictionary_handle));
467            }
468        }
469        dictionary_handle
470    }
471
472    fn dictionary_entry(&self, dictionary_handle: Handle, name: &str) -> Option<(String, Handle)> {
473        let Some(ObjectType::Dictionary(dictionary)) = self.objects.get(&dictionary_handle) else {
474            return None;
475        };
476        dictionary
477            .entries
478            .iter()
479            .find(|(key, _)| key.eq_ignore_ascii_case(name))
480            .cloned()
481    }
482
483    fn decode_layer_state(&self, name: &str, xrecord: &XRecord) -> LayerState {
484        let first_layer = xrecord
485            .entries
486            .iter()
487            .position(|entry| entry.code == 330)
488            .unwrap_or(xrecord.entries.len());
489        let header = &xrecord.entries[..first_layer];
490        let mask = header
491            .iter()
492            .find(|entry| entry.code == 91)
493            .and_then(|entry| entry.value.as_i32())
494            .map(|value| LayerStateMask::from_bits(value as u32))
495            .unwrap_or(LayerStateMask::ALL);
496        let description = header
497            .iter()
498            .find(|entry| entry.code == 301)
499            .and_then(|entry| entry.value.as_string())
500            .unwrap_or_default()
501            .to_string();
502        let hidden = header
503            .iter()
504            .find(|entry| entry.code == 290)
505            .and_then(|entry| entry.value.as_bool())
506            .unwrap_or(false);
507        let current_layer = header
508            .iter()
509            .find(|entry| entry.code == 302)
510            .and_then(|entry| entry.value.as_string())
511            .unwrap_or_default()
512            .to_string();
513
514        let mut layers = Vec::new();
515        let mut start = first_layer;
516        while start < xrecord.entries.len() {
517            let end = xrecord.entries[start + 1..]
518                .iter()
519                .position(|entry| entry.code == 330)
520                .map(|offset| start + 1 + offset)
521                .unwrap_or(xrecord.entries.len());
522            let group = &xrecord.entries[start..end];
523            if let Some(layer) = self.decode_layer_state_layer(group) {
524                layers.push(layer);
525            }
526            start = end;
527        }
528
529        LayerState {
530            name: name.to_string(),
531            description,
532            mask,
533            hidden,
534            current_layer,
535            layers,
536        }
537    }
538
539    fn decode_layer_state_layer(&self, entries: &[XRecordEntry]) -> Option<LayerStateLayer> {
540        let layer_handle = entries
541            .first()
542            .filter(|entry| entry.code == 330)?
543            .value
544            .as_handle()?;
545        let flags = entry_i32(entries, 90).unwrap_or(0);
546        let color = entry_i32(entries, 92)
547            .or_else(|| entry_i32(entries, 420))
548            .map(Color::from_true_color_value)
549            .unwrap_or_else(|| Color::from_index(entry_i32(entries, 62).unwrap_or(7) as i16));
550        let (book_name, color_name) = entry_string(entries, 300)
551            .map(crate::io::dxf::split_color_book_name)
552            .unwrap_or((None, None));
553        let line_type_handle = entry_handle(entries, 331).unwrap_or(Handle::NULL);
554        let layer_name = self
555            .layers
556            .iter()
557            .find(|layer| layer.handle == layer_handle)
558            .map(|layer| layer.name.clone())
559            .unwrap_or_default();
560        let line_type = self
561            .line_types
562            .iter()
563            .find(|line_type| line_type.handle == line_type_handle)
564            .map(|line_type| line_type.name.clone())
565            .unwrap_or_default();
566
567        Some(LayerStateLayer {
568            layer_handle,
569            layer_name,
570            off: flags & 0x01 != 0,
571            frozen: flags & 0x02 != 0,
572            locked: flags & 0x04 != 0,
573            plottable: flags & 0x08 != 0,
574            new_viewport_frozen: flags & 0x10 != 0,
575            color,
576            color_name,
577            book_name,
578            line_type,
579            line_weight: LineWeight::from_value(entry_i32(entries, 370).unwrap_or(-3) as i16),
580            plot_style: entry_string(entries, 1).unwrap_or_default().to_string(),
581            transparency: entry_i32(entries, 440)
582                .and_then(|value| Transparency::from_layer_alpha_value(value as u32)),
583        })
584    }
585
586    fn encode_layer_state(&self, state: &LayerState) -> Vec<XRecordEntry> {
587        let mut entries = vec![
588            XRecordEntry::int32(91, state.mask.bits() as i32),
589            XRecordEntry::string(301, &state.description),
590            XRecordEntry::bool(290, state.hidden),
591            XRecordEntry::string(302, &state.current_layer),
592        ];
593
594        for layer in &state.layers {
595            let layer_handle = if layer.layer_handle.is_null() {
596                self.layers
597                    .get(&layer.layer_name)
598                    .map(|layer| layer.handle)
599                    .unwrap_or(Handle::NULL)
600            } else {
601                layer.layer_handle
602            };
603            if layer_handle.is_null() {
604                continue;
605            }
606
607            let mut flags = 0i32;
608            if layer.off {
609                flags |= 0x01;
610            }
611            if layer.frozen {
612                flags |= 0x02;
613            }
614            if layer.locked {
615                flags |= 0x04;
616            }
617            if layer.plottable {
618                flags |= 0x08;
619            }
620            if layer.new_viewport_frozen {
621                flags |= 0x10;
622            }
623
624            let line_type_handle = self
625                .line_types
626                .iter()
627                .find(|line_type| line_type.name.eq_ignore_ascii_case(&layer.line_type))
628                .map(|line_type| line_type.handle)
629                .or_else(|| {
630                    self.line_types
631                        .get("Continuous")
632                        .map(|line_type| line_type.handle)
633                })
634                .unwrap_or(Handle::NULL);
635            entries.push(XRecordEntry::handle(330, layer_handle));
636            entries.push(XRecordEntry::int32(90, flags));
637            entries.push(XRecordEntry::int16(62, layer.color.approximate_index()));
638            if let Some(true_color) = layer.color.to_true_color_value() {
639                entries.push(XRecordEntry::int32(
640                    92,
641                    (0xC200_0000u32 | true_color as u32) as i32,
642                ));
643            }
644            entries.push(XRecordEntry::int16(370, layer.line_weight.value()));
645            entries.push(XRecordEntry::handle(331, line_type_handle));
646            entries.push(XRecordEntry::string(1, &layer.plot_style));
647            if let Some(transparency) = layer.transparency {
648                entries.push(XRecordEntry::int32(440, transparency.to_dxf_value()));
649            }
650            if let Some(name) = crate::io::dxf::join_color_book_name(
651                layer.book_name.as_deref(),
652                layer.color_name.as_deref(),
653            ) {
654                entries.push(XRecordEntry::string(300, &name));
655            }
656        }
657        entries
658    }
659}
660
661fn entry_i32(entries: &[XRecordEntry], code: i32) -> Option<i32> {
662    entries
663        .iter()
664        .find(|entry| entry.code == code)
665        .and_then(|entry| entry.value.as_i32())
666}
667
668fn entry_handle(entries: &[XRecordEntry], code: i32) -> Option<Handle> {
669    entries
670        .iter()
671        .find(|entry| entry.code == code)
672        .and_then(|entry| entry.value.as_handle())
673}
674
675fn entry_string(entries: &[XRecordEntry], code: i32) -> Option<&str> {
676    entries
677        .iter()
678        .find(|entry| entry.code == code)
679        .and_then(|entry| entry.value.as_string())
680}