Skip to main content

pdfrum_edit/
error.rs

1//! What can go wrong while writing a document back out.
2
3use std::io;
4
5use pdfrum_common::PageIndex;
6
7/// A failure that stops the editor producing output.
8///
9/// Damage in the *input* is not an error here: a broken object silently
10/// vanishes from the output the way the C++ writer drops it, and every such
11/// recovery is recorded as a [`pdfrum_common::Diagnostic`]. `Err` is reserved
12/// for "cannot continue".
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum Error {
16    /// The sink refused the bytes.
17    #[error("write failed: {0}")]
18    Io(#[from] io::Error),
19
20    /// The document declares `/Encrypt`, this reader derived no key for it
21    /// (an `/Identity` crypt filter, or a handler opened as
22    /// [`pdfrum_crypt::SecurityHandler::Identity`]), and `remove_security`
23    /// was not set. Re-declaring a cipher over plaintext would produce a file
24    /// nothing could open.
25    #[error(
26        "cannot save an encrypted document without its key; \
27         set `SaveOptions::remove_security` to save it decrypted"
28    )]
29    EncryptedSaveUnsupported,
30    /// A password given for a new encryption is not valid UTF-8, or does
31    /// not survive `SASLprep` (ISO 32000-2 §7.6.4.3.3).
32    #[error("a password for encryption must be text")]
33    PasswordNotText,
34    /// The operating system's cryptographic generator is unavailable, so the
35    /// file key and AES vectors a new encryption needs cannot be drawn.
36    /// Only an encrypting save can raise this; every other save needs no
37    /// randomness it cannot derive.
38    #[error("the operating system's random generator is unavailable")]
39    NoEntropy,
40
41    /// A document with no usable catalog cannot be the destination of an
42    /// import: there is nowhere to attach the pages.
43    #[error("the destination document has no catalog to import into")]
44    NoDestinationCatalog,
45
46    /// A page index named by an import is outside the source document.
47    #[error("page index {0} is outside the source document")]
48    PageIndexOutOfRange(PageIndex),
49
50    /// A page-range string the grammar of ISO 32000 viewers accepts could not
51    /// be parsed; the C++ treats one bad entry as discarding everything.
52    #[error("malformed page range")]
53    BadPageRange,
54
55    /// N-up was asked for a grid or sheet size with a zero dimension.
56    #[error("N-up needs a non-zero grid and sheet size")]
57    BadNupParams,
58
59    /// An SVG would not resolve. Only produced with the `svg-import`
60    /// feature, whose ingestion parses it.
61    #[cfg(feature = "svg-import")]
62    #[error("the SVG would not resolve: {0}")]
63    Svg(#[source] usvg::Error),
64
65    /// The font program could not be subset.
66    #[error("font subsetting failed: {0}")]
67    Subset(String),
68
69    /// The bytes are not a TrueType, OpenType, or Type 1 font program.
70    #[error("unrecognised font program")]
71    UnrecognisedFontProgram,
72
73    /// The program parsed but declares no glyphs.
74    #[error("font program has no glyphs")]
75    EmptyFontProgram,
76
77    /// A caller-supplied `/ToUnicode` CMap was empty.
78    ///
79    /// The font it would write would carry no statement of what its codes
80    /// mean. A caller who wanted a *generated* `/ToUnicode` should ask for
81    /// [`FontEncoding::Composite`](crate::FontEncoding::Composite) instead.
82    #[error("the /ToUnicode CMap is empty")]
83    EmptyToUnicodeCMap,
84
85    /// A caller-supplied `/CIDToGIDMap` was empty, or was not a whole number
86    /// of big-endian `u16` entries.
87    ///
88    /// `[oracle-bug]` ISO 32000-1 §9.7.4.2 defines `/CIDToGIDMap` as a stream
89    /// of two-byte glyph indices, so a half-entry is not a map. Both the
90    /// empty and the odd-length case are rejected here, before any of it is
91    /// written.
92    //
93    // [oracle-bug] `FPDFText_LoadCidType2Font` rejects the empty case
94    // (`fpdfsdk/fpdf_edittext.cpp:488-491`) but not the odd-length one:
95    // `LoadCustomCompositeFont` walks `i += 2` to
96    // `cid_to_gid_map_span.size()` and takes
97    // `cid_to_gid_map_span.subspan(i).first<2u>()` (`:308-313`), so a final
98    // one-byte remainder asks a 1-element span for its first 2 elements —
99    // a bounds `CHECK` in `pdfium::span`, i.e. an abort rather than a
100    // rejection. An error is the same answer without the crash. pdf.js is the
101    // reader that depends on the map: `readCidToGidMap` pairs the bytes as
102    // `(glyphsData[j++] << 8) | glyphsData[j]` over the map's whole length
103    // (`src/core/evaluator.js:4103-4116`), so a trailing half-entry reads
104    // `undefined` as its low byte and yields a glyph index 256 times too
105    // large for the last CID.
106    #[error("the /CIDToGIDMap is {0} bytes; it must be a non-empty whole number of 2-byte entries")]
107    BadCidToGidMap(usize),
108
109    /// The bytes are not a JPEG or JPEG 2000 codestream a PDF may hold.
110    ///
111    /// Raised when no JPEG header can be read, when the component count is
112    /// not 1, 3 or 4, or when the sample precision is not 1, 2, 4, 8 or 16.
113    /// The oracle reaches the same outcome by producing no stream at all;
114    /// this is the same refusal with the reason attached.
115    #[error("not a JPEG or JPEG 2000 image")]
116    UnrecognisedImageData,
117
118    /// An image was asked for with a zero width or height.
119    #[error("an image needs a non-zero width and height")]
120    EmptyImage,
121
122    /// The sample buffer is not the length the dimensions and pixel format
123    /// require.
124    ///
125    /// The oracle cannot reach this: its API takes a bitmap object that
126    /// already knows its own pitch, so a short buffer is not expressible.
127    /// Loose bytes are, and reading past them is not an option.
128    #[error("image data is {found} bytes; {expected} are needed")]
129    ImageDataLength {
130        /// Bytes the dimensions and format require.
131        expected: usize,
132        /// Bytes the caller supplied.
133        found: usize,
134    },
135
136    /// A drawing refused a character the font has no glyph for.
137    ///
138    /// Raised by [`EmbeddedFont::encode_checked`](crate::EmbeddedFont::encode_checked)
139    /// and by the facade's canvas, which encodes through it. A blank where a
140    /// glyph should be is a worse answer than a refusal, because nothing
141    /// downstream can tell the two apart.
142    #[error("cannot draw the text: {0}")]
143    MissingGlyph(#[from] crate::MissingGlyph),
144
145    /// A page written inline in its parent's `/Kids` has no object of its own
146    /// for an appended content stream to reach.
147    #[error("page {0} has no object of its own to draw on")]
148    InlinePage(PageIndex),
149
150    /// An object the writer needed could not be fetched or made sense of.
151    #[error("object model: {0}")]
152    Object(#[from] pdfrum_object::Error),
153}