Skip to main content

djvu_rs/
error.rs

1//! Typed error hierarchy for the djvu-rs crate.
2//!
3//! This module provides:
4//! - [`DjVuError`] — the new top-level error type for phase-1+ code
5//! - [`IffError`] — errors from the new IFF container parser
6//! - [`BzzError`] — errors from the BZZ decompressor (phase 2a)
7//! - [`Jb2Error`], [`Iw44Error`] — stubs for future decoders
8//! - `LegacyError` — the original error type, kept for backward compatibility
9//! - `TextError` — errors from the text layer parser (phase 4, see `text` module)
10//! - `AnnotationError` — errors from the annotation parser (phase 4, see `annotation` module)
11
12#[cfg(not(feature = "std"))]
13use alloc::{borrow::Cow, string::String};
14
15// ---- New phase-1 typed errors -----------------------------------------------
16
17/// Top-level error type for all DjVu decoding operations.
18#[derive(Debug, thiserror::Error)]
19pub enum DjVuError {
20    /// An error in the IFF container format.
21    #[error("IFF error: {0}")]
22    Iff(#[from] IffError),
23
24    /// A JB2 bitonal image decoding error (stub).
25    #[error("JB2 error: {0}")]
26    Jb2(#[from] Jb2Error),
27
28    /// An IW44 wavelet image decoding error (stub).
29    #[error("IW44 error: {0}")]
30    Iw44(#[from] Iw44Error),
31
32    /// A BZZ compression decoding error.
33    #[error("BZZ error: {0}")]
34    Bzz(#[from] BzzError),
35
36    /// A page number was not found in the document.
37    #[error("page {0} not found")]
38    PageNotFound(usize),
39
40    /// The document structure is invalid or unexpected.
41    #[error("invalid structure: {0}")]
42    InvalidStructure(&'static str),
43
44    /// A feature or format variant that is not yet supported.
45    #[error("unsupported: {0}")]
46    #[cfg(feature = "std")]
47    Unsupported(std::borrow::Cow<'static, str>),
48    /// A feature or format variant that is not yet supported.
49    #[error("unsupported: {0}")]
50    #[cfg(not(feature = "std"))]
51    Unsupported(Cow<'static, str>),
52
53    /// An I/O error (only available with the `std` feature).
54    #[cfg(feature = "std")]
55    #[error("I/O error: {0}")]
56    Io(#[from] std::io::Error),
57}
58
59/// Errors that can occur while parsing the IFF container.
60#[derive(Debug, thiserror::Error, PartialEq, Eq)]
61pub enum IffError {
62    /// Input data is too short to contain a valid IFF file.
63    #[error("input is too short to be a valid IFF file")]
64    TooShort,
65
66    /// The `AT&T` magic bytes were not found at the start of the file.
67    #[error("bad magic bytes: expected AT&T, got {got:?}")]
68    BadMagic { got: [u8; 4] },
69
70    /// The FORM type identifier is not a recognised DjVu type.
71    ///
72    /// Note: this is *not* an error — callers may encounter unknown form types
73    /// in bundled documents and should handle them gracefully.
74    #[error("unknown FORM type: {id:?}")]
75    UnknownFormType { id: [u8; 4] },
76
77    /// A chunk header claims more bytes than are available in the buffer.
78    #[error(
79        "chunk {:?} claims {} bytes but only {} are available",
80        id,
81        claimed,
82        available
83    )]
84    ChunkTooLong {
85        id: [u8; 4],
86        claimed: u32,
87        available: usize,
88    },
89
90    /// The input ended unexpectedly in the middle of a chunk.
91    #[error("unexpected end of input (truncated IFF data)")]
92    Truncated,
93}
94
95/// JB2 bitonal image decoding errors.
96#[derive(Debug, thiserror::Error, PartialEq, Eq)]
97pub enum Jb2Error {
98    /// Input ended before the JB2 stream was complete.
99    #[error("JB2 stream is truncated")]
100    Truncated,
101
102    /// A flag bit in the image/dict header was set when it must be zero.
103    #[error("JB2: bad flag bit in header")]
104    BadHeaderFlag,
105
106    /// The inherited dictionary length exceeds the shared dictionary size.
107    #[error("JB2: inherited dict length exceeds shared dict size")]
108    InheritedDictTooLarge,
109
110    /// The stream references a shared dictionary but none was provided.
111    #[error("JB2: stream requires shared dict but none provided")]
112    MissingSharedDict,
113
114    /// Image dimensions exceed the safety limit (~64M pixels).
115    #[error("JB2: image dimensions too large")]
116    ImageTooLarge,
117
118    /// A record references a dictionary symbol but the dictionary is empty.
119    #[error("JB2: dict reference with empty dict")]
120    EmptyDictReference,
121
122    /// A decoded symbol index is out of range for the current dictionary.
123    #[error("JB2: decoded symbol index out of dictionary range")]
124    InvalidSymbolIndex,
125
126    /// An unrecognized record type was encountered in the image stream.
127    #[error("JB2: unknown record type")]
128    UnknownRecordType,
129
130    /// An unexpected record type was encountered in a dictionary stream.
131    #[error("JB2: unexpected record type in dict stream")]
132    UnexpectedDictRecordType,
133
134    /// The ZP arithmetic coder could not be initialized (insufficient input).
135    #[error("JB2: insufficient data to initialize ZP coder")]
136    ZpInitFailed,
137
138    /// Stream contains more records than the safety limit allows.
139    #[error("JB2: record count exceeds safety limit")]
140    TooManyRecords,
141}
142
143/// IW44 wavelet image decoding errors.
144#[derive(Debug, thiserror::Error, PartialEq, Eq)]
145pub enum Iw44Error {
146    /// Input ended before the IW44 stream was complete.
147    #[error("IW44 stream is truncated")]
148    Truncated,
149
150    /// The IW44 stream contains invalid data.
151    #[error("IW44 stream contains invalid data")]
152    Invalid,
153
154    /// A BG44/FG44/TH44 chunk is too short (fewer than 2 bytes).
155    #[error("IW44 chunk is too short")]
156    ChunkTooShort,
157
158    /// The first chunk header is too short (needs at least 9 bytes).
159    #[error("IW44 first chunk header too short (need ≥ 9 bytes)")]
160    HeaderTooShort,
161
162    /// Image width or height is zero.
163    #[error("IW44 image has zero dimension")]
164    ZeroDimension,
165
166    /// Image dimensions exceed the safety limit.
167    #[error("IW44 image dimensions too large")]
168    ImageTooLarge,
169
170    /// A subsequent chunk was encountered before the first chunk.
171    #[error("IW44 subsequent chunk received before first chunk")]
172    MissingFirstChunk,
173
174    /// The subsample parameter must be >= 1.
175    #[error("IW44 subsample must be >= 1")]
176    InvalidSubsample,
177
178    /// No codec has been initialized (no chunks decoded yet).
179    #[error("IW44 codec not yet initialized")]
180    MissingCodec,
181
182    /// The ZP arithmetic coder stream is too short.
183    #[error("IW44 ZP coder stream too short")]
184    ZpTooShort,
185}
186
187/// BZZ compression decoding errors.
188#[derive(Debug, thiserror::Error, PartialEq, Eq)]
189pub enum BzzError {
190    /// Input is too short to be a valid BZZ stream (fewer than 2 bytes).
191    #[error("BZZ input is too short")]
192    TooShort,
193
194    /// The block size field in the BZZ stream is invalid or out of range.
195    #[error("BZZ stream contains an invalid block size")]
196    InvalidBlockSize,
197
198    /// The BWT sort index embedded in the stream is out of range.
199    #[error("BZZ stream contains an invalid BWT index")]
200    InvalidBwtIndex,
201
202    /// The ZP arithmetic coder encountered an error.
203    #[error("ZP coder error in BZZ stream")]
204    ZpError,
205
206    /// The BWT block did not contain an end-of-block marker.
207    #[error("BZZ block is missing the end-of-block marker")]
208    MissingMarker,
209}
210
211// ---- Legacy error type (kept for backward compatibility) --------------------
212
213/// Original error type used by the legacy implementation.
214///
215/// Kept at `crate::error::LegacyError` (and re-exported as `crate::Error`)
216/// so that djvu-rs and other dependents continue to compile.
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub enum LegacyError {
219    /// Input data is shorter than expected.
220    UnexpectedEof,
221    /// A required magic number or tag was not found.
222    InvalidMagic,
223    /// A chunk or field has an invalid length.
224    InvalidLength,
225    /// A required chunk is missing.
226    MissingChunk(&'static str),
227    /// An unsupported feature or version was encountered.
228    Unsupported(&'static str),
229    /// Generic format violation.
230    FormatError(String),
231}
232
233impl core::fmt::Display for LegacyError {
234    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
235        match self {
236            LegacyError::UnexpectedEof => write!(f, "unexpected end of input"),
237            LegacyError::InvalidMagic => write!(f, "invalid magic number"),
238            LegacyError::InvalidLength => write!(f, "invalid length"),
239            LegacyError::MissingChunk(id) => write!(f, "missing required chunk: {}", id),
240            LegacyError::Unsupported(msg) => write!(f, "unsupported: {}", msg),
241            LegacyError::FormatError(msg) => write!(f, "format error: {}", msg),
242        }
243    }
244}
245
246#[cfg(feature = "std")]
247impl std::error::Error for LegacyError {}
248
249/// Alias for [`LegacyError`] at the path `crate::error::Error`.
250///
251/// This allows the legacy modules (document.rs, render.rs) which use
252/// `crate::error::Error` to continue resolving correctly.
253pub use LegacyError as Error;