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;