Skip to main content

ag_psd/
descriptor.rs

1/*
2File: crates/ag-psd/src/descriptor.rs
3
4Purpose:
5чтение и запись Photoshop Action Descriptor (типизированные поля дескрипторов).
6
7Source compatibility:
8- порт upstream-файла `test/ag-psd/src/descriptor.ts`.
9
10Этот модуль портирует *format-critical* ядро `descriptor.ts`: чтение/запись
11дескриптора, диспетчеризацию OSType, кодирование ключей (4-байтовая сигнатура
12или длино-префиксная ASCII-строка), таблицу единиц (`UntF`/`UnFl`) и
13version-обёртки (`readVersionAndDescriptor` / `writeVersionAndDescriptor`).
14
15Модель данных
16-------------
17TS-версия слабо типизирована: `readDescriptorStructure` возвращает «голый»
18объект, а `writeDescriptorStructure` восстанавливает OSType каждого поля из
19большой эвристической таблицы `getTypeByKey`/`fieldToType` по имени ключа.
20В Rust мы превращаем дескриптор в строго типизированное дерево: тип значения
21несёт сам enum [`DescriptorValue`], поэтому writer пишет байты прямо по варианту
22enum'а — это и точнее (никаких догадок по имени ключа), и полностью совпадает с
23байтами, которые читает reader. Таблицы вывода типа из `descriptor.ts`
24(`fieldToType`, `fieldToExtType`, `getTypeByKey`, ...) намеренно не нужны в этой
25модели и не воспроизводятся — см. отчёт о портировании.
26
27Порядок полей
28-------------
29Photoshop чувствителен к порядку полей дескриптора, поэтому [`Descriptor`]
30хранит поля как `Vec<(String, DescriptorValue)>` (insertion-ordered) — порядок
31вставки сохраняется ровно так, как при записи. `HashMap`/`BTreeMap` исказили бы
32порядок, поэтому они не подходят; тащить внешний `indexmap` ради этого не нужно.
33*/
34
35use crate::reader::{
36    read_ascii_string, read_bytes, read_float32, read_float64, read_int32, read_int32_le,
37    read_signature, read_uint32, read_uint8, read_unicode_string,
38    read_unicode_string_with_length_le, PsdReader, ReadError, ReadResult,
39};
40use crate::writer::{
41    write_bytes, write_float64, write_int32, write_int32_le, write_signature,
42    write_uint32, write_uint8, write_unicode_string, write_unicode_string_with_padding,
43    write_unicode_string_without_length_le, PsdWriter,
44};
45
46// ===========================================================================
47// Unit maps (зеркало unitsMap / unitsMapRev)
48// ===========================================================================
49
50/// Зеркало `unitsMap`: 4-байтовый код единицы измерения -> человекочитаемое имя.
51///
52/// Порядок записей сохранён как в `descriptor.ts` (для соответствия и читаемости).
53pub const UNITS_MAP: &[(&str, &str)] = &[
54    ("#Ang", "Angle"),
55    ("#Rsl", "Density"),
56    ("#Rlt", "Distance"),
57    ("#Nne", "None"),
58    ("#Prc", "Percent"),
59    ("#Pxl", "Pixels"),
60    ("#Mlm", "Millimeters"),
61    ("#Pnt", "Points"),
62    ("RrPi", "Picas"),
63    ("RrIn", "Inches"),
64    ("RrCm", "Centimeters"),
65];
66
67/// `unitsMap[code]` — код -> имя.
68pub fn units_name_from_code(code: &str) -> Option<&'static str> {
69    UNITS_MAP
70        .iter()
71        .find(|(c, _)| *c == code)
72        .map(|(_, name)| *name)
73}
74
75/// `unitsMapRev[name]` — имя -> код.
76pub fn units_code_from_name(name: &str) -> Option<&'static str> {
77    UNITS_MAP
78        .iter()
79        .find(|(_, n)| *n == name)
80        .map(|(code, _)| *code)
81}
82
83// ===========================================================================
84// Модель данных дескриптора
85// ===========================================================================
86
87/// Значение единицы измерения (`UntF` / `UnFl`).
88///
89/// `units` хранит человекочитаемое имя (как в TS `{ units, value }`), а
90/// `float32` отличает `UnFl` (одинарная точность) от `UntF` (двойная) на записи.
91#[derive(Debug, Clone, PartialEq)]
92pub struct UnitDoubleValue {
93    pub units: String,
94    pub value: f64,
95}
96
97/// Large integer (`comp`): low/high 32-битные половины (зеркало `{ low, high }`).
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct LargeInteger {
100    pub low: u32,
101    pub high: u32,
102}
103
104/// Имя+classID (зеркало `readClassStructure` -> `{ name, classID }`).
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct ClassStructure {
107    pub name: String,
108    pub class_id: String,
109}
110
111/// Элемент `ObAr` (object array): тип точки + значения (зеркало `{ type, values }`).
112#[derive(Debug, Clone, PartialEq)]
113pub struct ObjectArrayItem {
114    pub type_: String,
115    pub values: Vec<f64>,
116}
117
118/// Значение `Pth ` (file path): сигнатура + путь (зеркало `{ sig, path }`).
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct PathValue {
121    pub sig: String,
122    pub path: String,
123}
124
125/// Элемент Reference-структуры (`obj `).
126///
127/// `descriptor.ts` сворачивает все под-типы ссылки в плоский массив значений:
128/// 'prop'/'rele'/'name' дают строку, 'Enmr' — строку `"type.value"`,
129/// 'Idnt'/'indx' — число, 'Clss' — class-структуру. Здесь — типизированный enum.
130#[derive(Debug, Clone, PartialEq)]
131pub enum ReferenceItem {
132    /// 'prop' — Property: keyID (после прочтения class-структуры).
133    Property(String),
134    /// 'Clss' — Class.
135    Class(ClassStructure),
136    /// 'Enmr' — Enumerated reference, хранится как `"typeID.value"`.
137    Enumerated(String),
138    /// 'rele' — Offset (uint32) после class-структуры.
139    Offset(u32),
140    /// 'Idnt' — Identifier (int32).
141    Identifier(i32),
142    /// 'indx' — Index (int32).
143    Index(i32),
144    /// 'name' — Name (unicode string) после class-структуры.
145    Name(String),
146}
147
148/// Значение OSType-поля дескриптора (динамическое типизированное дерево).
149#[derive(Debug, Clone, PartialEq)]
150pub enum DescriptorValue {
151    /// 'obj ' — Reference.
152    Reference(Vec<ReferenceItem>),
153    /// 'Objc' / 'GlbO' — вложенный дескриптор.
154    Descriptor(Descriptor),
155    /// 'VlLs' — список.
156    List(Vec<DescriptorValue>),
157    /// 'doub' — double.
158    Double(f64),
159    /// 'UntF' (float64) / 'UnFl' (float32) — unit double.
160    UnitDouble(UnitDoubleValue),
161    /// 'TEXT' — unicode string.
162    Text(String),
163    /// 'enum' — `"type.value"`.
164    Enum(String),
165    /// 'long' — int32.
166    Integer(i32),
167    /// 'comp' — large integer.
168    LargeInteger(LargeInteger),
169    /// 'bool'.
170    Boolean(bool),
171    /// 'type' / 'GlbC' — class.
172    Class(ClassStructure),
173    /// 'alis' — alias (ASCII string).
174    Alias(String),
175    /// 'tdta' — raw data.
176    RawData(Vec<u8>),
177    /// 'ObAr' — object array.
178    ObjectArray(Vec<ObjectArrayItem>),
179    /// 'Pth ' — file path / alias.
180    Path(PathValue),
181}
182
183impl DescriptorValue {
184    /// 4-байтовый код OSType для данного варианта значения.
185    ///
186    /// `UnFl` неотличим от `UntF` по значению — оба несут [`UnitDoubleValue`];
187    /// мы выбираем 'UntF' по умолчанию (как и большинство полей PSD).
188    /// На записи это управляется явным [`write_os_type`] (см. `float32`-вариант ниже).
189    pub fn os_type(&self) -> &'static str {
190        match self {
191            DescriptorValue::Reference(_) => "obj ",
192            DescriptorValue::Descriptor(_) => "Objc",
193            DescriptorValue::List(_) => "VlLs",
194            DescriptorValue::Double(_) => "doub",
195            DescriptorValue::UnitDouble(_) => "UntF",
196            DescriptorValue::Text(_) => "TEXT",
197            DescriptorValue::Enum(_) => "enum",
198            DescriptorValue::Integer(_) => "long",
199            DescriptorValue::LargeInteger(_) => "comp",
200            DescriptorValue::Boolean(_) => "bool",
201            DescriptorValue::Class(_) => "type",
202            DescriptorValue::Alias(_) => "alis",
203            DescriptorValue::RawData(_) => "tdta",
204            DescriptorValue::ObjectArray(_) => "ObAr",
205            DescriptorValue::Path(_) => "Pth ",
206        }
207    }
208}
209
210/// Дескриптор: classID + name + упорядоченный список полей.
211///
212/// `items` — `Vec<(String, DescriptorValue)>`: insertion-ordered, чтобы при записи
213/// поля шли ровно в том порядке, в каком были добавлены (Photoshop это важно).
214#[derive(Debug, Clone, PartialEq, Default)]
215pub struct Descriptor {
216    pub name: String,
217    pub class_id: String,
218    pub items: Vec<(String, DescriptorValue)>,
219}
220
221impl Descriptor {
222    pub fn new(name: impl Into<String>, class_id: impl Into<String>) -> Descriptor {
223        Descriptor {
224            name: name.into(),
225            class_id: class_id.into(),
226            items: Vec::new(),
227        }
228    }
229
230    /// Добавляет поле в конец (сохраняя порядок вставки).
231    pub fn set(&mut self, key: impl Into<String>, value: DescriptorValue) -> &mut Self {
232        self.items.push((key.into(), value));
233        self
234    }
235
236    /// Первое значение по ключу (порядок не нарушается).
237    pub fn get(&self, key: &str) -> Option<&DescriptorValue> {
238        self.items.iter().find(|(k, _)| k == key).map(|(_, v)| v)
239    }
240}
241
242// ===========================================================================
243// Высокоуровневые хелперы descriptor.ts (color / units / percent-or-angle)
244// ---------------------------------------------------------------------------
245// Канонические копии, ранее продублированные в group-модулях additional_info
246// (effects_keys / vector_keys / adjustment_keys / misc_keys / text_keys /
247// smart_object_keys) и в abr.rs. Сигнатуры выверены по
248// `test/ag-psd/src/descriptor.ts`.
249// ===========================================================================
250
251use crate::psd::{Cmyk, Color, Frgb, Grayscale, Hsb, Lab, Rgb, Units, UnitsValue};
252
253/// `Units` enum -> человекочитаемое имя единицы.
254pub fn units_to_str(u: Units) -> &'static str {
255    match u {
256        Units::Pixels => "Pixels",
257        Units::Points => "Points",
258        Units::Picas => "Picas",
259        Units::Millimeters => "Millimeters",
260        Units::Centimeters => "Centimeters",
261        Units::Inches => "Inches",
262        Units::None => "None",
263        Units::Density => "Density",
264    }
265}
266
267/// Имя единицы -> `Units` enum (валидирует допустимые единицы).
268pub fn units_from_str(s: &str) -> ReadResult<Units> {
269    Ok(match s {
270        "Pixels" => Units::Pixels,
271        "Points" => Units::Points,
272        "Picas" => Units::Picas,
273        "Millimeters" => Units::Millimeters,
274        "Centimeters" => Units::Centimeters,
275        "Inches" => Units::Inches,
276        "None" => Units::None,
277        "Density" => Units::Density,
278        other => {
279            return Err(ReadError::StrictViolation(format!("Invalid units: {other}")));
280        }
281    })
282}
283
284/// `unitsAngle(value)` — UntF с units = Angle.
285pub fn units_angle(value: f64) -> DescriptorValue {
286    DescriptorValue::UnitDouble(UnitDoubleValue {
287        units: "Angle".to_string(),
288        value,
289    })
290}
291
292/// Зеркало `unitsValue(x | undefined, key)` — `None` => Pixels/0.
293pub fn units_value(x: Option<&UnitsValue>) -> DescriptorValue {
294    match x {
295        None => DescriptorValue::UnitDouble(UnitDoubleValue {
296            units: "Pixels".to_string(),
297            value: 0.0,
298        }),
299        Some(v) => DescriptorValue::UnitDouble(UnitDoubleValue {
300            units: units_to_str(v.units).to_string(),
301            value: v.value,
302        }),
303    }
304}
305
306/// Зеркало `parseUnits({units,value})` — валидирует допустимые единицы.
307pub fn parse_units(v: &DescriptorValue) -> ReadResult<UnitsValue> {
308    match v {
309        DescriptorValue::UnitDouble(u) => Ok(UnitsValue {
310            units: units_from_str(&u.units)?,
311            value: u.value,
312        }),
313        other => Err(ReadError::StrictViolation(format!(
314            "Invalid units value: {other:?}"
315        ))),
316    }
317}
318
319/// Зеркало `parseUnitsOrNumber(value, units='Pixels')`: UntF/UnFl ИЛИ число => Pixels.
320pub fn parse_units_or_number(v: &DescriptorValue) -> ReadResult<UnitsValue> {
321    match v {
322        DescriptorValue::UnitDouble(u) => Ok(UnitsValue {
323            units: units_from_str(&u.units)?,
324            value: u.value,
325        }),
326        DescriptorValue::Double(d) => Ok(UnitsValue {
327            units: Units::Pixels,
328            value: *d,
329        }),
330        DescriptorValue::Integer(i) => Ok(UnitsValue {
331            units: Units::Pixels,
332            value: *i as f64,
333        }),
334        other => Err(ReadError::StrictViolation(format!(
335            "Invalid units-or-number value: {other:?}"
336        ))),
337    }
338}
339
340/// Зеркало `parseAngle(x)` — units must be Angle; отсутствие => 0.
341pub fn parse_angle(v: &DescriptorValue) -> ReadResult<f64> {
342    match v {
343        DescriptorValue::UnitDouble(u) if u.units == "Angle" => Ok(u.value),
344        DescriptorValue::UnitDouble(u) => {
345            Err(ReadError::StrictViolation(format!("Invalid units: {}", u.units)))
346        }
347        _ => Ok(0.0),
348    }
349}
350
351/// Зеркало `parsePercent(x)` — value/100, units must be Percent; отсутствие => 1.
352pub fn parse_percent(v: &DescriptorValue) -> ReadResult<f64> {
353    match v {
354        DescriptorValue::UnitDouble(u) if u.units == "Percent" => Ok(u.value / 100.0),
355        DescriptorValue::UnitDouble(u) => {
356            Err(ReadError::StrictViolation(format!("Invalid units: {}", u.units)))
357        }
358        _ => Ok(1.0),
359    }
360}
361
362/// Зеркало `parsePercentOrAngle(x)` — Percent/100 или Angle/360; отсутствие => 1.
363pub fn parse_percent_or_angle(v: &DescriptorValue) -> ReadResult<f64> {
364    match v {
365        DescriptorValue::UnitDouble(u) if u.units == "Percent" => Ok(u.value / 100.0),
366        DescriptorValue::UnitDouble(u) if u.units == "Angle" => Ok(u.value / 360.0),
367        DescriptorValue::UnitDouble(u) => {
368            Err(ReadError::StrictViolation(format!("Invalid units: {}", u.units)))
369        }
370        _ => Ok(1.0),
371    }
372}
373
374/// Числовое значение поля дескриптора-цвета (double/integer; прочее => 0).
375fn color_double(d: &Descriptor, key: &str) -> f64 {
376    match d.get(key) {
377        Some(DescriptorValue::Double(v)) => *v,
378        Some(DescriptorValue::Integer(v)) => *v as f64,
379        _ => 0.0,
380    }
381}
382
383/// Зеркало `parseColor(DescriptorColor)` — по наличию ключей.
384pub fn parse_color(d: &Descriptor) -> ReadResult<Color> {
385    if let Some(h) = d.get("H   ") {
386        let h = parse_percent_or_angle(h)?;
387        Ok(Color::Hsb(Hsb {
388            h,
389            s: color_double(d, "Strt"),
390            b: color_double(d, "Brgh"),
391        }))
392    } else if d.get("Rd  ").is_some() {
393        Ok(Color::Rgb(Rgb {
394            r: color_double(d, "Rd  "),
395            g: color_double(d, "Grn "),
396            b: color_double(d, "Bl  "),
397        }))
398    } else if d.get("Cyn ").is_some() {
399        Ok(Color::Cmyk(Cmyk {
400            c: color_double(d, "Cyn "),
401            m: color_double(d, "Mgnt"),
402            y: color_double(d, "Ylw "),
403            k: color_double(d, "Blck"),
404        }))
405    } else if d.get("Gry ").is_some() {
406        Ok(Color::Grayscale(Grayscale {
407            k: color_double(d, "Gry "),
408        }))
409    } else if d.get("Lmnc").is_some() {
410        Ok(Color::Lab(Lab {
411            l: color_double(d, "Lmnc"),
412            a: color_double(d, "A   "),
413            b: color_double(d, "B   "),
414        }))
415    } else if d.get("redFloat").is_some() {
416        Ok(Color::Frgb(Frgb {
417            fr: color_double(d, "redFloat"),
418            fg: color_double(d, "greenFloat"),
419            fb: color_double(d, "blueFloat"),
420        }))
421    } else {
422        Err(ReadError::StrictViolation(
423            "Unsupported color descriptor".to_string(),
424        ))
425    }
426}
427
428/// Зеркало `serializeColor(Color | undefined)` — `None` => нулевой RGBC.
429pub fn serialize_color(color: Option<&Color>) -> Descriptor {
430    let mut d;
431    match color {
432        None => {
433            d = Descriptor::new("", "RGBC");
434            d.set("Rd  ", DescriptorValue::Double(0.0));
435            d.set("Grn ", DescriptorValue::Double(0.0));
436            d.set("Bl  ", DescriptorValue::Double(0.0));
437        }
438        Some(Color::Rgb(c)) => {
439            d = Descriptor::new("", "RGBC");
440            d.set("Rd  ", DescriptorValue::Double(c.r));
441            d.set("Grn ", DescriptorValue::Double(c.g));
442            d.set("Bl  ", DescriptorValue::Double(c.b));
443        }
444        Some(Color::Rgba(c)) => {
445            // upstream `'r' in color` matches RGBA too.
446            d = Descriptor::new("", "RGBC");
447            d.set("Rd  ", DescriptorValue::Double(c.r));
448            d.set("Grn ", DescriptorValue::Double(c.g));
449            d.set("Bl  ", DescriptorValue::Double(c.b));
450        }
451        Some(Color::Frgb(c)) => {
452            d = Descriptor::new("", "RGBC");
453            d.set("redFloat", DescriptorValue::Double(c.fr));
454            d.set("greenFloat", DescriptorValue::Double(c.fg));
455            d.set("blueFloat", DescriptorValue::Double(c.fb));
456        }
457        Some(Color::Hsb(c)) => {
458            d = Descriptor::new("", "HSBC");
459            d.set("H   ", units_angle(c.h * 360.0));
460            d.set("Strt", DescriptorValue::Double(c.s));
461            d.set("Brgh", DescriptorValue::Double(c.b));
462        }
463        Some(Color::Cmyk(c)) => {
464            d = Descriptor::new("", "CMYC");
465            d.set("Cyn ", DescriptorValue::Double(c.c));
466            d.set("Mgnt", DescriptorValue::Double(c.m));
467            d.set("Ylw ", DescriptorValue::Double(c.y));
468            d.set("Blck", DescriptorValue::Double(c.k));
469        }
470        Some(Color::Lab(c)) => {
471            d = Descriptor::new("", "LABC");
472            d.set("Lmnc", DescriptorValue::Double(c.l));
473            d.set("A   ", DescriptorValue::Double(c.a));
474            d.set("B   ", DescriptorValue::Double(c.b));
475        }
476        Some(Color::Grayscale(c)) => {
477            d = Descriptor::new("", "GRYC");
478            d.set("Gry ", DescriptorValue::Double(c.k));
479        }
480    }
481    d
482}
483
484// ===========================================================================
485// Кодирование ключей / class id (zero-padded signature или длино-префиксная строка)
486// ===========================================================================
487
488/// Зеркало `readAsciiStringOrClassId(reader)`:
489/// читает int32-длину, затем ASCII-строку `length || 4` байт.
490pub fn read_ascii_string_or_class_id(reader: &mut PsdReader) -> ReadResult<String> {
491    let length = read_int32(reader)?;
492    let len = if length == 0 { 4 } else { length as usize };
493    read_ascii_string(reader, len)
494}
495
496/// Зеркало `writeAsciiStringOrClassId(writer, value)`.
497///
498/// Если строка ровно из 4 символов и не входит в спец-исключения
499/// (`warp`/`time`/`hold`/`list`) — пишется как classId: int32(0) + 4-байтовая
500/// сигнатура. Иначе — int32(длина) + ASCII-байты (по `charCodeAt`).
501pub fn write_ascii_string_or_class_id(writer: &mut PsdWriter, value: &str) {
502    let char_count = value.chars().count();
503    if char_count == 4 && value != "warp" && value != "time" && value != "hold" && value != "list"
504    {
505        // classId
506        write_int32(writer, 0);
507        write_signature(writer, value);
508    } else {
509        // ascii string
510        write_int32(writer, char_count as i32);
511        for ch in value.chars() {
512            write_uint8(writer, (ch as u32) as u8);
513        }
514    }
515}
516
517// ===========================================================================
518// Class structure
519// ===========================================================================
520
521/// Зеркало `readClassStructure(reader)` — `{ name (unicode), classID }`.
522pub fn read_class_structure(reader: &mut PsdReader) -> ReadResult<ClassStructure> {
523    let name = read_unicode_string(reader)?;
524    let class_id = read_ascii_string_or_class_id(reader)?;
525    Ok(ClassStructure { name, class_id })
526}
527
528/// Зеркало `writeClassStructure(writer, name, classID)`.
529pub fn write_class_structure(writer: &mut PsdWriter, name: &str, class_id: &str) {
530    write_unicode_string(writer, name);
531    write_ascii_string_or_class_id(writer, class_id);
532}
533
534// ===========================================================================
535// Reference structure ('obj ')
536// ===========================================================================
537
538/// Зеркало `readReferenceStructure(reader)`.
539pub fn read_reference_structure(reader: &mut PsdReader) -> ReadResult<Vec<ReferenceItem>> {
540    let items_count = read_int32(reader)?;
541    let mut items = Vec::new();
542
543    for _ in 0..items_count {
544        let type_ = read_signature(reader)?;
545
546        match type_.as_str() {
547            "prop" => {
548                // Property
549                read_class_structure(reader)?;
550                let key_id = read_ascii_string_or_class_id(reader)?;
551                items.push(ReferenceItem::Property(key_id));
552            }
553            "Clss" => {
554                // Class
555                items.push(ReferenceItem::Class(read_class_structure(reader)?));
556            }
557            "Enmr" => {
558                // Enumerated Reference
559                read_class_structure(reader)?;
560                let type_id = read_ascii_string_or_class_id(reader)?;
561                let value = read_ascii_string_or_class_id(reader)?;
562                items.push(ReferenceItem::Enumerated(format!("{}.{}", type_id, value)));
563            }
564            "rele" => {
565                // Offset
566                read_class_structure(reader)?;
567                items.push(ReferenceItem::Offset(read_uint32(reader)?));
568            }
569            "Idnt" => {
570                // Identifier
571                items.push(ReferenceItem::Identifier(read_int32(reader)?));
572            }
573            "indx" => {
574                // Index
575                items.push(ReferenceItem::Index(read_int32(reader)?));
576            }
577            "name" => {
578                // Name
579                read_class_structure(reader)?;
580                items.push(ReferenceItem::Name(read_unicode_string(reader)?));
581            }
582            other => {
583                return Err(ReadError::StrictViolation(format!(
584                    "Invalid descriptor reference type: {}",
585                    other
586                )));
587            }
588        }
589    }
590
591    Ok(items)
592}
593
594/// Зеркало `writeReferenceStructure(writer, key, items)`.
595///
596/// Upstream поддерживает запись только для 'Enmr' и 'name' (остальные ветки
597/// закомментированы и бросают исключение). Здесь мы пишем по типизированному
598/// варианту: 'Enmr' и 'name' — как upstream; прочие варианты — паника (writer.rs
599/// панически реагирует на невалидный ввод).
600pub fn write_reference_structure(writer: &mut PsdWriter, items: &[ReferenceItem]) {
601    write_int32(writer, items.len() as i32);
602
603    for item in items {
604        match item {
605            ReferenceItem::Enumerated(value) => {
606                write_signature(writer, "Enmr");
607                let mut parts = value.splitn(2, '.');
608                let type_id = parts.next().unwrap_or("");
609                let enum_value = parts.next().unwrap_or("");
610                write_class_structure(writer, "\0", type_id);
611                write_ascii_string_or_class_id(writer, type_id);
612                write_ascii_string_or_class_id(writer, enum_value);
613            }
614            ReferenceItem::Name(value) => {
615                write_signature(writer, "name");
616                write_class_structure(writer, "\0", "Lyr ");
617                write_unicode_string(writer, &format!("{}\0", value));
618            }
619            other => {
620                panic!("Invalid descriptor reference type for writing: {:?}", other);
621            }
622        }
623    }
624}
625
626// ===========================================================================
627// OSType dispatch
628// ===========================================================================
629
630/// Зеркало `readOSType(reader, type, includeClass)`.
631pub fn read_os_type(reader: &mut PsdReader, type_: &str) -> ReadResult<DescriptorValue> {
632    match type_ {
633        "obj " => Ok(DescriptorValue::Reference(read_reference_structure(reader)?)),
634        "Objc" | "GlbO" => Ok(DescriptorValue::Descriptor(read_descriptor_structure(
635            reader,
636        )?)),
637        "VlLs" => {
638            let length = read_int32(reader)?;
639            let mut items = Vec::new();
640            for _ in 0..length {
641                let item_type = read_signature(reader)?;
642                items.push(read_os_type(reader, &item_type)?);
643            }
644            Ok(DescriptorValue::List(items))
645        }
646        "doub" => Ok(DescriptorValue::Double(read_float64(reader)?)),
647        "UntF" => {
648            let units_code = read_signature(reader)?;
649            let value = read_float64(reader)?;
650            let units = units_name_from_code(&units_code)
651                .ok_or_else(|| ReadError::StrictViolation(format!("Invalid units: {}", units_code)))?;
652            Ok(DescriptorValue::UnitDouble(UnitDoubleValue {
653                units: units.to_string(),
654                value,
655            }))
656        }
657        "UnFl" => {
658            let units_code = read_signature(reader)?;
659            let value = read_float32(reader)? as f64;
660            let units = units_name_from_code(&units_code)
661                .ok_or_else(|| ReadError::StrictViolation(format!("Invalid units: {}", units_code)))?;
662            Ok(DescriptorValue::UnitDouble(UnitDoubleValue {
663                units: units.to_string(),
664                value,
665            }))
666        }
667        "TEXT" => Ok(DescriptorValue::Text(read_unicode_string(reader)?)),
668        "enum" => {
669            let enum_type = read_ascii_string_or_class_id(reader)?;
670            let value = read_ascii_string_or_class_id(reader)?;
671            Ok(DescriptorValue::Enum(format!("{}.{}", enum_type, value)))
672        }
673        "long" => Ok(DescriptorValue::Integer(read_int32(reader)?)),
674        "comp" => {
675            let low = read_uint32(reader)?;
676            let high = read_uint32(reader)?;
677            Ok(DescriptorValue::LargeInteger(LargeInteger { low, high }))
678        }
679        "bool" => Ok(DescriptorValue::Boolean(read_uint8(reader)? != 0)),
680        "type" | "GlbC" => Ok(DescriptorValue::Class(read_class_structure(reader)?)),
681        "alis" => {
682            let length = read_int32(reader)?;
683            Ok(DescriptorValue::Alias(read_ascii_string(
684                reader,
685                length as usize,
686            )?))
687        }
688        "tdta" => {
689            let length = read_int32(reader)?;
690            Ok(DescriptorValue::RawData(read_bytes(reader, length as usize)?))
691        }
692        "ObAr" => {
693            let _version = read_int32(reader)?; // version: 16
694            let _name = read_unicode_string(reader)?; // name: ''
695            let _type = read_ascii_string_or_class_id(reader)?; // e.g. 'rationalPoint'
696            let length = read_int32(reader)?;
697            let mut items = Vec::new();
698
699            for _ in 0..length {
700                let type1 = read_ascii_string_or_class_id(reader)?; // Hrzn | Vrtc
701                let _unfl = read_signature(reader)?; // 'UnFl'
702                let _units = read_signature(reader)?; // units e.g. '#Pxl'
703                let values_count = read_int32(reader)?;
704                let mut values = Vec::new();
705                for _ in 0..values_count {
706                    values.push(read_float64(reader)?);
707                }
708                items.push(ObjectArrayItem {
709                    type_: type1,
710                    values,
711                });
712            }
713
714            Ok(DescriptorValue::ObjectArray(items))
715        }
716        "Pth " => {
717            let _length = read_int32(reader)?; // total size of all fields below
718            let sig = read_signature(reader)?;
719            let _path_size = read_int32_le(reader)?; // same as length
720            let chars_count = read_int32_le(reader)?;
721            let path = read_unicode_string_with_length_le(reader, chars_count as usize)?;
722            Ok(DescriptorValue::Path(PathValue { sig, path }))
723        }
724        other => Err(ReadError::StrictViolation(format!(
725            "Invalid TySh descriptor OSType: {} at {:x}",
726            other, reader.offset
727        ))),
728    }
729}
730
731/// Зеркало `ObArTypes` — для каких ключей какой sub-type объявить в `ObAr`.
732fn ob_ar_type_for_key(key: &str) -> Option<&'static str> {
733    match key {
734        "meshPoints" => Some("rationalPoint"),
735        "quiltSliceX" => Some("UntF"),
736        "quiltSliceY" => Some("UntF"),
737        _ => None,
738    }
739}
740
741/// Зеркало `writeOSType(writer, type, value, key, extType, root)`.
742///
743/// В отличие от TS, тип определяется вариантом [`DescriptorValue`], а не
744/// эвристикой по имени ключа; `key` нужен только для `ObAr` (выбор sub-type).
745pub fn write_os_type(writer: &mut PsdWriter, value: &DescriptorValue, key: &str) {
746    match value {
747        DescriptorValue::Reference(items) => {
748            write_reference_structure(writer, items);
749        }
750        DescriptorValue::Descriptor(desc) => {
751            write_descriptor_structure(writer, desc);
752        }
753        DescriptorValue::List(items) => {
754            write_int32(writer, items.len() as i32);
755            for item in items {
756                write_signature(writer, item.os_type());
757                write_os_type(writer, item, &format!("{}[]", key));
758            }
759        }
760        DescriptorValue::Double(v) => {
761            write_float64(writer, *v);
762        }
763        DescriptorValue::UnitDouble(u) => {
764            let code = units_code_from_name(&u.units)
765                .unwrap_or_else(|| panic!("Invalid units: {} in {}", u.units, key));
766            write_signature(writer, code);
767            write_float64(writer, u.value);
768        }
769        DescriptorValue::Text(v) => {
770            write_unicode_string_with_padding(writer, v);
771        }
772        DescriptorValue::Enum(v) => {
773            let mut parts = v.splitn(2, '.');
774            let type_ = parts.next().unwrap_or("");
775            let val = parts.next().unwrap_or("");
776            write_ascii_string_or_class_id(writer, type_);
777            write_ascii_string_or_class_id(writer, val);
778        }
779        DescriptorValue::Integer(v) => {
780            write_int32(writer, *v);
781        }
782        DescriptorValue::LargeInteger(li) => {
783            // `comp` запись закомментирована в upstream; воспроизводим формат
784            // (low, high как в reader): два uint32.
785            write_uint32(writer, li.low);
786            write_uint32(writer, li.high);
787        }
788        DescriptorValue::Boolean(v) => {
789            write_uint8(writer, if *v { 1 } else { 0 });
790        }
791        DescriptorValue::Class(c) => {
792            // 'type'/'GlbC' запись закомментирована в upstream; воспроизводим
793            // структуру class (name + classID).
794            write_class_structure(writer, &c.name, &c.class_id);
795        }
796        DescriptorValue::Alias(s) => {
797            // 'alis' запись закомментирована в upstream; воспроизводим reader-формат.
798            write_int32(writer, s.chars().count() as i32);
799            for ch in s.chars() {
800                write_uint8(writer, (ch as u32) as u8);
801            }
802        }
803        DescriptorValue::RawData(bytes) => {
804            write_int32(writer, bytes.len() as i32);
805            write_bytes(writer, Some(bytes));
806        }
807        DescriptorValue::ObjectArray(items) => {
808            write_int32(writer, 16); // version
809            write_unicode_string_with_padding(writer, ""); // name
810            let type_ = ob_ar_type_for_key(key)
811                .unwrap_or_else(|| panic!("Not implemented ObArType for: {}", key));
812            write_ascii_string_or_class_id(writer, type_);
813            write_int32(writer, items.len() as i32);
814
815            for item in items {
816                write_ascii_string_or_class_id(writer, &item.type_); // Hrzn | Vrtc
817                write_signature(writer, "UnFl");
818                write_signature(writer, "#Pxl");
819                write_int32(writer, item.values.len() as i32);
820                for v in &item.values {
821                    write_float64(writer, *v);
822                }
823            }
824        }
825        DescriptorValue::Path(p) => {
826            let length = 4 + 4 + 4 + p.path.chars().count() as i32 * 2;
827            write_int32(writer, length);
828            write_signature(writer, &p.sig);
829            write_int32_le(writer, length);
830            write_int32_le(writer, p.path.chars().count() as i32);
831            write_unicode_string_without_length_le(writer, &p.path);
832        }
833    }
834}
835
836// ===========================================================================
837// Descriptor structure
838// ===========================================================================
839
840/// Зеркало `readDescriptorStructure(reader, includeClass)`.
841///
842/// `name`/`classID` всегда сохраняются на [`Descriptor`] (что эквивалентно
843/// `includeClass=true` в upstream — в типизированной модели они всегда доступны).
844pub fn read_descriptor_structure(reader: &mut PsdReader) -> ReadResult<Descriptor> {
845    let class = read_class_structure(reader)?;
846    let mut desc = Descriptor {
847        name: class.name,
848        class_id: class.class_id,
849        items: Vec::new(),
850    };
851
852    let items_count = read_uint32(reader)?;
853    for _ in 0..items_count {
854        let key = read_ascii_string_or_class_id(reader)?;
855        let type_ = read_signature(reader)?;
856        let data = read_os_type(reader, &type_)?;
857        desc.items.push((key, data));
858    }
859
860    Ok(desc)
861}
862
863/// Зеркало `writeDescriptorStructure(writer, name, classId, value, root)`.
864///
865/// Эвристика вывода OSType по имени ключа (`getTypeByKey`/`fieldToType`) не нужна:
866/// тип несёт сам [`DescriptorValue`]. Поля пишутся в порядке вставки.
867pub fn write_descriptor_structure(writer: &mut PsdWriter, desc: &Descriptor) {
868    write_unicode_string_with_padding(writer, &desc.name);
869    write_ascii_string_or_class_id(writer, &desc.class_id);
870
871    write_uint32(writer, desc.items.len() as u32);
872
873    for (key, value) in &desc.items {
874        write_ascii_string_or_class_id(writer, key);
875        write_signature(writer, value.os_type());
876        write_os_type(writer, value, key);
877    }
878}
879
880// ===========================================================================
881// Top-level read/write (alias для совместимости имён)
882// ===========================================================================
883
884/// Зеркало `readDescriptor` (в TS отсутствует отдельная функция; алиас на
885/// `readDescriptorStructure`, как ожидают вызывающие).
886pub fn read_descriptor(reader: &mut PsdReader) -> ReadResult<Descriptor> {
887    read_descriptor_structure(reader)
888}
889
890/// Зеркало `writeDescriptor` (алиас на `writeDescriptorStructure`).
891pub fn write_descriptor(writer: &mut PsdWriter, desc: &Descriptor) {
892    write_descriptor_structure(writer, desc);
893}
894
895// ===========================================================================
896// Version-prefixed wrappers (TySh и др. вызывающие)
897// ===========================================================================
898
899/// Зеркало `readVersionAndDescriptor(reader, includeClass = false)`.
900///
901/// Версия обязана быть 16, иначе ошибка (upstream `throw`).
902pub fn read_version_and_descriptor(reader: &mut PsdReader) -> ReadResult<Descriptor> {
903    let version = read_uint32(reader)?;
904    if version != 16 {
905        return Err(ReadError::StrictViolation(format!(
906            "Invalid descriptor version: {}",
907            version
908        )));
909    }
910    read_descriptor_structure(reader)
911}
912
913/// Зеркало `writeVersionAndDescriptor(writer, name, classID, descriptor, root='')`.
914pub fn write_version_and_descriptor(writer: &mut PsdWriter, desc: &Descriptor) {
915    write_uint32(writer, 16); // version
916    write_descriptor_structure(writer, desc);
917}
918
919#[cfg(test)]
920mod tests {
921    use super::*;
922    use crate::writer::{create_writer, get_writer_buffer};
923
924    fn build_sample() -> Descriptor {
925        let mut nested = Descriptor::new("", "Pnt ");
926        nested.set("Hrzn", DescriptorValue::Double(12.5));
927        nested.set("Vrtc", DescriptorValue::Double(-7.0));
928
929        let list = DescriptorValue::List(vec![
930            DescriptorValue::Integer(1),
931            DescriptorValue::Integer(2),
932            DescriptorValue::Integer(3),
933        ]);
934
935        let mut desc = Descriptor::new("layer name", "TxLr");
936        // Намеренно «непривычный» порядок — должен быть сохранён ровно так.
937        desc.set("Txt ", DescriptorValue::Text("Héllo 𝕎orld".to_string()));
938        desc.set("dbl ", DescriptorValue::Double(3.5));
939        desc.set(
940            "Opct",
941            DescriptorValue::UnitDouble(UnitDoubleValue {
942                units: "Percent".to_string(),
943                value: 75.0,
944            }),
945        );
946        desc.set("Ornt", DescriptorValue::Enum("Ornt.Hrzn".to_string()));
947        desc.set("long", DescriptorValue::Integer(-42));
948        desc.set("bool", DescriptorValue::Boolean(true));
949        desc.set("Objc", DescriptorValue::Descriptor(nested));
950        desc.set("VlLs", list);
951        desc
952    }
953
954    #[test]
955    fn round_trip_descriptor() {
956        let desc = build_sample();
957
958        let mut writer = create_writer(256);
959        write_descriptor(&mut writer, &desc);
960        let bytes = get_writer_buffer(&writer);
961
962        let mut reader = PsdReader::new(&bytes, None, None);
963        let read = read_descriptor(&mut reader).expect("read");
964
965        assert_eq!(read.name, "layer name");
966        assert_eq!(read.class_id, "TxLr");
967        assert_eq!(read, desc);
968        // Курсор должен дойти ровно до конца.
969        assert_eq!(reader.offset, bytes.len());
970    }
971
972    #[test]
973    fn round_trip_version_and_descriptor() {
974        let desc = build_sample();
975
976        let mut writer = create_writer(256);
977        write_version_and_descriptor(&mut writer, &desc);
978        let bytes = get_writer_buffer(&writer);
979
980        let mut reader = PsdReader::new(&bytes, None, None);
981        let read = read_version_and_descriptor(&mut reader).expect("read");
982        assert_eq!(read, desc);
983    }
984
985    #[test]
986    fn field_order_preserved() {
987        let desc = build_sample();
988        let expected_keys: Vec<&str> = desc.items.iter().map(|(k, _)| k.as_str()).collect();
989
990        let mut writer = create_writer(256);
991        write_descriptor(&mut writer, &desc);
992        let bytes = get_writer_buffer(&writer);
993
994        let mut reader = PsdReader::new(&bytes, None, None);
995        let read = read_descriptor(&mut reader).expect("read");
996        let read_keys: Vec<&str> = read.items.iter().map(|(k, _)| k.as_str()).collect();
997
998        assert_eq!(read_keys, expected_keys);
999    }
1000
1001    #[test]
1002    fn unit_float_round_trip() {
1003        // 'UnFl' — единичный float32; читаем как float и проверяем единицу.
1004        let value = UnitDoubleValue {
1005            units: "Pixels".to_string(),
1006            value: 10.0,
1007        };
1008        let mut writer = create_writer(64);
1009        // Записываем как UntF (float64) — read_os_type("UntF") вернёт то же.
1010        write_signature(&mut writer, "#Pxl");
1011        write_float64(&mut writer, value.value);
1012        let bytes = get_writer_buffer(&writer);
1013        let mut reader = PsdReader::new(&bytes, None, None);
1014        let v = read_os_type(&mut reader, "UntF").expect("read untf");
1015        assert_eq!(v, DescriptorValue::UnitDouble(value));
1016    }
1017
1018    #[test]
1019    fn ascii_string_or_class_id_encoding() {
1020        // 4-символьный classId -> int32(0) + signature.
1021        let mut w1 = create_writer(32);
1022        write_ascii_string_or_class_id(&mut w1, "TxLr");
1023        let b1 = get_writer_buffer(&w1);
1024        assert_eq!(&b1[0..4], &[0, 0, 0, 0]);
1025        assert_eq!(&b1[4..8], b"TxLr");
1026
1027        // Исключение 'list' -> длино-префиксная строка, не classId.
1028        let mut w2 = create_writer(32);
1029        write_ascii_string_or_class_id(&mut w2, "list");
1030        let b2 = get_writer_buffer(&w2);
1031        assert_eq!(&b2[0..4], &[0, 0, 0, 4]);
1032        assert_eq!(&b2[4..8], b"list");
1033
1034        // round-trip обоих
1035        for s in ["TxLr", "list", "longerKey"] {
1036            let mut w = create_writer(32);
1037            write_ascii_string_or_class_id(&mut w, s);
1038            let b = get_writer_buffer(&w);
1039            let mut r = PsdReader::new(&b, None, None);
1040            assert_eq!(read_ascii_string_or_class_id(&mut r).unwrap(), s);
1041        }
1042    }
1043
1044    #[test]
1045    fn raw_data_and_large_integer_round_trip() {
1046        let mut desc = Descriptor::new("", "test");
1047        desc.set("data", DescriptorValue::RawData(vec![1, 2, 3, 4, 5]));
1048        desc.set(
1049            "big ",
1050            DescriptorValue::LargeInteger(LargeInteger {
1051                low: 0xDEAD_BEEF,
1052                high: 0x1234_5678,
1053            }),
1054        );
1055
1056        let mut writer = create_writer(64);
1057        write_descriptor(&mut writer, &desc);
1058        let bytes = get_writer_buffer(&writer);
1059        let mut reader = PsdReader::new(&bytes, None, None);
1060        let read = read_descriptor(&mut reader).expect("read");
1061        assert_eq!(read, desc);
1062    }
1063
1064    #[test]
1065    fn units_table_complete() {
1066        // Все 11 единиц round-trip код<->имя.
1067        assert_eq!(UNITS_MAP.len(), 11);
1068        for (code, name) in UNITS_MAP {
1069            assert_eq!(units_name_from_code(code), Some(*name));
1070            assert_eq!(units_code_from_name(name), Some(*code));
1071        }
1072    }
1073}