Skip to main content

pdf_manip/
error.rs

1//! Error types for `pdf-manip` operations.
2
3// Copyright (c) 2026 Innovation Trigger B.V.
4//
5// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
6// the PDFluent Commercial Licence. See the LICENSE file in this repository --
7// that file travels with the copy you received, which a URL does not.
8
9use thiserror::Error;
10
11/// Errors returned by the `pdf-manip` crate while performing page-level
12/// operations (merge, split, extract, rotate, watermark, encrypt, etc.).
13///
14/// These bubble up through the `pdfluent` facade as `Error::*` variants —
15/// customers will normally see them indirectly. Variants with explicit
16/// limit fields (image size, decompression cap) describe safety guards
17/// that refused to proceed; the source document is not modified.
18#[derive(Debug, Error)]
19pub enum ManipError {
20    /// The underlying PDF byte stream could not be parsed (malformed
21    /// xref, truncated stream, unsupported construct). The wrapped
22    /// `lopdf::Error` carries the detail.
23    #[error("PDF error: {0}")]
24    Pdf(#[from] lopdf::Error),
25
26    /// An I/O error while reading the source or writing the result.
27    #[error("IO error: {0}")]
28    Io(#[from] std::io::Error),
29
30    /// A page index passed to a manipulation routine does not exist in
31    /// the document. Fields: `(requested_index, total_pages)`. Page
32    /// indexes at this layer are 0-based; the public facade translates
33    /// to 1-based.
34    #[error("page index {0} out of range (document has {1} pages)")]
35    PageOutOfRange(usize, usize),
36
37    /// A range-based operation (extract, split) received an empty range.
38    /// Returned eagerly to surface the no-op rather than silently produce
39    /// an empty document.
40    #[error("empty page range")]
41    EmptyPageRange,
42
43    /// Setting up an encryption pipeline failed (key derivation, cipher
44    /// initialization, or invalid permission flag combination). The
45    /// wrapped string identifies the failing step.
46    #[error("encryption error: {0}")]
47    Encryption(String),
48
49    /// Decryption failed. Either the supplied password is wrong, the
50    /// document uses an algorithm this build does not support (RC4 in a
51    /// no-RC4 build), or the encryption dictionary is malformed.
52    #[error("decryption failed: wrong password or unsupported algorithm")]
53    DecryptionFailed,
54
55    /// The document advertises an encryption algorithm that the SDK
56    /// cannot read. The wrapped string identifies the algorithm
57    /// (e.g. `"V=4 RC4 with custom CFM"`).
58    #[error("unsupported encryption: {0}")]
59    UnsupportedEncryption(String),
60
61    /// A bookmark/outline operation received an invalid bookmark
62    /// description (missing title, invalid destination, malformed tree).
63    #[error("invalid bookmark: {0}")]
64    InvalidBookmark(String),
65
66    /// A watermark operation could not be applied (font/image resolution
67    /// failure, invalid coordinates, malformed text).
68    #[error("watermark error: {0}")]
69    Watermark(String),
70
71    /// An image operation (insertion, replacement, watermark image)
72    /// failed before the page was modified.
73    #[error("image error: {0}")]
74    Image(String),
75
76    /// An image operation refused to proceed because the requested image
77    /// would exceed the configured allocation safety limits.
78    ///
79    /// Allocation = `width * height * bytes_per_pixel`. The guards exist
80    /// to prevent denial-of-service via crafted PDFs that trigger huge
81    /// pixel-buffer allocations. If you genuinely need a larger image,
82    /// raise the limit in the operation's options or pre-resize the
83    /// image before insertion.
84    #[error(
85        "image {width}x{height} exceeds pdf-manip safety limits (max dimension {max_dimension}, max allocation {max_allocation_bytes} bytes at {bytes_per_pixel} bytes/pixel)"
86    )]
87    ImageTooLarge {
88        /// Image width in pixels as decoded from the source.
89        width: u32,
90        /// Image height in pixels as decoded from the source.
91        height: u32,
92        /// Pixel-format depth (e.g. 4 for RGBA8) used to compute the
93        /// allocation estimate.
94        bytes_per_pixel: u32,
95        /// The dimension cap (applied to both width and height) that the
96        /// image violated.
97        max_dimension: u32,
98        /// The total-allocation cap in bytes that the image would have
99        /// exceeded.
100        max_allocation_bytes: usize,
101    },
102
103    /// A FlateDecode-compressed stream expanded past the configured
104    /// decompression limit. Like [`ImageTooLarge`](Self::ImageTooLarge)
105    /// this is a denial-of-service guard. The `u64` is the byte limit
106    /// that was hit.
107    #[error("decompressed FlateDecode stream exceeds {0}-byte limit")]
108    DecompressionLimitExceeded(u64),
109
110    /// The requested bold or italic variant font is not embedded in the document.
111    ///
112    /// `set_text_run_style` only swaps to fonts that are already present in the
113    /// xref. Synthetic bold (stroke-and-fill) and system-font injection are
114    /// explicitly forbidden. The caller must embed the variant before styling.
115    ///
116    /// `available_variants` lists the BaseFont names of all variants of the same
117    /// family that *are* embedded, so the caller can surface actionable guidance.
118    #[error(
119        "font variant not embedded: cannot apply {requested:?} (current font {current:?}); \
120         available embedded variants: {available_variants:?}"
121    )]
122    FontVariantNotEmbedded {
123        /// BaseFont name of the current (pre-swap) font.
124        current: String,
125        /// BaseFont name of the desired variant that was not found.
126        requested: String,
127        /// All embedded font variants belonging to the same family.
128        available_variants: Vec<String>,
129    },
130
131    /// A non-categorised manipulation failure. Reserved for cases the
132    /// more specific variants do not cover; the message describes the
133    /// situation.
134    #[error("{0}")]
135    Other(String),
136}
137
138/// Convenience `Result` alias for fallible `pdf-manip` operations.
139pub type Result<T> = std::result::Result<T, ManipError>;