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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
use crate::{
catalog::{MetadataStream, OptionalContent},
error::PdfResult,
filter::flate::BitsPerComponent,
objects::{Dictionary, Object},
resources::graphics_state_parameters::RenderingIntent,
stream::Stream,
Resolve,
};
use super::OpenPrepressInterface;
#[derive(Debug)]
pub struct ImageXObject {
/// The width of the image, in samples
width: u32,
/// The height of the image, in samples
height: u32,
/// The colour space in which image samples shall be specified; it can be
/// any type of colour space except Pattern.
///
/// If the image uses the JPXDecode filter, this entry may be present:
/// * If ColorSpace is present, any colour space specifications in the
/// JPEG2000 data shall be ignored.
/// * If ColorSpace is absent, the colour space specifications in the
/// JPEG2000 data shall be used. The Decode array shall also be
/// ignored unless ImageMask is true
// todo: type of this
color_space: Option<Object>,
/// The number of bits used to represent each colour component. Only a
/// single value shall be specified; the number of bits shall be the same
/// for all colour components. The value shall be 1, 2, 4, 8, or 16. If
/// ImageMask is true, this entry is optional, but if specified, its value
/// shall be 1.
///
/// If the image stream uses a filter, the value of BitsPerComponent shall
/// be consistent with the size of the data samples that the filter delivers.
/// In particular, a CCITTFaxDecode or JBIG2Decode filter shall always deliver
/// 1-bit samples, a RunLengthDecode or DCTDecode filter shall always deliver
/// 8-bit samples, and an LZWDecode or FlateDecode filter shall deliver
/// samples of a specified size if a predictor function is used.
///
/// If the image stream uses the JPXDecode filter, this entry is optional and
/// shall be ignored if present. The bit depth is determined by the conforming
/// reader in the process of decoding the JPEG2000 image
bits_per_component: Option<BitsPerComponent>,
/// The name of a colour rendering intent to be used in rendering the image
///
/// Default value: the current rendering intent in the graphics state
intent: Option<RenderingIntent>,
/// A flag indicating whether the image shall be treated as an image mask
///
/// If this flag is true, the value of BitsPerComponent shall be 1 and Mask
/// and ColorSpace shall not be specified; unmasked areas shall be painted
/// using the current nonstroking colour
///
/// Default value: false
image_mask: bool,
/// An image XObject defining an image mask to be applied to this image, or an
/// array specifying a range of colours to be applied to it as a colour key
/// mask. If ImageMask is true, this entry shall not be present
mask: Option<ImageMask>,
/// An array of numbers describing how to map image samples into the range of
/// values appropriate for the image's colour space. If ImageMask is true, the
/// array shall be either [0 1] or [1 0]; otherwise, its length shall be twice
/// the number of colour components required by ColorSpace. If the image uses
/// the JPXDecode filter and ImageMask is false, Decode shall be ignored by a
/// conforming reader
decode: Option<Vec<f32>>,
/// A flag indicating whether image interpolation shall be performed by a conforming
/// reader
///
/// Default value: false
interpolate: bool,
/// An array of alternate image dictionaries for this image. The order of elements
/// within the array shall have no significance. This entry shall not be present
/// in an image XObject that is itself an alternate image
alternates: Option<Vec<AlternateImage>>,
/// A subsidiary image XObject defining a softmask image that shall be used as a source
/// of mask shape or mask opacity values in the transparent imaging model. The alpha
/// source parameter in the graphics state determines whether the mask values shall be
/// interpreted as shape or opacity.
///
/// If present, this entry shall override the current soft mask in the graphics state,
/// as well as the image's Mask entry, if any. However, the other transparency-related
/// graphics state parameters -- blend mode and alpha constant -- shall remain in effect.
/// If SMask is absent, the image shall have no associated soft mask (although the current
/// soft mask in the graphics state may still apply)
s_mask: Option<SoftMask>,
/// A code specifying how soft-mask information encoded with image samples shall be used:
/// 0 If present, encoded soft-mask image information shall be ignored.
/// 1 The image's data stream includes encoded soft-mask values. A conforming reader
/// may create a soft-mask image from the information to be used as a source of mask
/// shape or mask opacity in the transparency imaging model.
/// 2 The image's data stream includes colour channels that have been preblended with a
/// background; the image data also includes an opacity channel. A conforming reader
/// may create a soft-mask image with a Matte entry from the opacity channel information
/// to be used as a source of mask shape or mask opacity in the transparency model.
///
/// If this entry has a nonzero value, SMask shall not be specified.
///
/// Default value: 0.
s_mask_in_data: i32,
/// The name by which this image XObject is referenced in the XObject subdictionary of the
/// current resource dictionary.
///
/// This entry is obsolescent and shall no longer be used
name: Option<String>,
/// The integer key of the image's entry in the structural parent tree
struct_parent: Option<i32>,
/// The digital identifier of the image's parent Web Capture content set
id: Option<String>,
/// An OPI version dictionary for the image. If ImageMask is true, this entry shall be ignored
opi: Option<OpenPrepressInterface>,
/// A metadata stream containing metadata for the image
metadata: Option<MetadataStream>,
/// An optional content group or optional content membership dictionary, specifying the optional
/// content properties for this image XObject. Before the image is processed by a conforming reader,
/// its visibility shall be determined based on this entry. If it is determined to be invisible,
/// the entire image shall be skipped, as if there were no Do operator to invoke it
oc: Option<OptionalContent>,
}
impl ImageXObject {
pub fn from_stream(mut stream: Stream, resolver: &mut dyn Resolve) -> PdfResult<Self> {
let dict = &mut stream.dict.other;
let width = dict.expect_unsigned_integer("Width", resolver)?;
let height = dict.expect_unsigned_integer("Height", resolver)?;
let color_space = dict.get_object("ColorSpace", resolver)?;
let bits_per_component = dict
.get_integer("BitsPerComponent", resolver)?
.map(BitsPerComponent::from_integer)
.transpose()?;
let intent = dict
.get_name("Intent", resolver)?
.as_deref()
.map(RenderingIntent::from_str)
.transpose()?;
let image_mask = dict.get_bool("ImageMask", resolver)?.unwrap_or(false);
let mask = dict
.get_object("Mask", resolver)?
.map(|obj| ImageMask::from_obj(obj, resolver))
.transpose()?;
let decode = dict
.get_arr("Decode", resolver)?
.map(|arr| {
arr.into_iter()
.map(|obj| resolver.assert_number(obj))
.collect::<PdfResult<Vec<f32>>>()
})
.transpose()?;
let interpolate = dict.get_bool("Interpolate", resolver)?.unwrap_or(false);
let alternates = dict
.get_arr("Alternates", resolver)?
.map(|arr| {
arr.into_iter()
.map(|obj| AlternateImage::from_stream(resolver.assert_stream(obj)?, resolver))
.collect::<PdfResult<Vec<AlternateImage>>>()
})
.transpose()?;
let s_mask = dict
.get_stream("SMask", resolver)?
.map(|stream| SoftMask::from_stream(stream, resolver))
.transpose()?;
let s_mask_in_data = dict.get_integer("SMaskInData", resolver)?.unwrap_or(0);
let name = dict.get_name("Name", resolver)?;
let struct_parent = dict.get_integer("StructParent", resolver)?;
let id = dict.get_string("ID", resolver)?;
let opi = dict
.get_dict("OPI", resolver)?
.map(|dict| OpenPrepressInterface::from_dict(dict, resolver))
.transpose()?;
let metadata = dict
.get_stream("Metadata", resolver)?
.map(|stream| MetadataStream::from_stream(stream, resolver))
.transpose()?;
let oc = dict
.get_dict("OC", resolver)?
.map(|dict| OptionalContent::from_dict(dict, resolver))
.transpose()?;
Ok(Self {
width,
height,
color_space,
bits_per_component,
intent,
image_mask,
mask,
decode,
interpolate,
alternates,
s_mask,
s_mask_in_data,
name,
struct_parent,
id,
opi,
metadata,
oc,
})
}
}
#[derive(Debug)]
pub struct SoftMask {
/// If a Matte entry is present, shall be the same as the Width value of the parent
/// image; otherwise independent of it. Both images shall be mapped to the unit square
/// in user space (as are all images), regardless of whether the samples coincide individually.
width: u32,
/// Same considerations as for Width.
height: u32,
/// Required; shall be DeviceGray.
// todo: color space
color_space: Object,
bits_per_component: BitsPerComponent,
/// Default value: [0 1]
decode: Vec<f32>,
interpolate: Option<bool>,
/// An array of component values specifying the matte colour with which the image data in
/// the parent image shall have been preblended. The array shall consist of n numbers, where
/// n is the number of components in the colour space specified by the ColorSpace entry in
/// the parent image's image dictionary; the numbers shall be valid colour components in that
/// colour space. If this entry is absent, the image data shall not be preblended
// todo: type
matte: Option<Vec<Object>>,
stream: Stream,
}
impl SoftMask {
pub fn from_stream(mut stream: Stream, resolver: &mut dyn Resolve) -> PdfResult<Self> {
let dict = &mut stream.dict.other;
let width = dict.expect_unsigned_integer("Width", resolver)?;
let height = dict.expect_unsigned_integer("Height", resolver)?;
let color_space = dict.expect_object("ColorSpace", resolver)?;
let bits_per_component =
BitsPerComponent::from_integer(dict.expect_integer("BitsPerComponent", resolver)?)?;
let decode = dict
.get_arr("Decode", resolver)?
.map(|arr| {
arr.into_iter()
.map(|obj| resolver.assert_number(obj))
.collect::<PdfResult<Vec<f32>>>()
})
.transpose()?
.unwrap_or_else(|| vec![0.0, 1.0]);
let interpolate = dict.get_bool("Interpolate", resolver)?;
let matte = dict.get_arr("Matte", resolver)?;
Ok(Self {
width,
height,
color_space,
bits_per_component,
decode,
interpolate,
matte,
stream,
})
}
}
#[derive(Debug)]
pub struct AlternateImage {
/// The image XObject for the alternate image
image: ImageXObject,
/// A flag indicating whether this alternate image shall be the default
/// version to be used for printing
///
/// At most one alternate for a given base image shall be so designated.
/// If no alternate has this entry set to true, the base image shall be used
/// for printing by a conforming reader
default_for_printing: Option<bool>,
/// An optional content group or optional content membership dictionary
/// that facilitates the selection of which alternate image to use
// todo: optional content membership
oc: Option<OptionalContentGroup>,
}
impl AlternateImage {
pub fn from_stream(mut stream: Stream, resolver: &mut dyn Resolve) -> PdfResult<Self> {
let dict = &mut stream.dict.other;
let default_for_printing = dict.get_bool("DefaultForPrinting", resolver)?;
let oc = dict
.get_dict("OC", resolver)?
.map(|dict| OptionalContentGroup::from_dict(dict, resolver))
.transpose()?;
let image = ImageXObject::from_stream(stream, resolver)?;
Ok(Self {
image,
default_for_printing,
oc,
})
}
}
#[derive(Debug)]
pub struct OptionalContentGroup;
impl OptionalContentGroup {
pub fn from_dict(_dict: Dictionary, _resolver: &mut dyn Resolve) -> PdfResult<Self> {
todo!()
}
}
#[derive(Debug)]
pub struct ImageMask;
impl ImageMask {
pub fn from_obj(_obj: Object, _resolver: &mut dyn Resolve) -> PdfResult<Self> {
todo!()
}
}