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}