Skip to main content

ag_psd/
reader.rs

1/*
2File: crates/ag-psd/src/reader.rs
3
4Purpose:
5низкоуровневое чтение байтов из буфера PSD (курсор чтения, примитивы чтения чисел и строк).
6
7Source compatibility:
8- порт upstream-файла `test/ag-psd/src/psdReader.ts` (разбиение 1:1).
9
10Main responsibilities:
11- зеркалировать соответствующий upstream-модуль при портировании;
12- держать публичный контракт этого участка в одном месте.
13*/
14
15// PORT STATUS: primitives ported; document orchestration pending
16//
17// Ported: the low-level byte/string/section reader primitives that operate only
18// on the reader buffer/offset plus scalar/length args. Deferred (require the full
19// Psd/Layer document shape, descriptor/additionalInfo/imageResources handlers):
20//   readPsd, readLayerInfo, readLayerRecord, readLayerMaskData,
21//   readLayerBlendingRanges, readLayerChannelImageData, decodeLayerImageData,
22//   readData, readDataRaw/Zip/RLE, readGlobalLayerMaskInfo,
23//   readAdditionalLayerInfo, readImageData, readColor, readPattern,
24//   createImageDataBitDepth and the cmyk/indexed/grayscale pixel helpers.
25// See `// TODO: orchestration, later task` markers below.
26
27//! # Endianness
28//!
29//! PSD is **big-endian**. Upstream calls `DataView.getInt16/getUint16/getInt32/
30//! getUint32/getFloat32/getFloat64` with the `littleEndian` argument either
31//! omitted or explicitly `false` (e.g. `getInt16(off, false)`), which means
32//! big-endian. The few `*LE` variants pass `true`. This port reproduces that:
33//! all default readers use `from_be_bytes`, the `_le` variants use
34//! `from_le_bytes`.
35//!
36//! # Error strategy
37//!
38//! Upstream throws `Error` in a handful of places (`checkSignature`,
39//! `readSection` overflow, `warnOrThrow` when `strict`, the >100MB guard in
40//! `readBytes`). It also reads past the end of a slice in some "broken file"
41//! recovery paths. We model fallible operations as `Result<T, ReadError>` with
42//! a small crate-local [`ReadError`] enum, returned consistently from every
43//! primitive that can fail. This is preferred over panicking because callers
44//! (the future document orchestration) need to distinguish recoverable from
45//! fatal conditions, mirroring upstream's `strict`/`warnOrThrow` split.
46
47use crate::psd::ReadOptions;
48
49/// Ошибки низкоуровневого ридера (зеркало `throw new Error(...)` из upstream).
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum ReadError {
52    /// Чтение за пределами буфера (зеркало `Reading bytes exceeding buffer length`
53    /// в strict-режиме, и общая защита границ в Rust-порте).
54    UnexpectedEndOfBuffer,
55    /// Защита `Reading past end of file` (length > 100MB).
56    ReadingPastEndOfFile,
57    /// `checkSignature`: подпись не совпала ни с `a`, ни с `b`.
58    InvalidSignature { signature: String, offset: usize },
59    /// `readSection`: длина > 4GB при чтении 8-байтового размера.
60    SizeTooLarge,
61    /// `readSection`: секция выходит за пределы буфера.
62    SectionExceedsFileSize,
63    /// `warnOrThrow` в strict-режиме (`Exceeded section limits` / `Unread section data`).
64    StrictViolation(String),
65}
66
67impl std::fmt::Display for ReadError {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            ReadError::UnexpectedEndOfBuffer => write!(f, "Reading bytes exceeding buffer length"),
71            ReadError::ReadingPastEndOfFile => write!(f, "Reading past end of file"),
72            ReadError::InvalidSignature { signature, offset } => {
73                write!(f, "Invalid signature: '{}' at 0x{:x}", signature, offset)
74            }
75            ReadError::SizeTooLarge => write!(f, "Sizes larger than 4GB are not supported"),
76            ReadError::SectionExceedsFileSize => write!(f, "Section exceeds file size"),
77            ReadError::StrictViolation(msg) => write!(f, "{}", msg),
78        }
79    }
80}
81
82impl std::error::Error for ReadError {}
83
84/// Результат операций ридера.
85pub type ReadResult<T> = Result<T, ReadError>;
86
87/// Состояние низкоуровневого ридера (зеркало TS `interface PsdReader extends ReadOptions`).
88///
89/// ## Borrow vs owned
90///
91/// Буфер хранится как **заимствованный срез** `&'a [u8]`. Upstream держит
92/// `DataView` поверх существующего `ArrayBuffer` и читает строго вперёд по
93/// `offset`; данные он не модифицирует и владения ими не требует. Заимствованный
94/// срез даёт ту же zero-copy семантику (`readBytes` возвращает под-срез исходного
95/// буфера, как `new Uint8Array(buffer, start, length)` в upstream), без аллокаций
96/// и без лишнего клонирования. Поэтому `&'a [u8]` предпочтительнее `Vec<u8>`.
97///
98/// TS `DataView(buffer, offset, length)` сдвигает базу представления; здесь это
99/// учтено тем, что вызывающий передаёт уже подрезанный срез (как
100/// `createReader(buffer, offset, length)` создаёт view на под-диапазон). Поле
101/// `offset` — позиция курсора внутри `buffer`, ровно как `reader.offset`.
102#[derive(Debug)]
103pub struct PsdReader<'a> {
104    pub buffer: &'a [u8],
105    pub offset: usize,
106    // зеркало ReadOptions-полей, которые upstream подмешивает в reader.
107    pub strict: bool,
108    pub debug: bool,
109    pub large: bool,
110    pub global_alpha: bool,
111    pub options: ReadOptions,
112}
113
114impl<'a> PsdReader<'a> {
115    /// Зеркало `createReader(buffer, offset?, length?)`.
116    ///
117    /// В upstream `offset`/`length` задают окно `DataView`; здесь это выражается
118    /// под-срезом `buffer[offset..offset+length]`. Если `length` не задан — до
119    /// конца буфера. Курсор (`offset`-поле) всегда начинается с 0 относительно
120    /// окна, как в upstream (`offset: 0`).
121    pub fn new(buffer: &'a [u8], offset: Option<usize>, length: Option<usize>) -> PsdReader<'a> {
122        let start = offset.unwrap_or(0);
123        let end = match length {
124            Some(len) => start + len,
125            None => buffer.len(),
126        };
127        PsdReader {
128            buffer: &buffer[start..end],
129            offset: 0,
130            strict: false,
131            debug: false,
132            large: false,
133            global_alpha: false,
134            options: ReadOptions::default(),
135        }
136    }
137}
138
139/// Зеркало `warnOrThrow(reader, message)`.
140///
141/// В strict-режиме upstream бросает исключение — здесь возвращаем `Err`. Вне
142/// strict (с `debug`) — просто логирование, которое мы опускаем как поведение,
143/// не данные; возвращаем `Ok(())`.
144pub fn warn_or_throw(reader: &PsdReader, message: &str) -> ReadResult<()> {
145    if reader.strict {
146        return Err(ReadError::StrictViolation(message.to_string()));
147    }
148    // `if (reader.debug) reader.log(message);` — лог опускаем.
149    Ok(())
150}
151
152// ===========================================================================
153// Scalar readers (big-endian, кроме *_le)
154// ===========================================================================
155
156#[inline]
157fn ensure(reader: &PsdReader, len: usize) -> ReadResult<usize> {
158    let start = reader.offset;
159    if start + len > reader.buffer.len() {
160        return Err(ReadError::UnexpectedEndOfBuffer);
161    }
162    Ok(start)
163}
164
165pub fn read_uint8(reader: &mut PsdReader) -> ReadResult<u8> {
166    let start = ensure(reader, 1)?;
167    reader.offset += 1;
168    Ok(reader.buffer[start])
169}
170
171/// Зеркало `peekUint8` — читает без сдвига курсора.
172pub fn peek_uint8(reader: &PsdReader) -> ReadResult<u8> {
173    let start = ensure(reader, 1)?;
174    Ok(reader.buffer[start])
175}
176
177/// Upstream имеет `readInt8`? Нет отдельной функции, но задание просит её —
178/// реализуем через интерпретацию байта как знакового (DataView.getInt8).
179pub fn read_int8(reader: &mut PsdReader) -> ReadResult<i8> {
180    Ok(read_uint8(reader)? as i8)
181}
182
183pub fn read_int16(reader: &mut PsdReader) -> ReadResult<i16> {
184    let start = ensure(reader, 2)?;
185    reader.offset += 2;
186    Ok(i16::from_be_bytes([reader.buffer[start], reader.buffer[start + 1]]))
187}
188
189pub fn read_uint16(reader: &mut PsdReader) -> ReadResult<u16> {
190    let start = ensure(reader, 2)?;
191    reader.offset += 2;
192    Ok(u16::from_be_bytes([reader.buffer[start], reader.buffer[start + 1]]))
193}
194
195/// Зеркало `readUint16LE` (little-endian).
196pub fn read_uint16_le(reader: &mut PsdReader) -> ReadResult<u16> {
197    let start = ensure(reader, 2)?;
198    reader.offset += 2;
199    Ok(u16::from_le_bytes([reader.buffer[start], reader.buffer[start + 1]]))
200}
201
202pub fn read_int32(reader: &mut PsdReader) -> ReadResult<i32> {
203    let start = ensure(reader, 4)?;
204    reader.offset += 4;
205    Ok(i32::from_be_bytes([
206        reader.buffer[start],
207        reader.buffer[start + 1],
208        reader.buffer[start + 2],
209        reader.buffer[start + 3],
210    ]))
211}
212
213/// Зеркало `readInt32LE` (little-endian).
214pub fn read_int32_le(reader: &mut PsdReader) -> ReadResult<i32> {
215    let start = ensure(reader, 4)?;
216    reader.offset += 4;
217    Ok(i32::from_le_bytes([
218        reader.buffer[start],
219        reader.buffer[start + 1],
220        reader.buffer[start + 2],
221        reader.buffer[start + 3],
222    ]))
223}
224
225pub fn read_uint32(reader: &mut PsdReader) -> ReadResult<u32> {
226    let start = ensure(reader, 4)?;
227    reader.offset += 4;
228    Ok(u32::from_be_bytes([
229        reader.buffer[start],
230        reader.buffer[start + 1],
231        reader.buffer[start + 2],
232        reader.buffer[start + 3],
233    ]))
234}
235
236pub fn read_float32(reader: &mut PsdReader) -> ReadResult<f32> {
237    let start = ensure(reader, 4)?;
238    reader.offset += 4;
239    Ok(f32::from_be_bytes([
240        reader.buffer[start],
241        reader.buffer[start + 1],
242        reader.buffer[start + 2],
243        reader.buffer[start + 3],
244    ]))
245}
246
247pub fn read_float64(reader: &mut PsdReader) -> ReadResult<f64> {
248    let start = ensure(reader, 8)?;
249    reader.offset += 8;
250    Ok(f64::from_be_bytes([
251        reader.buffer[start],
252        reader.buffer[start + 1],
253        reader.buffer[start + 2],
254        reader.buffer[start + 3],
255        reader.buffer[start + 4],
256        reader.buffer[start + 5],
257        reader.buffer[start + 6],
258        reader.buffer[start + 7],
259    ]))
260}
261
262/// Зеркало `readFixedPoint32` — 32-битное число с фиксированной точкой 16.16.
263pub fn read_fixed_point32(reader: &mut PsdReader) -> ReadResult<f64> {
264    Ok(read_int32(reader)? as f64 / (1i64 << 16) as f64)
265}
266
267/// Зеркало `readFixedPointPath32` — 32-битное число с фиксированной точкой 8.24.
268pub fn read_fixed_point_path32(reader: &mut PsdReader) -> ReadResult<f64> {
269    Ok(read_int32(reader)? as f64 / (1i64 << 24) as f64)
270}
271
272/// Зеркало `readBytes(reader, length)`.
273///
274/// Upstream при выходе за конец буфера выдаёт предупреждение (или бросает в
275/// strict), затем возвращает нулевой буфер нужной длины, частично заполненный
276/// доступными байтами (фикс для битых PSD). Защита: length > 100MB → throw.
277///
278/// Возвращает `Vec<u8>`, а не срез: в обычном случае это `buffer[start..start+len]`
279/// (как zero-copy под-Uint8Array в upstream), но ветка восстановления требует
280/// собственного буфера, поэтому для единообразия сигнатуры возвращаем владеющий
281/// `Vec`. Для zero-copy подреза есть отдельный [`read_bytes_slice`].
282pub fn read_bytes(reader: &mut PsdReader, length: usize) -> ReadResult<Vec<u8>> {
283    let start = reader.offset;
284    reader.offset += length;
285
286    if start + length > reader.buffer.len() {
287        // фикс для битых PSD, где не хватает части файла в конце.
288        warn_or_throw(reader, "Reading bytes exceeding buffer length")?;
289        if length > 100 * 1024 * 1024 {
290            return Err(ReadError::ReadingPastEndOfFile);
291        }
292        let mut result = vec![0u8; length];
293        let avail = reader.buffer.len().saturating_sub(start);
294        let len = length.min(avail);
295        if len > 0 {
296            result[..len].copy_from_slice(&reader.buffer[start..start + len]);
297        }
298        Ok(result)
299    } else {
300        Ok(reader.buffer[start..start + length].to_vec())
301    }
302}
303
304/// Zero-copy вариант чтения байтов: возвращает под-срез исходного буфера.
305///
306/// Эквивалент успешной (не-восстановительной) ветки upstream'а
307/// `new Uint8Array(reader.view.buffer, start, length)`. Ошибается, если выходит
308/// за пределы буфера (восстановительной ветки тут нет — она требует аллокации).
309pub fn read_bytes_slice<'a>(reader: &mut PsdReader<'a>, length: usize) -> ReadResult<&'a [u8]> {
310    let start = ensure(reader, length)?;
311    reader.offset += length;
312    Ok(&reader.buffer[start..start + length])
313}
314
315/// Зеркало `skipBytes(reader, count)`.
316pub fn skip_bytes(reader: &mut PsdReader, count: usize) {
317    reader.offset += count;
318}
319
320// ===========================================================================
321// String readers
322// ===========================================================================
323
324/// Зеркало приватной `readShortString(reader, length)`.
325///
326/// Upstream строит строку через `String.fromCharCode(byte)` для каждого байта —
327/// то есть **каждый байт 0..=255 становится UTF-16 code unit'ом** (Latin-1-подобно),
328/// это НЕ UTF-8-декодирование. Воспроизводим точно: каждый байт → `char`.
329pub fn read_short_string(reader: &mut PsdReader, length: usize) -> ReadResult<String> {
330    let buffer = read_bytes(reader, length)?;
331    let mut result = String::with_capacity(buffer.len());
332    for &b in &buffer {
333        result.push(b as char); // char::from(u8) == fromCharCode для 0..=255
334    }
335    Ok(result)
336}
337
338/// Зеркало `readAsciiString(reader, length)`.
339pub fn read_ascii_string(reader: &mut PsdReader, length: usize) -> ReadResult<String> {
340    let mut result = String::with_capacity(length);
341    for _ in 0..length {
342        result.push(read_uint8(reader)? as char);
343    }
344    Ok(result)
345}
346
347/// Зеркало `readSignature(reader)` — 4-байтовая подпись.
348pub fn read_signature(reader: &mut PsdReader) -> ReadResult<String> {
349    read_short_string(reader, 4)
350}
351
352/// Зеркало `validSignatureAt(reader, offset)` — `8BIM`/`8B64` по абсолютному offset.
353pub fn valid_signature_at(reader: &PsdReader, offset: usize) -> bool {
354    if offset + 4 > reader.buffer.len() {
355        return false;
356    }
357    let sig = &reader.buffer[offset..offset + 4];
358    sig == b"8BIM" || sig == b"8B64"
359}
360
361/// Зеркало `readPascalString(reader, padTo)`.
362///
363/// Layout: 1 байт длины, затем `length` байт текста, затем padding так, чтобы
364/// `(length + 1)` (счёт включает байт длины) был кратен `padTo`.
365pub fn read_pascal_string(reader: &mut PsdReader, pad_to: usize) -> ReadResult<String> {
366    let mut length = read_uint8(reader)? as usize;
367    let text = if length != 0 {
368        read_short_string(reader, length)?
369    } else {
370        String::new()
371    };
372
373    // `while (++length % padTo) reader.offset++;`
374    loop {
375        length += 1;
376        if length % pad_to == 0 {
377            break;
378        }
379        reader.offset += 1;
380    }
381
382    Ok(text)
383}
384
385/// Зеркало `readUnicodeString(reader)` — uint32 длина (в code unit'ах), затем строка.
386pub fn read_unicode_string(reader: &mut PsdReader) -> ReadResult<String> {
387    let length = read_uint32(reader)? as usize;
388    read_unicode_string_with_length(reader, length)
389}
390
391/// Зеркало `readUnicodeStringWithLength(reader, length)` (big-endian uint16 code units).
392///
393/// Каждый code unit читается как `readUint16` и добавляется через
394/// `String.fromCharCode`; финальный `\0` (значение 0 на последней позиции)
395/// отбрасывается. См. [`push_code_unit`] о суррогатах.
396pub fn read_unicode_string_with_length(
397    reader: &mut PsdReader,
398    length: usize,
399) -> ReadResult<String> {
400    let mut units: Vec<u16> = Vec::with_capacity(length);
401    let mut remaining = length;
402    while remaining > 0 {
403        remaining -= 1;
404        let value = read_uint16(reader)?;
405        // `if (value || length > 0)` — убираем хвостовой \0 (последняя итерация).
406        if value != 0 || remaining > 0 {
407            units.push(value);
408        }
409    }
410    Ok(utf16_units_to_string(&units))
411}
412
413/// Зеркало `readUnicodeStringWithLengthLE` (little-endian uint16 code units).
414pub fn read_unicode_string_with_length_le(
415    reader: &mut PsdReader,
416    length: usize,
417) -> ReadResult<String> {
418    let mut units: Vec<u16> = Vec::with_capacity(length);
419    let mut remaining = length;
420    while remaining > 0 {
421        remaining -= 1;
422        let value = read_uint16_le(reader)?;
423        if value != 0 || remaining > 0 {
424            units.push(value);
425        }
426    }
427    Ok(utf16_units_to_string(&units))
428}
429
430/// Сборка строки из UTF-16 code unit'ов.
431///
432/// Upstream аккумулирует JS-строку напрямую из `fromCharCode(unit)`, что
433/// допускает одиночные суррогаты. Rust `String` хранит только валидные scalar
434/// values, поэтому для битых/одиночных суррогатов используем
435/// `decode_utf16` с заменой на U+FFFD — для всех корректных PSD-строк результат
436/// идентичен upstream'у.
437fn utf16_units_to_string(units: &[u16]) -> String {
438    char::decode_utf16(units.iter().copied())
439        .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
440        .collect()
441}
442
443/// Зеркало `checkSignature(reader, a, b?)`.
444///
445/// Читает 4-байтовую подпись; если она не равна ни `a`, ни (опционально) `b` —
446/// возвращает `Err(InvalidSignature)` (upstream `throw`).
447pub fn check_signature(reader: &mut PsdReader, a: &str, b: Option<&str>) -> ReadResult<()> {
448    let offset = reader.offset;
449    let signature = read_signature(reader)?;
450
451    if signature != a && Some(signature.as_str()) != b {
452        return Err(ReadError::InvalidSignature { signature, offset });
453    }
454    Ok(())
455}
456
457// ===========================================================================
458// Section helper
459// ===========================================================================
460
461/// Зеркало `readSection<T>(reader, round, func, skipEmpty = true, eightBytes = false)`.
462///
463/// Читает length-prefixed секцию, вызывает `func` с замыканием `left()`
464/// (сколько байт осталось до конца секции), затем выравнивает курсор на конец
465/// секции, округлённый так, чтобы `length` стал кратен `round`.
466///
467/// Логика округления воспроизведена ровно: `while (length % round) { length++; end++; }`.
468///
469/// `func` принимает `&mut PsdReader` и `&dyn Fn(&PsdReader) -> usize` (вычисление
470/// `left()`). Поскольку Rust не даёт замыканию захватить `reader`, который
471/// одновременно передаётся мутабельно в `func`, `left` принимает текущий ридер
472/// явным аргументом — это эквивалент upstream'а, где `left` читает `reader.offset`.
473pub fn read_section<T, F>(
474    reader: &mut PsdReader,
475    round: usize,
476    func: F,
477    skip_empty: bool,
478    eight_bytes: bool,
479) -> ReadResult<Option<T>>
480where
481    F: FnOnce(&mut PsdReader, &dyn Fn(&PsdReader) -> usize) -> ReadResult<T>,
482{
483    let mut length = read_uint32(reader)? as usize;
484
485    if eight_bytes {
486        if length != 0 {
487            return Err(ReadError::SizeTooLarge);
488        }
489        length = read_uint32(reader)? as usize;
490    }
491
492    // `if (length <= 0 && skipEmpty) return undefined;` (length unsigned → == 0)
493    if length == 0 && skip_empty {
494        return Ok(None);
495    }
496
497    let mut end = reader.offset + length;
498    if end > reader.buffer.len() {
499        return Err(ReadError::SectionExceedsFileSize);
500    }
501
502    let left = move |r: &PsdReader| end_minus_offset(end, r);
503    let result = func(reader, &left)?;
504
505    if reader.offset != end {
506        if reader.offset > end {
507            warn_or_throw(reader, "Exceeded section limits")?;
508        } else {
509            warn_or_throw(reader, "Unread section data")?;
510        }
511    }
512
513    // `while (length % round) { length++; end++; }`
514    while length % round != 0 {
515        length += 1;
516        end += 1;
517    }
518
519    reader.offset = end;
520
521    Ok(Some(result))
522}
523
524#[inline]
525fn end_minus_offset(end: usize, reader: &PsdReader) -> usize {
526    end.saturating_sub(reader.offset)
527}
528
529/// Хелпер `peekUint32` — задача упоминает его наличие; в upstream отдельной
530/// функции нет, но peek-семантика (чтение без сдвига курсора) полезна и
531/// согласуется с `peekUint8`. Big-endian.
532pub fn peek_uint32(reader: &PsdReader) -> ReadResult<u32> {
533    let start = ensure(reader, 4)?;
534    Ok(u32::from_be_bytes([
535        reader.buffer[start],
536        reader.buffer[start + 1],
537        reader.buffer[start + 2],
538        reader.buffer[start + 3],
539    ]))
540}
541
542// ===========================================================================
543// Document orchestration (port of psdReader.ts readPsd & friends)
544// ===========================================================================
545
546use crate::additional_info::{read_additional_info_key, ReadCtx};
547use crate::helpers::{
548    create_image_data, decode_bitmap, image_data_to_canvas, offset_for_channel,
549    to_blend_mode, ColorSpace, LayerMaskFlags, MaskParams,
550};
551use crate::image_resources::read_image_resource;
552use crate::psd::{
553    Color, ColorMode, Compression, GlobalLayerMaskInfo, ImageResources, Layer, LayerAdditionalInfo,
554    LayerMaskData, LayerRawData, LayerRawDataChannel, PatternInfo, PixelData, Cmyk, Grayscale, Hsb,
555    Lab, PatternBounds, Rgb, ChannelId, SectionDividerType,
556};
557
558/// Internal per-channel `{ id, length }` (mirror of TS `ChannelInfo`).
559#[derive(Debug, Clone, Copy)]
560struct ChannelInfo {
561    id: i16,
562    length: usize,
563}
564
565/// Mirror `supportedColorModes`.
566fn is_supported_color_mode(mode: u16) -> bool {
567    matches!(mode, 0 | 1 | 3 | 2) // Bitmap, Grayscale, RGB, Indexed
568}
569
570fn color_mode_from_u16(mode: u16) -> Option<ColorMode> {
571    Some(match mode {
572        0 => ColorMode::Bitmap,
573        1 => ColorMode::Grayscale,
574        2 => ColorMode::Indexed,
575        3 => ColorMode::Rgb,
576        4 => ColorMode::Cmyk,
577        7 => ColorMode::Multichannel,
578        8 => ColorMode::Duotone,
579        9 => ColorMode::Lab,
580        _ => return None,
581    })
582}
583
584fn channel_id_from_i16(id: i16) -> ChannelId {
585    match id {
586        0 => ChannelId::Color0,
587        1 => ChannelId::Color1,
588        2 => ChannelId::Color2,
589        3 => ChannelId::Color3,
590        -2 => ChannelId::UserMask,
591        -3 => ChannelId::RealUserMask,
592        // -1 transparency, and any unknown extra color channels (>3): treat as
593        // transparency-like (offset_for_channel guards what actually lands).
594        _ => ChannelId::Transparency,
595    }
596}
597
598/// Pixel storage backing a `PixelData` during decode, tracking bit depth so the
599/// channel codecs can write at the correct stride.
600///
601/// Upstream uses `Uint8ClampedArray` / `Uint16Array` / `Float32Array` views.
602/// Here we keep a `Vec<u8>` of RGBA8 always (PixelData is RGBA8 in this port);
603/// 16/32-bit source samples are down-converted to 8-bit on store so the public
604/// `PixelData` stays RGBA8 (matching how this crate models pixels).
605pub struct DecodeTarget {
606    pub width: usize,
607    pub height: usize,
608    /// RGBA8 (or `channels`-wide) byte buffer.
609    pub data: Vec<u8>,
610    pub channels: usize,
611}
612
613impl DecodeTarget {
614    pub fn rgba(width: usize, height: usize) -> DecodeTarget {
615        DecodeTarget { width, height, data: vec![0u8; width * height * 4], channels: 4 }
616    }
617    pub fn wide(width: usize, height: usize, channels: usize) -> DecodeTarget {
618        DecodeTarget { width, height, data: vec![0u8; width * height * channels], channels }
619    }
620    pub fn into_pixel_data(self) -> PixelData {
621        PixelData { width: self.width as u32, height: self.height as u32, data: self.data }
622    }
623}
624
625// ---------------------------------------------------------------------------
626// readPsd
627// ---------------------------------------------------------------------------
628
629/// High-level entry point. Mirror of upstream `readPsd(reader, readOptions)`,
630/// but takes a byte slice + options and builds a [`PsdReader`] internally.
631pub fn read_psd(buffer: &[u8], options: &ReadOptions) -> ReadResult<crate::psd::Psd> {
632    let mut reader = PsdReader::new(buffer, None, None);
633    reader.options = options.clone();
634    reader.strict = options.strict.unwrap_or(false);
635    reader.debug = options.debug.unwrap_or(false);
636    read_psd_from_reader(&mut reader)
637}
638
639/// Mirror of upstream `readPsd` operating on an existing reader (options must
640/// already be set on the reader, as upstream does via `Object.assign`).
641pub fn read_psd_from_reader(reader: &mut PsdReader) -> ReadResult<crate::psd::Psd> {
642    // header
643    check_signature(reader, "8BPS", None)?;
644    let version = read_uint16(reader)?;
645    if version != 1 && version != 2 {
646        return Err(ReadError::StrictViolation(format!(
647            "Invalid PSD file version: {}",
648            version
649        )));
650    }
651
652    skip_bytes(reader, 6);
653    let channels = read_uint16(reader)?;
654    let height = read_uint32(reader)?;
655    let width = read_uint32(reader)?;
656    let bits_per_channel = read_uint16(reader)?;
657    let color_mode_raw = read_uint16(reader)?;
658    let max_size: u32 = if version == 1 { 30000 } else { 300000 };
659
660    if width > max_size || height > max_size {
661        return Err(ReadError::StrictViolation(format!(
662            "Invalid size: {}x{}",
663            width, height
664        )));
665    }
666    if channels > 16 {
667        return Err(ReadError::StrictViolation(format!(
668            "Invalid channel count: {}",
669            channels
670        )));
671    }
672    if ![1, 8, 16, 32].contains(&bits_per_channel) {
673        return Err(ReadError::StrictViolation(format!(
674            "Invalid bitsPerChannel: {}",
675            bits_per_channel
676        )));
677    }
678    if !is_supported_color_mode(color_mode_raw) {
679        return Err(ReadError::StrictViolation(format!(
680            "Color mode not supported: {}",
681            color_mode_raw
682        )));
683    }
684
685    let color_mode = color_mode_from_u16(color_mode_raw);
686
687    let mut psd = crate::psd::Psd {
688        width: width as f64,
689        height: height as f64,
690        channels: Some(channels as f64),
691        bits_per_channel: Some(bits_per_channel as f64),
692        color_mode,
693        ..Default::default()
694    };
695
696    reader.large = version == 2;
697    reader.global_alpha = false;
698
699    // color mode data
700    let palette = read_section(
701        reader,
702        1,
703        |reader, left| {
704            if left(reader) == 0 {
705                return Ok(None);
706            }
707            let mut palette: Option<Vec<Rgb>> = None;
708            if color_mode == Some(ColorMode::Indexed) {
709                if left(reader) != 768 {
710                    return Err(ReadError::StrictViolation(
711                        "Invalid color palette size".to_string(),
712                    ));
713                }
714                let mut pal: Vec<Rgb> = Vec::with_capacity(256);
715                for _ in 0..256 {
716                    pal.push(Rgb { r: read_uint8(reader)? as f64, g: 0.0, b: 0.0 });
717                }
718                for i in 0..256 {
719                    pal[i].g = read_uint8(reader)? as f64;
720                }
721                for i in 0..256 {
722                    pal[i].b = read_uint8(reader)? as f64;
723                }
724                palette = Some(pal);
725            }
726            skip_bytes(reader, left(reader));
727            Ok(palette)
728        },
729        true,
730        false,
731    )?;
732    if let Some(Some(p)) = palette {
733        psd.palette = Some(p);
734    }
735
736    // image resources
737    let mut image_resources = ImageResources::default();
738    read_section(
739        reader,
740        1,
741        |reader, left| {
742            while left(reader) > 0 {
743                realign_with_signature(reader, is_valid_image_resource_signature)?;
744                let id = read_uint16(reader)?;
745                read_pascal_string(reader, 2)?; // name
746
747                read_section(
748                    reader,
749                    2,
750                    |reader, left| {
751                        let skip = id == 1036 && reader.options.skip_thumbnail == Some(true);
752                        let throw_for_missing =
753                            reader.options.throw_for_missing_features == Some(true);
754                        let block_len = left(reader);
755                        if !skip {
756                            match read_image_resource(id, reader, &mut image_resources, block_len) {
757                                Ok(()) => {}
758                                Err(e) => {
759                                    if throw_for_missing {
760                                        return Err(e);
761                                    }
762                                    skip_bytes(reader, left(reader));
763                                }
764                            }
765                        } else {
766                            skip_bytes(reader, left(reader));
767                        }
768                        Ok(())
769                    },
770                    false,
771                    false,
772                )?;
773            }
774            Ok(())
775        },
776        true,
777        false,
778    )?;
779    psd.image_resources = Some(image_resources);
780
781    // layer and mask info
782    read_section(
783        reader,
784        1,
785        |reader, left| {
786            read_section(
787                reader,
788                2,
789                |reader, left| {
790                    read_layer_info(reader, &mut psd)?;
791                    skip_bytes(reader, left(reader));
792                    Ok(())
793                },
794                true,
795                reader.large,
796            )?;
797
798            // SAI does not include this section
799            if left(reader) > 0 {
800                if let Some(info) = read_global_layer_mask_info(reader)? {
801                    psd.global_layer_mask_info = Some(info);
802                }
803            } else {
804                skip_bytes(reader, left(reader));
805            }
806
807            while left(reader) > 0 {
808                // sometimes there are empty bytes here
809                while left(reader) > 0 && peek_uint8(reader)? == 0 {
810                    skip_bytes(reader, 1);
811                }
812
813                if left(reader) >= 12 {
814                    // additional layer info applied to the whole document.
815                    let mut info = std::mem::take(&mut psd.additional_info);
816                    read_additional_layer_info(reader, &mut info)?;
817                    psd.additional_info = info;
818                } else {
819                    skip_bytes(reader, left(reader));
820                    break;
821                }
822            }
823            Ok(())
824        },
825        true,
826        reader.large,
827    )?;
828
829    let has_children = psd.children.as_ref().map_or(false, |c| !c.is_empty());
830    let skip_layer = reader.options.skip_layer_image_data == Some(true);
831    let skip_composite =
832        reader.options.skip_composite_image_data == Some(true) && (skip_layer || has_children);
833
834    if !skip_composite {
835        read_image_data(reader, &mut psd)?;
836    }
837
838    Ok(psd)
839}
840
841fn is_valid_image_resource_signature(sig: &str) -> bool {
842    sig == "8BIM" || sig == "MeSa" || sig == "AgHg" || sig == "PHUT" || sig == "DCSR"
843}
844
845// ---------------------------------------------------------------------------
846// readLayerInfo
847// ---------------------------------------------------------------------------
848
849fn read_layer_info(reader: &mut PsdReader, psd: &mut crate::psd::Psd) -> ReadResult<()> {
850    let mut layer_count = read_int16(reader)? as i32;
851
852    if layer_count < 0 {
853        reader.global_alpha = true;
854        layer_count = -layer_count;
855    }
856    let layer_count = layer_count as usize;
857
858    let mut layers: Vec<Layer> = Vec::with_capacity(layer_count);
859    let mut layer_channels: Vec<Vec<ChannelInfo>> = Vec::with_capacity(layer_count);
860
861    for _ in 0..layer_count {
862        let (layer, channels) = read_layer_record(reader, psd)?;
863        layers.push(layer);
864        layer_channels.push(channels);
865    }
866
867    for i in 0..layer_count {
868        read_layer_channel_image_data(reader, psd, &mut layers[i], &layer_channels[i])?;
869    }
870
871    if psd.children.is_none() {
872        psd.children = Some(Vec::new());
873    }
874
875    // Build the tree. We mirror upstream's stack-based unshift algorithm, but
876    // since Rust ownership makes a stack of mutable references hard, we collect
877    // into a nesting structure by tracking a path of indices.
878    build_layer_tree(psd, layers);
879
880    Ok(())
881}
882
883/// Mirror of the upstream stack/unshift folder-nesting algorithm.
884fn build_layer_tree(psd: &mut crate::psd::Psd, mut layers: Vec<Layer>) {
885    // Pre-process: apply opened/children/blendMode for folders.
886    // We process from the end (as upstream loops i = len-1 .. 0) building nested
887    // vectors. `stack` holds the children-list under construction; each entry is
888    // a Vec<Layer>. When we open a folder we push a new list; when we hit a
889    // bounding divider we pop and attach to the parent's last-unshifted folder.
890    //
891    // Because upstream unshifts (prepends) and we iterate end->start, the final
892    // order is preserved by pushing to front of each list.
893
894    // Stack of (children list, optional folder layer awaiting its children).
895    struct Frame {
896        children: Vec<Layer>,
897        folder: Option<Layer>,
898    }
899
900    let mut stack: Vec<Frame> = vec![Frame { children: Vec::new(), folder: None }];
901
902    for i in (0..layers.len()).rev() {
903        let l = std::mem::take(&mut layers[i]);
904        let ty = l
905            .additional_info
906            .section_divider
907            .as_ref()
908            .map(|d| d.divider_type)
909            .unwrap_or(SectionDividerType::Other);
910
911        match ty {
912            SectionDividerType::OpenFolder | SectionDividerType::ClosedFolder => {
913                let mut folder = l;
914                folder.opened = Some(ty == SectionDividerType::OpenFolder);
915                folder.children = Some(Vec::new());
916                if let Some(div) = &folder.additional_info.section_divider {
917                    if let Some(key) = &div.key {
918                        if let Some(bm) = to_blend_mode(key) {
919                            folder.blend_mode = Some(bm);
920                        }
921                    }
922                }
923                // push the folder frame; its children come from subsequent
924                // (deeper-in-file, earlier-in-loop) layers between this and the
925                // bounding divider.
926                stack.push(Frame { children: Vec::new(), folder: Some(folder) });
927            }
928            SectionDividerType::BoundingSectionDivider => {
929                // close current frame: attach collected children to folder, then
930                // unshift folder into parent.
931                let frame = stack.pop().unwrap_or(Frame { children: Vec::new(), folder: None });
932                if let Some(mut folder) = frame.folder {
933                    folder.children = Some(frame.children);
934                    if let Some(parent) = stack.last_mut() {
935                        parent.children.insert(0, folder);
936                    }
937                } else {
938                    // bounding divider without matching folder; ignore body.
939                    if let Some(parent) = stack.last_mut() {
940                        for layer in frame.children.into_iter().rev() {
941                            parent.children.insert(0, layer);
942                        }
943                    }
944                }
945            }
946            _ => {
947                if let Some(top) = stack.last_mut() {
948                    top.children.insert(0, l);
949                }
950            }
951        }
952    }
953
954    // Drain any unterminated folders (defensive — well-formed files end clean).
955    while stack.len() > 1 {
956        let frame = stack.pop().unwrap();
957        if let Some(mut folder) = frame.folder {
958            folder.children = Some(frame.children);
959            if let Some(parent) = stack.last_mut() {
960                parent.children.insert(0, folder);
961            }
962        } else if let Some(parent) = stack.last_mut() {
963            for layer in frame.children.into_iter().rev() {
964                parent.children.insert(0, layer);
965            }
966        }
967    }
968
969    let root = stack.pop().unwrap();
970    let children = psd.children.get_or_insert_with(Vec::new);
971    *children = root.children;
972}
973
974// ---------------------------------------------------------------------------
975// readLayerRecord
976// ---------------------------------------------------------------------------
977
978fn read_layer_record(
979    reader: &mut PsdReader,
980    _psd: &mut crate::psd::Psd,
981) -> ReadResult<(Layer, Vec<ChannelInfo>)> {
982    let mut layer = Layer::default();
983    layer.top = Some(read_int32(reader)? as f64);
984    layer.left = Some(read_int32(reader)? as f64);
985    layer.bottom = Some(read_int32(reader)? as f64);
986    layer.right = Some(read_int32(reader)? as f64);
987
988    let channel_count = read_uint16(reader)?;
989    let mut channels: Vec<ChannelInfo> = Vec::with_capacity(channel_count as usize);
990
991    for _ in 0..channel_count {
992        let id = read_int16(reader)?;
993        let mut length = read_uint32(reader)? as usize;
994        if reader.large {
995            if length != 0 {
996                return Err(ReadError::StrictViolation(
997                    "Sizes larger than 4GB are not supported".to_string(),
998                ));
999            }
1000            length = read_uint32(reader)? as usize;
1001        }
1002        channels.push(ChannelInfo { id, length });
1003    }
1004
1005    check_signature(reader, "8BIM", None)?;
1006    let blend_mode = read_signature(reader)?;
1007    match to_blend_mode(&blend_mode) {
1008        Some(bm) => layer.blend_mode = Some(bm),
1009        None => {
1010            return Err(ReadError::StrictViolation(format!(
1011                "Invalid blend mode: '{}'",
1012                blend_mode
1013            )))
1014        }
1015    }
1016
1017    layer.opacity = Some(read_uint8(reader)? as f64 / 0xff as f64);
1018    layer.clipping = Some(read_uint8(reader)? == 1);
1019
1020    let flags = read_uint8(reader)?;
1021    layer.transparency_protected = Some((flags & 0x01) != 0);
1022    layer.hidden = Some((flags & 0x02) != 0);
1023    if flags & 0x20 != 0 {
1024        layer.effects_open = Some(true);
1025    }
1026
1027    skip_bytes(reader, 1);
1028
1029    // extra data section
1030    let large = reader.large;
1031    let mut info = std::mem::take(&mut layer.additional_info);
1032    read_section(
1033        reader,
1034        1,
1035        |reader, left| {
1036            read_layer_mask_data(reader, &mut info)?;
1037
1038            if let Some(ranges) = read_layer_blending_ranges(reader)? {
1039                info.blending_ranges = Some(ranges);
1040            }
1041            info.name = Some(read_pascal_string(reader, 1)?);
1042
1043            // HACK: skip junk until a valid signature
1044            while left(reader) > 4 && !valid_signature_at(reader, reader.offset) {
1045                reader.offset += 1;
1046            }
1047
1048            while left(reader) >= 12 {
1049                read_additional_layer_info(reader, &mut info)?;
1050            }
1051
1052            skip_bytes(reader, left(reader));
1053            Ok(())
1054        },
1055        true,
1056        false,
1057    )?;
1058    let _ = large;
1059    layer.additional_info = info;
1060
1061    Ok((layer, channels))
1062}
1063
1064fn read_layer_mask_data(
1065    reader: &mut PsdReader,
1066    info: &mut LayerAdditionalInfo,
1067) -> ReadResult<()> {
1068    read_section(
1069        reader,
1070        1,
1071        |reader, left| {
1072            if left(reader) == 0 {
1073                return Ok(());
1074            }
1075            let mut mask = LayerMaskData::default();
1076            mask.top = Some(read_int32(reader)? as f64);
1077            mask.left = Some(read_int32(reader)? as f64);
1078            mask.bottom = Some(read_int32(reader)? as f64);
1079            mask.right = Some(read_int32(reader)? as f64);
1080            mask.default_color = Some(read_uint8(reader)? as f64);
1081
1082            let flags = read_uint8(reader)?;
1083            mask.position_relative_to_layer =
1084                Some((flags & LayerMaskFlags::PositionRelativeToLayer as u8) != 0);
1085            mask.disabled = Some((flags & LayerMaskFlags::LayerMaskDisabled as u8) != 0);
1086            mask.from_vector_data =
1087                Some((flags & LayerMaskFlags::LayerMaskFromRenderingOtherData as u8) != 0);
1088
1089            if left(reader) >= 18 {
1090                let mut real_mask = LayerMaskData::default();
1091                let real_flags = read_uint8(reader)?;
1092                real_mask.position_relative_to_layer =
1093                    Some((real_flags & LayerMaskFlags::PositionRelativeToLayer as u8) != 0);
1094                real_mask.disabled =
1095                    Some((real_flags & LayerMaskFlags::LayerMaskDisabled as u8) != 0);
1096                real_mask.from_vector_data = Some(
1097                    (real_flags & LayerMaskFlags::LayerMaskFromRenderingOtherData as u8) != 0,
1098                );
1099                real_mask.default_color = Some(read_uint8(reader)? as f64);
1100                real_mask.top = Some(read_int32(reader)? as f64);
1101                real_mask.left = Some(read_int32(reader)? as f64);
1102                real_mask.bottom = Some(read_int32(reader)? as f64);
1103                real_mask.right = Some(read_int32(reader)? as f64);
1104                info.real_mask = Some(real_mask);
1105            }
1106
1107            if flags & LayerMaskFlags::MaskHasParametersAppliedToIt as u8 != 0 {
1108                let params = read_uint8(reader)?;
1109                if params & MaskParams::UserMaskDensity as u8 != 0 {
1110                    mask.user_mask_density = Some(read_uint8(reader)? as f64 / 0xff as f64);
1111                }
1112                if params & MaskParams::UserMaskFeather as u8 != 0 {
1113                    mask.user_mask_feather = Some(read_float64(reader)?);
1114                }
1115                if params & MaskParams::VectorMaskDensity as u8 != 0 {
1116                    mask.vector_mask_density = Some(read_uint8(reader)? as f64 / 0xff as f64);
1117                }
1118                if params & MaskParams::VectorMaskFeather as u8 != 0 {
1119                    mask.vector_mask_feather = Some(read_float64(reader)?);
1120                }
1121            }
1122
1123            info.mask = Some(mask);
1124            skip_bytes(reader, left(reader));
1125            Ok(())
1126        },
1127        true,
1128        false,
1129    )?;
1130    Ok(())
1131}
1132
1133fn read_blending_range(reader: &mut PsdReader) -> ReadResult<Vec<f64>> {
1134    Ok(vec![
1135        read_uint8(reader)? as f64,
1136        read_uint8(reader)? as f64,
1137        read_uint8(reader)? as f64,
1138        read_uint8(reader)? as f64,
1139    ])
1140}
1141
1142fn read_layer_blending_ranges(
1143    reader: &mut PsdReader,
1144) -> ReadResult<Option<crate::psd::BlendingRanges>> {
1145    let res = read_section(
1146        reader,
1147        1,
1148        |reader, left| {
1149            let composite_gray_blend_source = read_blending_range(reader)?;
1150            let composite_graph_blend_destination_range = read_blending_range(reader)?;
1151            let mut ranges: Vec<crate::psd::BlendingRange> = Vec::new();
1152            while left(reader) > 0 {
1153                let source_range = read_blending_range(reader)?;
1154                let dest_range = read_blending_range(reader)?;
1155                ranges.push(crate::psd::BlendingRange { source_range, dest_range });
1156            }
1157            Ok(crate::psd::BlendingRanges {
1158                composite_gray_blend_source,
1159                composite_graph_blend_destination_range,
1160                ranges,
1161            })
1162        },
1163        true,
1164        false,
1165    )?;
1166    Ok(res)
1167}
1168
1169// ---------------------------------------------------------------------------
1170// readLayerChannelImageData
1171// ---------------------------------------------------------------------------
1172
1173fn read_layer_channel_image_data(
1174    reader: &mut PsdReader,
1175    psd: &crate::psd::Psd,
1176    layer: &mut Layer,
1177    channels: &[ChannelInfo],
1178) -> ReadResult<()> {
1179    if reader.options.skip_layer_image_data == Some(true) {
1180        return Ok(());
1181    }
1182
1183    let color_mode = psd.color_mode.unwrap_or(ColorMode::Rgb);
1184    let bits_per_channel = psd.bits_per_channel.unwrap_or(8.0);
1185    let large = reader.large;
1186
1187    let mut raw_channels: Vec<LayerRawDataChannel> = Vec::with_capacity(channels.len());
1188
1189    for channel in channels {
1190        let start = reader.offset;
1191        let mut compression = Compression::RawData;
1192        let mut data: Option<Vec<u8>> = None;
1193
1194        if channel.length == 1 {
1195            return Err(ReadError::StrictViolation("Invalid channel length".to_string()));
1196        }
1197        if channel.length != 0 {
1198            let mut comp = read_uint16(reader)?;
1199            if comp > 3 {
1200                reader.offset -= 1;
1201                comp = read_uint16(reader)?;
1202            }
1203            if comp > 3 {
1204                reader.offset -= 3;
1205                comp = read_uint16(reader)?;
1206            }
1207            if comp > 3 {
1208                return Err(ReadError::StrictViolation(format!(
1209                    "Invalid compression: {}",
1210                    comp
1211                )));
1212            }
1213            compression = compression_from_u16(comp);
1214            if channel.length > 2 {
1215                data = Some(read_bytes(reader, channel.length - 2)?);
1216            }
1217        }
1218
1219        reader.offset = start + channel.length;
1220        raw_channels.push(LayerRawDataChannel {
1221            id: channel_id_from_i16(channel.id),
1222            compression,
1223            data,
1224        });
1225    }
1226
1227    layer.raw_data = Some(LayerRawData {
1228        color_mode,
1229        bits_per_channel,
1230        channels: raw_channels,
1231        large,
1232    });
1233
1234    if reader.options.use_raw_data != Some(true) {
1235        let use_image_data = reader.options.use_image_data == Some(true);
1236        let throw_missing = reader.options.throw_for_missing_features == Some(true);
1237        decode_layer_image_data(layer, use_image_data, throw_missing)?;
1238    }
1239
1240    Ok(())
1241}
1242
1243fn compression_from_u16(v: u16) -> Compression {
1244    match v {
1245        0 => Compression::RawData,
1246        1 => Compression::RleCompressed,
1247        2 => Compression::ZipWithoutPrediction,
1248        _ => Compression::ZipWithPrediction,
1249    }
1250}
1251
1252fn setup_grayscale(data: &mut [u8], width: usize, height: usize) {
1253    let size = width * height * 4;
1254    let mut i = 0;
1255    while i < size {
1256        let c = data[i];
1257        data[i + 1] = c;
1258        data[i + 2] = c;
1259        i += 4;
1260    }
1261}
1262
1263fn reset_alpha(target: &mut DecodeTarget, cmyk: bool) {
1264    let alpha = 0xffu8;
1265    let offset = if cmyk { 4 } else { 3 };
1266    let step = if cmyk { 5 } else { 4 };
1267    let length = target.data.len();
1268    let mut p = offset;
1269    while p < length {
1270        target.data[p] = alpha;
1271        p += step;
1272    }
1273}
1274
1275/// Mirror `decodeLayerImageData`.
1276fn decode_layer_image_data(
1277    layer: &mut Layer,
1278    use_image_data: bool,
1279    throw_for_missing_features: bool,
1280) -> ReadResult<()> {
1281    let raw = match layer.raw_data.take() {
1282        Some(r) => r,
1283        None => return Ok(()),
1284    };
1285
1286    let color_mode = raw.color_mode;
1287    let bits_per_channel = raw.bits_per_channel as u32;
1288    let large = raw.large;
1289    let layer_width =
1290        (layer.right.unwrap_or(0.0) - layer.left.unwrap_or(0.0)).max(0.0) as usize;
1291    let layer_height =
1292        (layer.bottom.unwrap_or(0.0) - layer.top.unwrap_or(0.0)).max(0.0) as usize;
1293    let cmyk = color_mode == ColorMode::Cmyk;
1294
1295    let mut image_data: Option<DecodeTarget> = None;
1296    let mut initialized_alpha = false;
1297
1298    if layer_width != 0 && layer_height != 0 {
1299        if cmyk {
1300            if bits_per_channel != 8 {
1301                return Err(ReadError::StrictViolation("bitsPerChannel Not supproted".to_string()));
1302            }
1303            image_data = Some(DecodeTarget::wide(layer_width, layer_height, 5));
1304        } else {
1305            image_data = Some(DecodeTarget::rgba(layer_width, layer_height));
1306        }
1307    }
1308
1309    for ch in &raw.channels {
1310        let data = match &ch.data {
1311            Some(d) => d,
1312            None => continue,
1313        };
1314        let mut data_reader = PsdReader::new(data, None, None);
1315
1316        if ch.id == ChannelId::UserMask || ch.id == ChannelId::RealUserMask {
1317            let mask_ref = if ch.id == ChannelId::UserMask {
1318                layer.additional_info.mask.as_ref()
1319            } else {
1320                layer.additional_info.real_mask.as_ref()
1321            };
1322            let (mtop, mleft, mbottom, mright) = match mask_ref {
1323                Some(m) => (
1324                    m.top.unwrap_or(0.0),
1325                    m.left.unwrap_or(0.0),
1326                    m.bottom.unwrap_or(0.0),
1327                    m.right.unwrap_or(0.0),
1328                ),
1329                None => {
1330                    return Err(ReadError::StrictViolation(format!(
1331                        "Missing layer {} data",
1332                        if ch.id == ChannelId::UserMask { "mask" } else { "real mask" }
1333                    )))
1334                }
1335            };
1336            let mask_width = (mright - mleft) as i64;
1337            let mask_height = (mbottom - mtop) as i64;
1338            if !(0..=30000).contains(&mask_width) || !(0..=30000).contains(&mask_height) {
1339                return Err(ReadError::StrictViolation("Invalid mask size".to_string()));
1340            }
1341            let mw = mask_width as usize;
1342            let mh = mask_height as usize;
1343            if mw != 0 && mh != 0 {
1344                let mut mask_data = DecodeTarget::rgba(mw, mh);
1345                read_data(
1346                    &mut data_reader,
1347                    data.len(),
1348                    Some(&mut mask_data),
1349                    ch.compression,
1350                    mw,
1351                    mh,
1352                    bits_per_channel,
1353                    0,
1354                    large,
1355                    4,
1356                )?;
1357                setup_grayscale(&mut mask_data.data, mw, mh);
1358                reset_alpha(&mut mask_data, false);
1359                let pd = mask_data.into_pixel_data();
1360                let mask = if ch.id == ChannelId::UserMask {
1361                    layer.additional_info.mask.as_mut()
1362                } else {
1363                    layer.additional_info.real_mask.as_mut()
1364                };
1365                if let Some(mask) = mask {
1366                    if use_image_data {
1367                        mask.image_data = Some(pd);
1368                    } else {
1369                        mask.canvas = Some(image_data_to_canvas(&pd));
1370                    }
1371                }
1372            }
1373        } else {
1374            let offset = offset_for_channel(ch.id, cmyk);
1375            let target = if offset < 0 {
1376                if throw_for_missing_features {
1377                    return Err(ReadError::StrictViolation(format!(
1378                        "Channel not supported: {}",
1379                        ch.id as i32
1380                    )));
1381                }
1382                None
1383            } else {
1384                image_data.as_mut()
1385            };
1386
1387            let step = if cmyk { 5 } else { 4 };
1388            read_data(
1389                &mut data_reader,
1390                data.len(),
1391                target,
1392                ch.compression,
1393                layer_width,
1394                layer_height,
1395                bits_per_channel,
1396                offset.max(0) as usize,
1397                large,
1398                step,
1399            )?;
1400
1401            if offset >= 0 && color_mode == ColorMode::Grayscale {
1402                if let Some(t) = image_data.as_mut() {
1403                    setup_grayscale(&mut t.data, t.width, t.height);
1404                }
1405            }
1406        }
1407
1408        if ch.id == ChannelId::Transparency {
1409            initialized_alpha = true;
1410        }
1411    }
1412
1413    if let Some(mut img) = image_data {
1414        if !initialized_alpha {
1415            reset_alpha(&mut img, cmyk);
1416        }
1417
1418        let final_pd = if cmyk {
1419            let mut rgb = create_image_data(img.width as u32, img.height as u32);
1420            cmyk_to_rgb(&img, &mut rgb, false);
1421            rgb
1422        } else {
1423            img.into_pixel_data()
1424        };
1425
1426        if use_image_data {
1427            layer.image_data = Some(final_pd);
1428        } else {
1429            layer.canvas = Some(image_data_to_canvas(&final_pd));
1430        }
1431    }
1432
1433    Ok(())
1434}
1435
1436// ---------------------------------------------------------------------------
1437// Channel image-data codecs
1438// ---------------------------------------------------------------------------
1439
1440/// Mirror `readData` dispatch.
1441fn read_data(
1442    reader: &mut PsdReader,
1443    length: usize,
1444    pixels: Option<&mut DecodeTarget>,
1445    compression: Compression,
1446    width: usize,
1447    height: usize,
1448    bit_depth: u32,
1449    offset: usize,
1450    large: bool,
1451    step: usize,
1452) -> ReadResult<()> {
1453    if length == 0 {
1454        return Ok(());
1455    }
1456    match compression {
1457        Compression::RawData => {
1458            let data = read_bytes(reader, length)?;
1459            read_data_raw(&data, pixels, bit_depth, step, offset);
1460            Ok(())
1461        }
1462        Compression::RleCompressed => {
1463            read_data_rle(reader, pixels, width, height, bit_depth, step, &[offset], large)
1464        }
1465        Compression::ZipWithoutPrediction => {
1466            let data = read_bytes(reader, length)?;
1467            read_data_zip(&data, pixels, width, height, bit_depth, step, offset, false);
1468            Ok(())
1469        }
1470        Compression::ZipWithPrediction => {
1471            let data = read_bytes(reader, length)?;
1472            read_data_zip(&data, pixels, width, height, bit_depth, step, offset, true);
1473            Ok(())
1474        }
1475    }
1476}
1477
1478fn copy_channel_to_pixel_data(target: &mut DecodeTarget, channel: &[u8], offset: usize, step: usize) {
1479    let size = target.width * target.height;
1480    let mut p = offset;
1481    for i in 0..size {
1482        if i >= channel.len() || p >= target.data.len() {
1483            break;
1484        }
1485        target.data[p] = channel[i];
1486        p += step;
1487    }
1488}
1489
1490/// Mirror `readDataRaw`. Down-converts 16/32-bit samples to 8-bit (top byte).
1491pub fn read_data_raw(
1492    buffer: &[u8],
1493    pixel_data: Option<&mut DecodeTarget>,
1494    bit_depth: u32,
1495    step: usize,
1496    offset: usize,
1497) {
1498    let pixel_data = match pixel_data {
1499        Some(p) => p,
1500        None => return,
1501    };
1502    if offset >= step {
1503        return;
1504    }
1505    let bytes = bytes_to_u8_channel(buffer, bit_depth);
1506    copy_channel_to_pixel_data(pixel_data, &bytes, offset, step);
1507}
1508
1509/// Convert a big-endian channel byte buffer to an 8-bit sample-per-element Vec.
1510/// For 16/32-bit, takes the most significant byte (matching down-conversion to
1511/// RGBA8 used elsewhere in this crate).
1512fn bytes_to_u8_channel(buffer: &[u8], bit_depth: u32) -> Vec<u8> {
1513    match bit_depth {
1514        8 => buffer.to_vec(),
1515        16 => {
1516            // big-endian: MSB first
1517            let mut out = Vec::with_capacity(buffer.len() / 2);
1518            let mut i = 0;
1519            while i + 1 < buffer.len() {
1520                out.push(buffer[i]);
1521                i += 2;
1522            }
1523            out
1524        }
1525        32 => {
1526            // 32-bit float channel; clamp [0,1] -> [0,255].
1527            let mut out = Vec::with_capacity(buffer.len() / 4);
1528            let mut i = 0;
1529            while i + 3 < buffer.len() {
1530                let v = f32::from_be_bytes([
1531                    buffer[i],
1532                    buffer[i + 1],
1533                    buffer[i + 2],
1534                    buffer[i + 3],
1535                ]);
1536                let c = (v.max(0.0).min(1.0) * 255.0).round() as u8;
1537                out.push(c);
1538                i += 4;
1539            }
1540            out
1541        }
1542        _ => buffer.to_vec(),
1543    }
1544}
1545
1546fn decode_predicted_u8(data: &mut [u8], width: usize, height: usize) {
1547    for y in 0..height {
1548        let offset = y * width;
1549        for x in 1..width {
1550            let o = offset + x;
1551            data[o] = data[o - 1].wrapping_add(data[o]);
1552        }
1553    }
1554}
1555
1556fn decode_predicted_u16(data: &mut [u16], width: usize, height: usize) {
1557    for y in 0..height {
1558        let offset = y * width;
1559        for x in 1..width {
1560            let o = offset + x;
1561            data[o] = data[o - 1].wrapping_add(data[o]);
1562        }
1563    }
1564}
1565
1566/// Mirror `readDataZip` (zlib via flate2).
1567pub fn read_data_zip(
1568    compressed: &[u8],
1569    pixel_data: Option<&mut DecodeTarget>,
1570    width: usize,
1571    height: usize,
1572    bit_depth: u32,
1573    step: usize,
1574    offset: usize,
1575    prediction: bool,
1576) {
1577    use flate2::read::ZlibDecoder;
1578    use std::io::Read;
1579
1580    let mut decoder = ZlibDecoder::new(compressed);
1581    let mut decompressed: Vec<u8> = Vec::new();
1582    if decoder.read_to_end(&mut decompressed).is_err() {
1583        return;
1584    }
1585
1586    let pixel_data = match pixel_data {
1587        Some(p) => p,
1588        None => return,
1589    };
1590    if offset >= step {
1591        return;
1592    }
1593
1594    match bit_depth {
1595        8 => {
1596            if prediction {
1597                decode_predicted_u8(&mut decompressed, width, height);
1598            }
1599            copy_channel_to_pixel_data(pixel_data, &decompressed, offset, step);
1600        }
1601        16 => {
1602            // big-endian u16 samples
1603            let mut samples: Vec<u16> = Vec::with_capacity(decompressed.len() / 2);
1604            let mut i = 0;
1605            while i + 1 < decompressed.len() {
1606                samples.push(u16::from_be_bytes([decompressed[i], decompressed[i + 1]]));
1607                i += 2;
1608            }
1609            if prediction {
1610                decode_predicted_u16(&mut samples, width, height);
1611            }
1612            // down-convert to MSB byte
1613            let bytes: Vec<u8> = samples.iter().map(|&s| (s >> 8) as u8).collect();
1614            copy_channel_to_pixel_data(pixel_data, &bytes, offset, step);
1615        }
1616        32 => {
1617            // 32-bit float, optionally byte-predicted across width*4 bytes.
1618            if prediction {
1619                decode_predicted_u8(&mut decompressed, width * 4, height);
1620            }
1621            // Photoshop stores planar bytes: reconstruct big-endian floats.
1622            let mut p = offset;
1623            for y in 0..height {
1624                let a0 = width * 4 * y;
1625                for x in 0..width {
1626                    let a = a0 + x;
1627                    let b = a + width;
1628                    let c = b + width;
1629                    let d = c + width;
1630                    if d >= decompressed.len() || p >= pixel_data.data.len() {
1631                        break;
1632                    }
1633                    let v = f32::from_be_bytes([
1634                        decompressed[a],
1635                        decompressed[b],
1636                        decompressed[c],
1637                        decompressed[d],
1638                    ]);
1639                    pixel_data.data[p] = (v.max(0.0).min(1.0) * 255.0).round() as u8;
1640                    p += step;
1641                }
1642            }
1643        }
1644        _ => {}
1645    }
1646}
1647
1648/// Mirror `readDataRLE` (PackBits). Writes one byte per sample to the 8-bit
1649/// RGBA target. For >8 bit depths the source is still byte-stream PackBits, so
1650/// we keep upstream's byte semantics (the upstream RLE path also writes bytes).
1651pub fn read_data_rle(
1652    reader: &mut PsdReader,
1653    mut pixel_data: Option<&mut DecodeTarget>,
1654    width: usize,
1655    height: usize,
1656    _bit_depth: u32,
1657    step: usize,
1658    offsets: &[usize],
1659    large: bool,
1660) -> ReadResult<()> {
1661    let mut lengths: Vec<usize> = Vec::with_capacity(offsets.len() * height);
1662    if large {
1663        for _ in 0..offsets.len() {
1664            for _ in 0..height {
1665                lengths.push(read_uint32(reader)? as usize);
1666            }
1667        }
1668    } else {
1669        for _ in 0..offsets.len() {
1670            for _ in 0..height {
1671                lengths.push(read_uint16(reader)? as usize);
1672            }
1673        }
1674    }
1675
1676    let extra_limit = step.saturating_sub(1);
1677
1678    let mut li = 0usize;
1679    for c in 0..offsets.len() {
1680        let offset = offsets[c];
1681        let extra = c > extra_limit || offset > extra_limit;
1682
1683        let have_data = pixel_data.is_some() && !extra;
1684        if !have_data {
1685            for _ in 0..height {
1686                let len = lengths[li];
1687                li += 1;
1688                skip_bytes(reader, len);
1689            }
1690            continue;
1691        }
1692
1693        let mut p = offset;
1694        for _ in 0..height {
1695            let length = lengths[li];
1696            li += 1;
1697            let buffer = read_bytes(reader, length)?;
1698
1699            let mut i = 0usize;
1700            let mut x = 0usize;
1701            while i < length {
1702                let header = buffer[i];
1703                if header > 128 {
1704                    i += 1;
1705                    if i >= buffer.len() {
1706                        break;
1707                    }
1708                    let value = buffer[i];
1709                    let count = (256 - header as usize) as usize;
1710                    let mut j = 0;
1711                    while j <= count && x < width {
1712                        let pd = pixel_data.as_deref_mut_unchecked();
1713                        if p < pd.data.len() {
1714                            pd.data[p] = value;
1715                        }
1716                        p += step;
1717                        j += 1;
1718                        x += 1;
1719                    }
1720                } else if header < 128 {
1721                    let count = header as usize;
1722                    let mut j = 0;
1723                    while j <= count && x < width {
1724                        i += 1;
1725                        if i >= buffer.len() {
1726                            break;
1727                        }
1728                        let value = buffer[i];
1729                        let pd = pixel_data.as_deref_mut_unchecked();
1730                        if p < pd.data.len() {
1731                            pd.data[p] = value;
1732                        }
1733                        p += step;
1734                        j += 1;
1735                        x += 1;
1736                    }
1737                }
1738                i += 1;
1739            }
1740        }
1741        // assignment of p back happens implicitly via loop continuation; in
1742        // upstream p resets per channel via offset, which we did at loop top.
1743        let _ = p;
1744    }
1745
1746    Ok(())
1747}
1748
1749// Helper trait to reborrow Option<&mut T> inside the RLE inner loops without
1750// fighting the borrow checker over the per-iteration mutable access.
1751trait OptMutHelper {
1752    fn as_deref_mut_unchecked(&mut self) -> &mut DecodeTarget;
1753}
1754impl OptMutHelper for Option<&mut DecodeTarget> {
1755    #[inline]
1756    fn as_deref_mut_unchecked(&mut self) -> &mut DecodeTarget {
1757        self.as_deref_mut().expect("pixel_data present in RLE write path")
1758    }
1759}
1760
1761// ---------------------------------------------------------------------------
1762// readGlobalLayerMaskInfo
1763// ---------------------------------------------------------------------------
1764
1765fn read_global_layer_mask_info(
1766    reader: &mut PsdReader,
1767) -> ReadResult<Option<GlobalLayerMaskInfo>> {
1768    let res = read_section(
1769        reader,
1770        1,
1771        |reader, left| {
1772            if left(reader) == 0 {
1773                return Ok(None);
1774            }
1775            let overlay_color_space = read_uint16(reader)? as f64;
1776            let color_space1 = read_uint16(reader)? as f64;
1777            let color_space2 = read_uint16(reader)? as f64;
1778            let color_space3 = read_uint16(reader)? as f64;
1779            let color_space4 = read_uint16(reader)? as f64;
1780            let opacity = read_uint16(reader)? as f64 / 0xff as f64;
1781            let kind = read_uint8(reader)? as f64;
1782            skip_bytes(reader, left(reader));
1783            Ok(Some(GlobalLayerMaskInfo {
1784                overlay_color_space,
1785                color_space1,
1786                color_space2,
1787                color_space3,
1788                color_space4,
1789                opacity,
1790                kind,
1791            }))
1792        },
1793        true,
1794        false,
1795    )?;
1796    Ok(res.flatten())
1797}
1798
1799// ---------------------------------------------------------------------------
1800// realignWithSignature & readAdditionalLayerInfo
1801// ---------------------------------------------------------------------------
1802
1803const FIX_OFFSETS: [i32; 9] = [0, 1, -1, 2, -2, 3, -3, 4, -4];
1804
1805/// Mirror `realignWithSignature`.
1806fn realign_with_signature(
1807    reader: &mut PsdReader,
1808    is_valid: fn(&str) -> bool,
1809) -> ReadResult<String> {
1810    let sig_offset = reader.offset as i64;
1811    let mut sig = String::new();
1812
1813    for &off in FIX_OFFSETS.iter() {
1814        let new_off = sig_offset + off as i64;
1815        if new_off < 0 || (new_off as usize) + 4 > reader.buffer.len() {
1816            continue;
1817        }
1818        reader.offset = new_off as usize;
1819        if let Ok(s) = read_signature(reader) {
1820            sig = s;
1821        }
1822        if is_valid(&sig) {
1823            break;
1824        }
1825    }
1826
1827    if !is_valid(&sig) {
1828        return Err(ReadError::InvalidSignature {
1829            signature: sig,
1830            offset: sig_offset as usize,
1831        });
1832    }
1833    Ok(sig)
1834}
1835
1836fn is_valid_additional_info_signature(sig: &str) -> bool {
1837    sig == "8BIM" || sig == "8B64"
1838}
1839
1840/// Mirror `readAdditionalLayerInfo`.
1841fn read_additional_layer_info(
1842    reader: &mut PsdReader,
1843    target: &mut LayerAdditionalInfo,
1844) -> ReadResult<()> {
1845    let sig = realign_with_signature(reader, is_valid_additional_info_signature)?;
1846    let key = read_signature(reader)?;
1847
1848    let large = reader.large;
1849    let u64_size = sig == "8B64"
1850        || (large && crate::additional_info::is_large_key(&key));
1851
1852    let options = reader.options.clone();
1853    let throw_for_missing = options.throw_for_missing_features == Some(true);
1854
1855    read_section(
1856        reader,
1857        2,
1858        |reader, left| {
1859            let mut ctx = ReadCtx { options: &options, large };
1860            match read_additional_info_key(&key, reader, target, &left_fn_wrap(left), &mut ctx) {
1861                Ok(handled) => {
1862                    if !handled {
1863                        skip_bytes(reader, left(reader));
1864                    }
1865                }
1866                Err(e) => {
1867                    if throw_for_missing {
1868                        return Err(e);
1869                    }
1870                    // swallow and skip remaining
1871                }
1872            }
1873            if left(reader) > 0 {
1874                skip_bytes(reader, left(reader));
1875            }
1876            Ok(())
1877        },
1878        false,
1879        u64_size,
1880    )?;
1881    Ok(())
1882}
1883
1884/// `read_additional_info_key` expects `&dyn Fn(&PsdReader)->usize`; the section
1885/// closure already provides one (`left`). This wrapper just re-types it.
1886fn left_fn_wrap<'a>(left: &'a dyn Fn(&PsdReader) -> usize) -> impl Fn(&PsdReader) -> usize + 'a {
1887    move |r: &PsdReader| left(r)
1888}
1889
1890// ---------------------------------------------------------------------------
1891// readImageData (composite)
1892// ---------------------------------------------------------------------------
1893
1894fn read_image_data(reader: &mut PsdReader, psd: &mut crate::psd::Psd) -> ReadResult<()> {
1895    let compression = compression_from_u16(read_uint16(reader)?);
1896    let bits_per_channel = psd.bits_per_channel.unwrap_or(8.0) as u32;
1897    let color_mode = psd.color_mode.unwrap_or(ColorMode::Rgb);
1898
1899    let width = psd.width as usize;
1900    let height = psd.height as usize;
1901    let channels_count = psd.channels.unwrap_or(0.0) as usize;
1902
1903    if compression != Compression::RawData && compression != Compression::RleCompressed {
1904        return Err(ReadError::StrictViolation(format!(
1905            "Compression type not supported: {:?}",
1906            compression
1907        )));
1908    }
1909
1910    let mut image_data = DecodeTarget::rgba(width, height);
1911    {
1912        // resetImageData: black, opaque.
1913        let buf = &mut image_data.data;
1914        let mut p = 0;
1915        while p < buf.len() {
1916            buf[p] = 0;
1917            buf[p + 1] = 0;
1918            buf[p + 2] = 0;
1919            buf[p + 3] = 0xff;
1920            p += 4;
1921        }
1922    }
1923
1924    match color_mode {
1925        ColorMode::Bitmap => {
1926            if bits_per_channel != 1 {
1927                return Err(ReadError::StrictViolation(
1928                    "Invalid bitsPerChannel for bitmap color mode".to_string(),
1929                ));
1930            }
1931            let bytes: Vec<u8> = match compression {
1932                Compression::RawData => {
1933                    read_bytes(reader, ((width + 7) / 8) * height)?
1934                }
1935                Compression::RleCompressed => {
1936                    let mut tgt = DecodeTarget {
1937                        width,
1938                        height,
1939                        data: vec![0u8; width * height],
1940                        channels: 1,
1941                    };
1942                    read_data_rle(
1943                        reader,
1944                        Some(&mut tgt),
1945                        width,
1946                        height,
1947                        8,
1948                        1,
1949                        &[0],
1950                        reader.large,
1951                    )?;
1952                    tgt.data
1953                }
1954                _ => {
1955                    return Err(ReadError::StrictViolation(
1956                        "Bitmap compression not supported".to_string(),
1957                    ))
1958                }
1959            };
1960            decode_bitmap(&bytes, &mut image_data.data, width, height);
1961        }
1962        ColorMode::Rgb | ColorMode::Grayscale => {
1963            let mut channels: Vec<usize> =
1964                if color_mode == ColorMode::Grayscale { vec![0] } else { vec![0, 1, 2] };
1965
1966            if channels_count > 3 {
1967                for i in 3..channels_count {
1968                    channels.push(i);
1969                }
1970            } else if reader.global_alpha {
1971                channels.push(3);
1972            }
1973
1974            match compression {
1975                Compression::RawData => {
1976                    for &c in &channels {
1977                        let data =
1978                            read_bytes(reader, width * height * (bits_per_channel as usize / 8))?;
1979                        read_data_raw(&data, Some(&mut image_data), bits_per_channel, 4, c);
1980                    }
1981                }
1982                Compression::RleCompressed => {
1983                    read_data_rle(
1984                        reader,
1985                        Some(&mut image_data),
1986                        width,
1987                        height,
1988                        bits_per_channel,
1989                        4,
1990                        &channels,
1991                        reader.large,
1992                    )?;
1993                }
1994                _ => {}
1995            }
1996
1997            if color_mode == ColorMode::Grayscale {
1998                setup_grayscale(&mut image_data.data, width, height);
1999            }
2000        }
2001        ColorMode::Indexed => {
2002            if bits_per_channel != 8 {
2003                return Err(ReadError::StrictViolation("bitsPerChannel Not supproted".to_string()));
2004            }
2005            if channels_count != 1 {
2006                return Err(ReadError::StrictViolation("Invalid channel count".to_string()));
2007            }
2008            let palette = psd
2009                .palette
2010                .clone()
2011                .ok_or_else(|| ReadError::StrictViolation("Missing color palette".to_string()))?;
2012
2013            match compression {
2014                Compression::RleCompressed => {
2015                    let mut indexed = DecodeTarget {
2016                        width,
2017                        height,
2018                        data: vec![0u8; width * height],
2019                        channels: 1,
2020                    };
2021                    read_data_rle(
2022                        reader,
2023                        Some(&mut indexed),
2024                        width,
2025                        height,
2026                        bits_per_channel,
2027                        1,
2028                        &[0],
2029                        reader.large,
2030                    )?;
2031                    indexed_to_rgb(&indexed, &mut image_data, &palette);
2032                }
2033                _ => return Err(ReadError::StrictViolation("Not implemented".to_string())),
2034            }
2035        }
2036        _ => {
2037            return Err(ReadError::StrictViolation(format!(
2038                "Color mode not supported: {:?}",
2039                color_mode
2040            )))
2041        }
2042    }
2043
2044    // remove weird white matte
2045    if reader.global_alpha && bits_per_channel == 8 {
2046        let p = &mut image_data.data;
2047        let size = width * height * 4;
2048        let mut i = 0;
2049        while i < size {
2050            let pa = p[i + 3];
2051            if pa != 0 && pa != 255 {
2052                let a = pa as f64 / 255.0;
2053                let ra = 1.0 / a;
2054                let inv_a = 255.0 * (1.0 - ra);
2055                p[i] = (p[i] as f64 * ra + inv_a) as u8;
2056                p[i + 1] = (p[i + 1] as f64 * ra + inv_a) as u8;
2057                p[i + 2] = (p[i + 2] as f64 * ra + inv_a) as u8;
2058            }
2059            i += 4;
2060        }
2061    }
2062
2063    let pd = image_data.into_pixel_data();
2064    if reader.options.use_image_data == Some(true) {
2065        psd.image_data = Some(pd);
2066    } else {
2067        psd.canvas = Some(image_data_to_canvas(&pd));
2068    }
2069
2070    Ok(())
2071}
2072
2073fn cmyk_to_rgb(cmyk: &DecodeTarget, rgb: &mut PixelData, reverse_alpha: bool) {
2074    let size = (rgb.width as usize) * (rgb.height as usize) * 4;
2075    let src = &cmyk.data;
2076    let dst = &mut rgb.data;
2077    let mut s = 0usize;
2078    let mut d = 0usize;
2079    while d < size && s + 4 < src.len() {
2080        let c = src[s] as u32;
2081        let m = src[s + 1] as u32;
2082        let y = src[s + 2] as u32;
2083        let k = src[s + 3] as u32;
2084        dst[d] = ((c * k) / 255) as u8;
2085        dst[d + 1] = ((m * k) / 255) as u8;
2086        dst[d + 2] = ((y * k) / 255) as u8;
2087        dst[d + 3] = if reverse_alpha { 255 - src[s + 4] } else { src[s + 4] };
2088        s += 5;
2089        d += 4;
2090    }
2091}
2092
2093fn indexed_to_rgb(indexed: &DecodeTarget, rgb: &mut DecodeTarget, palette: &[Rgb]) {
2094    let size = indexed.width * indexed.height;
2095    let mut d = 0usize;
2096    for s in 0..size {
2097        let idx = indexed.data[s] as usize;
2098        if let Some(c) = palette.get(idx) {
2099            rgb.data[d] = c.r as u8;
2100            rgb.data[d + 1] = c.g as u8;
2101            rgb.data[d + 2] = c.b as u8;
2102            rgb.data[d + 3] = 255;
2103        }
2104        d += 4;
2105    }
2106}
2107
2108// ---------------------------------------------------------------------------
2109// readColor (consolidated) & readPattern
2110// ---------------------------------------------------------------------------
2111
2112/// Consolidated `readColor`. NOTE (see report): `effects_helpers`, `image_resources`,
2113/// `additional_info::adjustment_keys`, and `additional_info::misc_keys` each keep
2114/// a LOCAL copy of this function (they cannot reach reader-internal helpers).
2115/// Those should switch to this in a later cleanup task; not edited now.
2116pub fn read_color(reader: &mut PsdReader) -> ReadResult<Color> {
2117    let color_space = read_uint16(reader)?;
2118    if color_space == ColorSpace::Rgb as u16 {
2119        let r = read_uint16(reader)? as f64 / 257.0;
2120        let g = read_uint16(reader)? as f64 / 257.0;
2121        let b = read_uint16(reader)? as f64 / 257.0;
2122        skip_bytes(reader, 2);
2123        Ok(Color::Rgb(Rgb { r, g, b }))
2124    } else if color_space == ColorSpace::Hsb as u16 {
2125        let h = read_uint16(reader)? as f64 / 0xffff as f64;
2126        let s = read_uint16(reader)? as f64 / 0xffff as f64;
2127        let b = read_uint16(reader)? as f64 / 0xffff as f64;
2128        skip_bytes(reader, 2);
2129        Ok(Color::Hsb(Hsb { h, s, b }))
2130    } else if color_space == ColorSpace::Cmyk as u16 {
2131        let c = read_uint16(reader)? as f64 / 257.0;
2132        let m = read_uint16(reader)? as f64 / 257.0;
2133        let y = read_uint16(reader)? as f64 / 257.0;
2134        let k = read_uint16(reader)? as f64 / 257.0;
2135        Ok(Color::Cmyk(Cmyk { c, m, y, k }))
2136    } else if color_space == ColorSpace::Lab as u16 {
2137        let l = read_int16(reader)? as f64 / 10000.0;
2138        let ta = read_int16(reader)? as f64;
2139        let tb = read_int16(reader)? as f64;
2140        let a = if ta < 0.0 { ta / 12800.0 } else { ta / 12700.0 };
2141        let b = if tb < 0.0 { tb / 12800.0 } else { tb / 12700.0 };
2142        skip_bytes(reader, 2);
2143        Ok(Color::Lab(Lab { l, a, b }))
2144    } else if color_space == ColorSpace::Grayscale as u16 {
2145        let k = read_uint16(reader)? as f64 * 255.0 / 10000.0;
2146        skip_bytes(reader, 6);
2147        Ok(Color::Grayscale(Grayscale { k }))
2148    } else {
2149        Err(ReadError::StrictViolation("Invalid color space".to_string()))
2150    }
2151}
2152
2153/// Consolidated `readPattern`. NOTE (see report): `abr.rs` and
2154/// `smart_object_keys.rs` hold local copies; they should switch to this later.
2155pub fn read_pattern(reader: &mut PsdReader) -> ReadResult<PatternInfo> {
2156    let mut length = read_uint32(reader)? as usize;
2157    while length % 4 != 0 {
2158        length += 1;
2159    }
2160    let end = reader.offset + length;
2161    let version = read_uint32(reader)?;
2162    if version != 1 {
2163        return Err(ReadError::StrictViolation(format!(
2164            "Invalid pattern version: {}",
2165            version
2166        )));
2167    }
2168
2169    let color_mode_raw = read_uint32(reader)?;
2170    let color_mode = color_mode_from_u16(color_mode_raw as u16);
2171    let x = read_int16(reader)? as f64;
2172    let y = read_int16(reader)? as f64;
2173
2174    if !matches!(
2175        color_mode,
2176        Some(ColorMode::Rgb) | Some(ColorMode::Grayscale) | Some(ColorMode::Indexed)
2177    ) {
2178        return Err(ReadError::StrictViolation(format!(
2179            "Unsupported pattern color mode: {}",
2180            color_mode_raw
2181        )));
2182    }
2183    let color_mode = color_mode.unwrap();
2184
2185    let name = read_unicode_string(reader)?;
2186    let id = read_pascal_string(reader, 1)?;
2187
2188    let mut palette: Vec<Rgb> = Vec::new();
2189    if color_mode == ColorMode::Indexed {
2190        for _ in 0..256 {
2191            palette.push(Rgb {
2192                r: read_uint8(reader)? as f64,
2193                g: read_uint8(reader)? as f64,
2194                b: read_uint8(reader)? as f64,
2195            });
2196        }
2197        skip_bytes(reader, 4);
2198    }
2199
2200    let version2 = read_uint32(reader)?;
2201    if version2 != 3 {
2202        return Err(ReadError::StrictViolation(format!(
2203            "Invalid pattern VMAL version: {}",
2204            version2
2205        )));
2206    }
2207
2208    read_uint32(reader)?; // length
2209    let top = read_uint32(reader)? as i64;
2210    let left = read_uint32(reader)? as i64;
2211    let bottom = read_uint32(reader)? as i64;
2212    let right = read_uint32(reader)? as i64;
2213    let channels_count = read_uint32(reader)? as usize;
2214    let width = (right - left) as usize;
2215    let height = (bottom - top) as usize;
2216    let mut data = vec![0u8; width * height * 4];
2217    let mut i = 3;
2218    while i < data.len() {
2219        data[i] = 255;
2220        i += 4;
2221    }
2222
2223    let mut ch = 0usize;
2224    for _ in 0..(channels_count + 2) {
2225        let has = read_uint32(reader)?;
2226        if has == 0 {
2227            continue;
2228        }
2229        let length = read_uint32(reader)? as usize;
2230        let pixel_depth = read_uint32(reader)?;
2231        let ctop = read_uint32(reader)? as i64;
2232        let cleft = read_uint32(reader)? as i64;
2233        let cbottom = read_uint32(reader)? as i64;
2234        let cright = read_uint32(reader)? as i64;
2235        let pixel_depth2 = read_uint16(reader)?;
2236        let compression_mode = read_uint8(reader)?;
2237        let data_length = length.saturating_sub(4 + 16 + 2 + 1);
2238        let cdata = read_bytes(reader, data_length)?;
2239
2240        if pixel_depth != 8 || pixel_depth2 != 8 {
2241            return Err(ReadError::StrictViolation(
2242                "16bit pixel depth not supported for patterns".to_string(),
2243            ));
2244        }
2245
2246        let w = (cright - cleft) as usize;
2247        let h = (cbottom - ctop) as usize;
2248        let ox = (cleft - left) as usize;
2249        let oy = (ctop - top) as usize;
2250
2251        if compression_mode == 0 {
2252            if color_mode == ColorMode::Rgb && ch < 3 {
2253                for yy in 0..h {
2254                    for xx in 0..w {
2255                        let src = xx + yy * w;
2256                        let dst = (ox + xx + (yy + oy) * width) * 4;
2257                        if dst + ch < data.len() && src < cdata.len() {
2258                            data[dst + ch] = cdata[src];
2259                        }
2260                    }
2261                }
2262            }
2263            if color_mode == ColorMode::Grayscale && ch < 1 {
2264                for yy in 0..h {
2265                    for xx in 0..w {
2266                        let src = xx + yy * w;
2267                        let dst = (ox + xx + (yy + oy) * width) * 4;
2268                        if dst + 2 < data.len() && src < cdata.len() {
2269                            let value = cdata[src];
2270                            data[dst] = value;
2271                            data[dst + 1] = value;
2272                            data[dst + 2] = value;
2273                        }
2274                    }
2275                }
2276            }
2277            if color_mode == ColorMode::Indexed {
2278                return Err(ReadError::StrictViolation(
2279                    "Indexed pattern color mode not implemented".to_string(),
2280                ));
2281            }
2282        } else if compression_mode == 1 {
2283            let mut temp = DecodeTarget { width: w, height: h, data: vec![0u8; w * h], channels: 1 };
2284            let mut cdata_reader = PsdReader::new(&cdata, None, None);
2285            if color_mode == ColorMode::Rgb && ch < 3 {
2286                read_data_rle(&mut cdata_reader, Some(&mut temp), w, h, 8, 1, &[0], false)?;
2287                copy_channel_to_rgba(&temp, &mut data, width, ox, oy, ch);
2288            }
2289            if color_mode == ColorMode::Grayscale && ch < 1 {
2290                read_data_rle(&mut cdata_reader, Some(&mut temp), w, h, 8, 1, &[0], false)?;
2291                copy_channel_to_rgba(&temp, &mut data, width, ox, oy, 0);
2292                // setup grayscale on the destination region is approximated by
2293                // copying channel 0 into 1 and 2 in copy step below.
2294                copy_channel_to_rgba(&temp, &mut data, width, ox, oy, 1);
2295                copy_channel_to_rgba(&temp, &mut data, width, ox, oy, 2);
2296            }
2297            if color_mode == ColorMode::Indexed {
2298                return Err(ReadError::StrictViolation(
2299                    "Indexed pattern color mode not implemented".to_string(),
2300                ));
2301            }
2302        } else {
2303            return Err(ReadError::StrictViolation(
2304                "Invalid pattern compression mode".to_string(),
2305            ));
2306        }
2307
2308        ch += 1;
2309    }
2310
2311    reader.offset = end;
2312
2313    Ok(PatternInfo {
2314        id,
2315        name,
2316        x,
2317        y,
2318        bounds: PatternBounds {
2319            x: left as f64,
2320            y: top as f64,
2321            w: width as f64,
2322            h: height as f64,
2323        },
2324        data,
2325    })
2326}
2327
2328fn copy_channel_to_rgba(
2329    src: &DecodeTarget,
2330    dst: &mut [u8],
2331    dst_width: usize,
2332    ox: usize,
2333    oy: usize,
2334    offset: usize,
2335) {
2336    let w = src.width;
2337    let h = src.height;
2338    for y in 0..h {
2339        for x in 0..w {
2340            let s = x + y * w;
2341            let d = (ox + x + (y + oy) * dst_width) * 4;
2342            if d + offset < dst.len() && s < src.data.len() {
2343                dst[d + offset] = src.data[s];
2344            }
2345        }
2346    }
2347}
2348
2349#[cfg(test)]
2350mod tests {
2351    use super::*;
2352
2353    #[test]
2354    fn scalar_round_trip_big_endian() {
2355        // Hand-crafted big-endian bytes.
2356        // u8=0x12, i8=-1(0xFF), i16=-2(0xFFFE), u16=0x0102, i32=-3, u32=0x01020304
2357        let buf: Vec<u8> = vec![
2358            0x12, // u8
2359            0xFF, // i8 = -1
2360            0xFF, 0xFE, // i16 = -2
2361            0x01, 0x02, // u16 = 0x0102
2362            0xFF, 0xFF, 0xFF, 0xFD, // i32 = -3
2363            0x01, 0x02, 0x03, 0x04, // u32 = 0x01020304
2364        ];
2365        let mut r = PsdReader::new(&buf, None, None);
2366        assert_eq!(read_uint8(&mut r).unwrap(), 0x12);
2367        assert_eq!(read_int8(&mut r).unwrap(), -1);
2368        assert_eq!(read_int16(&mut r).unwrap(), -2);
2369        assert_eq!(read_uint16(&mut r).unwrap(), 0x0102);
2370        assert_eq!(read_int32(&mut r).unwrap(), -3);
2371        assert_eq!(read_uint32(&mut r).unwrap(), 0x0102_0304);
2372        assert_eq!(r.offset, buf.len());
2373    }
2374
2375    #[test]
2376    fn float_round_trip_big_endian() {
2377        let f32v: f32 = 3.5;
2378        let f64v: f64 = -1234.5678;
2379        let mut buf = Vec::new();
2380        buf.extend_from_slice(&f32v.to_be_bytes());
2381        buf.extend_from_slice(&f64v.to_be_bytes());
2382        let mut r = PsdReader::new(&buf, None, None);
2383        assert_eq!(read_float32(&mut r).unwrap(), f32v);
2384        assert_eq!(read_float64(&mut r).unwrap(), f64v);
2385    }
2386
2387    #[test]
2388    fn uint16_le_differs_from_be() {
2389        let buf = vec![0x01, 0x02];
2390        let mut r = PsdReader::new(&buf, None, None);
2391        assert_eq!(read_uint16_le(&mut r).unwrap(), 0x0201);
2392    }
2393
2394    #[test]
2395    fn fixed_point() {
2396        // 16.16: value 1.5 -> int32 = 1.5 * 65536 = 98304 = 0x00018000
2397        let buf = vec![0x00, 0x01, 0x80, 0x00];
2398        let mut r = PsdReader::new(&buf, None, None);
2399        assert_eq!(read_fixed_point32(&mut r).unwrap(), 1.5);
2400    }
2401
2402    #[test]
2403    fn signature_and_check() {
2404        let buf = b"8BIM".to_vec();
2405        let mut r = PsdReader::new(&buf, None, None);
2406        assert!(valid_signature_at(&r, 0));
2407        check_signature(&mut r, "8BIM", None).unwrap();
2408
2409        let mut r2 = PsdReader::new(&buf, None, None);
2410        let err = check_signature(&mut r2, "8BPS", None).unwrap_err();
2411        assert_eq!(
2412            err,
2413            ReadError::InvalidSignature {
2414                signature: "8BIM".to_string(),
2415                offset: 0
2416            }
2417        );
2418    }
2419
2420    #[test]
2421    fn pascal_string_pad_to_2() {
2422        // length=3, "abc", padTo=2: bytes consumed = 1(len)+3(text)=4, already
2423        // multiple of 2 -> no extra padding. count starts at length+1=4.
2424        let buf = vec![0x03, b'a', b'b', b'c'];
2425        let mut r = PsdReader::new(&buf, None, None);
2426        assert_eq!(read_pascal_string(&mut r, 2).unwrap(), "abc");
2427        assert_eq!(r.offset, 4);
2428    }
2429
2430    #[test]
2431    fn pascal_string_with_padding() {
2432        // length=2, "ab", padTo=4: total with len byte = 3, must pad to 4 -> +1.
2433        let buf = vec![0x02, b'a', b'b', 0x00, 0xFF];
2434        let mut r = PsdReader::new(&buf, None, None);
2435        assert_eq!(read_pascal_string(&mut r, 4).unwrap(), "ab");
2436        // consumed: len(1) + text(2) + pad(1) = 4
2437        assert_eq!(r.offset, 4);
2438    }
2439
2440    #[test]
2441    fn pascal_string_empty() {
2442        // length=0, padTo=4: count starts at 1, pads to 4 -> 3 extra offset bumps.
2443        let buf = vec![0x00, 0x00, 0x00, 0x00];
2444        let mut r = PsdReader::new(&buf, None, None);
2445        assert_eq!(read_pascal_string(&mut r, 4).unwrap(), "");
2446        // len byte read (offset 1) + 3 pad bumps = 4
2447        assert_eq!(r.offset, 4);
2448    }
2449
2450    #[test]
2451    fn unicode_string_with_length() {
2452        // "Hi" + trailing \0 -> length 3 code units, big-endian uint16 each.
2453        let buf = vec![
2454            0x00, 0x00, 0x00, 0x03, // uint32 length = 3
2455            0x00, 0x48, // 'H'
2456            0x00, 0x69, // 'i'
2457            0x00, 0x00, // trailing \0 (dropped)
2458        ];
2459        let mut r = PsdReader::new(&buf, None, None);
2460        assert_eq!(read_unicode_string(&mut r).unwrap(), "Hi");
2461    }
2462
2463    #[test]
2464    fn unicode_string_non_ascii() {
2465        // Cyrillic 'Я' = U+042F
2466        let buf = vec![
2467            0x00, 0x00, 0x00, 0x01, // length 1
2468            0x04, 0x2F, // U+042F
2469        ];
2470        let mut r = PsdReader::new(&buf, None, None);
2471        assert_eq!(read_unicode_string(&mut r).unwrap(), "Я");
2472    }
2473
2474    #[test]
2475    fn unicode_string_surrogate_pair() {
2476        // U+1F600 emoji -> surrogate pair D83D DE00
2477        let buf = vec![
2478            0x00, 0x00, 0x00, 0x02, // length 2 units
2479            0xD8, 0x3D, // high surrogate
2480            0xDE, 0x00, // low surrogate
2481        ];
2482        let mut r = PsdReader::new(&buf, None, None);
2483        assert_eq!(read_unicode_string(&mut r).unwrap(), "😀");
2484    }
2485
2486    #[test]
2487    fn signature_str_is_latin1_codeunits() {
2488        // bytes > 127 must map to their code point, not be UTF-8 decoded.
2489        let buf = vec![0xFF, 0x00, b'A', b'B'];
2490        let mut r = PsdReader::new(&buf, None, None);
2491        let sig = read_signature(&mut r).unwrap();
2492        let chars: Vec<u32> = sig.chars().map(|c| c as u32).collect();
2493        assert_eq!(chars, vec![0xFF, 0x00, 0x41, 0x42]);
2494    }
2495
2496    #[test]
2497    fn read_bytes_recovery_past_end() {
2498        let buf = vec![0x01, 0x02];
2499        let mut r = PsdReader::new(&buf, None, None);
2500        // ask for 4 bytes; only 2 available, not strict -> zero-filled result.
2501        let out = read_bytes(&mut r, 4).unwrap();
2502        assert_eq!(out, vec![0x01, 0x02, 0x00, 0x00]);
2503        assert_eq!(r.offset, 4);
2504    }
2505
2506    #[test]
2507    fn read_bytes_strict_errors() {
2508        let buf = vec![0x01, 0x02];
2509        let mut r = PsdReader::new(&buf, None, None);
2510        r.strict = true;
2511        // strict mode routes through warn_or_throw -> StrictViolation (upstream `throw`).
2512        let err = read_bytes(&mut r, 4).unwrap_err();
2513        assert_eq!(
2514            err,
2515            ReadError::StrictViolation("Reading bytes exceeding buffer length".to_string())
2516        );
2517    }
2518
2519    #[test]
2520    fn section_rounding() {
2521        // length prefix = 3 (uint32 BE), then 3 payload bytes, round=4.
2522        // Payload: read 3 bytes via func. After func offset == end (4+3=7).
2523        // Rounding: length 3 -> 4, end 7 -> 8. Final offset must be 8.
2524        let buf = vec![
2525            0x00, 0x00, 0x00, 0x03, // length = 3
2526            0xAA, 0xBB, 0xCC, // payload (3 bytes)
2527            0xEE, // padding byte to reach rounded end
2528        ];
2529        let mut r = PsdReader::new(&buf, None, None);
2530        let collected: Vec<u8> = read_section(
2531            &mut r,
2532            4,
2533            |reader, left| {
2534                assert_eq!(left(reader), 3);
2535                let a = read_uint8(reader)?;
2536                let b = read_uint8(reader)?;
2537                let c = read_uint8(reader)?;
2538                assert_eq!(left(reader), 0);
2539                Ok(vec![a, b, c])
2540            },
2541            true,
2542            false,
2543        )
2544        .unwrap()
2545        .unwrap();
2546        assert_eq!(collected, vec![0xAA, 0xBB, 0xCC]);
2547        // end was 7, rounded up to 8.
2548        assert_eq!(r.offset, 8);
2549    }
2550
2551    #[test]
2552    fn section_empty_skipped() {
2553        let buf = vec![0x00, 0x00, 0x00, 0x00];
2554        let mut r = PsdReader::new(&buf, None, None);
2555        let res: Option<()> =
2556            read_section(&mut r, 4, |_r, _left| Ok(()), true, false).unwrap();
2557        assert!(res.is_none());
2558    }
2559
2560    #[test]
2561    fn section_eight_bytes() {
2562        // eightBytes: first uint32 must be 0, then real uint32 length.
2563        let buf = vec![
2564            0x00, 0x00, 0x00, 0x00, // high u32 = 0
2565            0x00, 0x00, 0x00, 0x02, // low u32 = 2 (length)
2566            0x11, 0x22, // payload
2567        ];
2568        let mut r = PsdReader::new(&buf, None, None);
2569        let res: Option<u16> = read_section(
2570            &mut r,
2571            1,
2572            |reader, _left| read_uint16(reader),
2573            true,
2574            true,
2575        )
2576        .unwrap();
2577        assert_eq!(res, Some(0x1122));
2578    }
2579
2580    #[test]
2581    fn section_exceeds_file() {
2582        let buf = vec![0x00, 0x00, 0x00, 0x10]; // claims 16 bytes but none follow
2583        let mut r = PsdReader::new(&buf, None, None);
2584        let err = read_section::<(), _>(&mut r, 1, |_r, _l| Ok(()), true, false).unwrap_err();
2585        assert_eq!(err, ReadError::SectionExceedsFileSize);
2586    }
2587
2588    // -----------------------------------------------------------------------
2589    // Real-fixture end-to-end pipeline tests (read_psd).
2590    // -----------------------------------------------------------------------
2591
2592    fn read_fixture(rel: &str) -> crate::psd::Psd {
2593        let path = format!(
2594            "{}/../../test/ag-psd/test/read/{}/src.psd",
2595            env!("CARGO_MANIFEST_DIR"),
2596            rel
2597        );
2598        let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("read {}: {}", path, e));
2599        let opts = ReadOptions::default();
2600        read_psd(&bytes, &opts).unwrap_or_else(|e| panic!("read_psd {}: {:?}", rel, e))
2601    }
2602
2603    fn count_layers(layers: &[Layer]) -> usize {
2604        layers.iter().map(|l| 1 + l.children.as_ref().map_or(0, |c| count_layers(c))).sum()
2605    }
2606
2607    fn any_layer_has_pixels(layers: &[Layer]) -> bool {
2608        layers.iter().any(|l| {
2609            let has = l
2610                .canvas
2611                .as_ref()
2612                .map_or(false, |c| !c.data.is_empty())
2613                || l.image_data.as_ref().map_or(false, |c| !c.data.is_empty());
2614            has || l.children.as_ref().map_or(false, |c| any_layer_has_pixels(c))
2615        })
2616    }
2617
2618    #[test]
2619    fn read_fixture_layers_rgb8() {
2620        let psd = read_fixture("layers");
2621        assert_eq!(psd.width, 300.0);
2622        assert_eq!(psd.height, 200.0);
2623        assert_eq!(psd.color_mode, Some(ColorMode::Rgb));
2624        assert_eq!(psd.bits_per_channel, Some(8.0));
2625        let children = psd.children.as_ref().expect("children");
2626        assert_eq!(children.len(), 3, "top-level children count");
2627        assert!(any_layer_has_pixels(children), "at least one layer has pixel data");
2628        // composite image should be present (not skipped)
2629        assert!(psd.canvas.as_ref().map_or(false, |c| !c.data.is_empty()));
2630    }
2631
2632    #[test]
2633    fn read_fixture_groups_nesting() {
2634        let psd = read_fixture("groups");
2635        assert_eq!(psd.width, 300.0);
2636        assert_eq!(psd.height, 200.0);
2637        assert_eq!(psd.color_mode, Some(ColorMode::Rgb));
2638        let children = psd.children.as_ref().expect("children");
2639        assert_eq!(children.len(), 2, "top-level children count (2 incl. group)");
2640        // total layers across the tree should exceed top-level count (nesting).
2641        assert!(count_layers(children) >= 3);
2642        assert!(any_layer_has_pixels(children));
2643    }
2644
2645    #[test]
2646    fn read_fixture_just_bg_no_layers() {
2647        let psd = read_fixture("just-bg");
2648        assert_eq!(psd.width, 100.0);
2649        assert_eq!(psd.height, 100.0);
2650        assert_eq!(psd.color_mode, Some(ColorMode::Rgb));
2651        let count = psd.children.as_ref().map_or(0, |c| c.len());
2652        assert_eq!(count, 0, "background-only document has no layer children");
2653        assert!(psd.canvas.as_ref().map_or(false, |c| !c.data.is_empty()));
2654    }
2655
2656    #[test]
2657    fn new_with_offset_window() {
2658        let buf = vec![0x00, 0x11, 0x22, 0x33, 0x44];
2659        let mut r = PsdReader::new(&buf, Some(1), Some(2));
2660        // window is [0x11, 0x22]; cursor starts at 0 relative to window.
2661        assert_eq!(read_uint8(&mut r).unwrap(), 0x11);
2662        assert_eq!(read_uint8(&mut r).unwrap(), 0x22);
2663        assert!(read_uint8(&mut r).is_err());
2664    }
2665}