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