pdf_writer/
xobject.rs

1use super::*;
2use crate::object::TextStrLike;
3use crate::types::RenderingIntent;
4
5/// Writer for an _image XObject stream_.
6///
7/// This struct is created by [`Chunk::image_xobject`].
8pub struct ImageXObject<'a> {
9    stream: Stream<'a>,
10}
11
12impl<'a> ImageXObject<'a> {
13    /// Create a new image stream writer.
14    pub(crate) fn start(mut stream: Stream<'a>) -> Self {
15        stream.pair(Name(b"Type"), Name(b"XObject"));
16        stream.pair(Name(b"Subtype"), Name(b"Image"));
17        Self { stream }
18    }
19
20    /// Write the `/Width` attribute.
21    pub fn width(&mut self, width: i32) -> &mut Self {
22        self.pair(Name(b"Width"), width);
23        self
24    }
25
26    /// Write the `/Height` attribute.
27    pub fn height(&mut self, height: i32) -> &mut Self {
28        self.pair(Name(b"Height"), height);
29        self
30    }
31
32    /// Start writing the `/ColorSpace` attribute.
33    ///
34    /// Required for all images except if using the `JPXDecode` filter.
35    /// If this is an image soft mask, the color space must be `DeviceGray`.
36    /// Must not be `Pattern`.
37    pub fn color_space(&mut self) -> ColorSpace<'_> {
38        self.insert(Name(b"ColorSpace")).start()
39    }
40
41    /// Write the `/ColorSpace` attribute as a name from the resource dictionary.
42    ///
43    /// Required for all images except if using the `JPXDecode` filter.
44    /// If this is an image soft mask, the color space must be `DeviceGray`.
45    /// Must not be `Pattern`.
46    pub fn color_space_name(&mut self, name: Name) -> &mut Self {
47        self.pair(Name(b"ColorSpace"), name);
48        self
49    }
50
51    /// Write the `/BitsPerComponent` attribute. Required.
52    ///
53    /// Required for all images except if using the `JPXDecode` filter.
54    pub fn bits_per_component(&mut self, bits: i32) -> &mut Self {
55        self.pair(Name(b"BitsPerComponent"), bits);
56        self
57    }
58
59    /// Write the `/Intent` attribute. PDF 1.1+.
60    pub fn intent(&mut self, intent: RenderingIntent) -> &mut Self {
61        self.pair(Name(b"Intent"), intent.to_name());
62        self
63    }
64
65    /// Write the `/ImageMask` attribute to set whether this image is a clipping
66    /// mask. If so, the `/BitsPerComponent` must be `1` and `/Mask` and
67    /// `/ColorSpace` attributes shall be left undefined.
68    pub fn image_mask(&mut self, mask: bool) -> &mut Self {
69        self.pair(Name(b"ImageMask"), mask);
70        self
71    }
72
73    /// Write the `/Mask` attribute to set a color key mask. The iterable color
74    /// argument must contain a range of colors (minimum and maximum) for each
75    /// channel that shall be masked out. PDF 1.3+.
76    pub fn color_mask(&mut self, colors: impl IntoIterator<Item = i32>) -> &mut Self {
77        self.insert(Name(b"Mask")).array().typed().items(colors);
78        self
79    }
80
81    /// Write the `/Mask` attribute to set another image as the stencil mask of
82    /// this image.
83    pub fn stencil_mask(&mut self, mask: Ref) -> &mut Self {
84        self.pair(Name(b"Mask"), mask);
85        self
86    }
87
88    /// Write the `/Decode` attribute to set the decoding of the image sample
89    /// colors to the specified color space. Must have twice the amount of
90    /// elements as the color space.
91    pub fn decode(&mut self, decode: impl IntoIterator<Item = f32>) -> &mut Self {
92        self.insert(Name(b"Decode")).array().typed().items(decode);
93        self
94    }
95
96    /// Write the `/Interpolate` attribute.
97    ///
98    /// Must be false or unset for PDF/A files.
99    pub fn interpolate(&mut self, interpolate: bool) -> &mut Self {
100        self.pair(Name(b"Interpolate"), interpolate);
101        self
102    }
103
104    /// Write the `/Alternates` attribute. PDF 1.3+.
105    ///
106    /// Images that may replace this image. The order is not relevant.
107    ///
108    /// Note that this key is forbidden in PDF/A.
109    pub fn alternates(&mut self, alternates: impl IntoIterator<Item = Ref>) -> &mut Self {
110        self.insert(Name(b"Alternates")).array().items(alternates);
111        self
112    }
113
114    /// Start writing the `/SMask` attribute. PDF 1.4+.
115    ///
116    /// Must not be used if this image already is an image soft mask.
117    ///
118    /// Note that this key is forbidden in PDF/A-1.
119    pub fn s_mask(&mut self, x_object: Ref) -> &mut Self {
120        self.pair(Name(b"SMask"), x_object);
121        self
122    }
123
124    /// Write the `/SMaskInData` attribute. PDF 1.5+.
125    ///
126    /// May only be used for images that use the `JPXDecode` filter. If set to
127    /// something other than `Ignore`, the `SMask` attribute must not be used.
128    pub fn s_mask_in_data(&mut self, mode: SMaskInData) -> &mut Self {
129        self.pair(Name(b"SMaskInData"), mode.to_int());
130        self
131    }
132
133    /// Write the `/StructParent` attribute to indicate the [structure tree
134    /// element][StructElement] this image belongs to. PDF 1.3+.
135    pub fn struct_parent(&mut self, key: i32) -> &mut Self {
136        self.pair(Name(b"StructParent"), key);
137        self
138    }
139
140    /// Write the `/Matte` attribute for image soft masks. PDF 1.4+.
141    ///
142    /// This shall be the matte color of the parent image encoded in its color
143    /// space.
144    pub fn matte(&mut self, color: impl IntoIterator<Item = f32>) -> &mut Self {
145        self.insert(Name(b"Matte")).array().items(color);
146        self
147    }
148
149    /// Write the `/Metadata` attribute to specify the image's metadata. PDF
150    /// 1.4+.
151    ///
152    /// The reference shall point to a [metadata stream](Metadata).
153    pub fn metadata(&mut self, id: Ref) -> &mut Self {
154        self.pair(Name(b"Metadata"), id);
155        self
156    }
157
158    /// Start writing the `/AF` array to specify the associated files of the
159    /// image. PDF 2.0+ or PDF/A-3.
160    pub fn associated_files(&mut self) -> TypedArray<'_, FileSpec<'_>> {
161        self.insert(Name(b"AF")).array().typed()
162    }
163}
164
165deref!('a, ImageXObject<'a> => Stream<'a>, stream);
166
167/// What to do with in-data mask information in `JPXDecode` images.
168#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
169pub enum SMaskInData {
170    /// Discard the mask data.
171    #[default]
172    Ignore,
173    /// Use the mask data.
174    Use,
175    /// Use the mask data on the image whose backdrop has been pre-blended with
176    /// a matte color.
177    Preblended,
178}
179
180impl SMaskInData {
181    pub(crate) fn to_int(&self) -> i32 {
182        match self {
183            Self::Ignore => 0,
184            Self::Use => 1,
185            Self::Preblended => 2,
186        }
187    }
188}
189
190/// Writer for an _form XObject stream_. PDF 1.1+.
191///
192/// This struct is created by [`Chunk::form_xobject`].
193///
194/// Note that these have nothing to do with forms that have fields to fill out.
195/// Rather, they are a way to encapsulate and reuse content across the file.
196pub struct FormXObject<'a> {
197    stream: Stream<'a>,
198}
199
200impl<'a> FormXObject<'a> {
201    /// Create a new form stream writer.
202    pub(crate) fn start(mut stream: Stream<'a>) -> Self {
203        stream.pair(Name(b"Type"), Name(b"XObject"));
204        stream.pair(Name(b"Subtype"), Name(b"Form"));
205        Self { stream }
206    }
207
208    /// Write the `/BBox` attribute. Required.
209    ///
210    /// This clips the form xobject to coordinates in its coordinate system.
211    pub fn bbox(&mut self, bbox: Rect) -> &mut Self {
212        self.pair(Name(b"BBox"), bbox);
213        self
214    }
215
216    /// Write the `/Matrix` attribute to map form space to user space.
217    pub fn matrix(&mut self, matrix: [f32; 6]) -> &mut Self {
218        self.insert(Name(b"Matrix")).array().items(matrix);
219        self
220    }
221
222    /// Start writing the `/Resources` dictionary to specify the resources used
223    /// by the XObject. This makes it independent of the parent content stream
224    /// it is eventually invoked in. PDF 1.2+.
225    pub fn resources(&mut self) -> Resources<'_> {
226        self.insert(Name(b"Resources")).start()
227    }
228
229    /// Start writing the `/Group` dictionary to set up transparency model
230    /// parameters and let this XObject be known as a group. PDF 1.4+.
231    pub fn group(&mut self) -> Group<'_> {
232        self.insert(Name(b"Group")).start()
233    }
234
235    /// Write the `/StructParent` attribute to indicate the [structure tree
236    /// element][StructElement] this XObject belongs to. Mutually exclusive with
237    /// [`Self::struct_parents`]. PDF 1.3+.
238    pub fn struct_parent(&mut self, key: i32) -> &mut Self {
239        self.pair(Name(b"StructParent"), key);
240        self
241    }
242
243    /// Write the `/StructParents` attribute to indicate the [structure tree
244    /// elements][StructElement] the contents of this XObject may belong to.
245    /// Mutually exclusive with [`Self::struct_parent`]. PDF 1.3+.
246    pub fn struct_parents(&mut self, key: i32) -> &mut Self {
247        self.pair(Name(b"StructParents"), key);
248        self
249    }
250
251    /// Start writing the `/Ref` dictionary to identify the page from an
252    /// external document that the XObject is a reference to. PDF 1.4+.
253    ///
254    /// Note that this key is forbidden in PDF/A.
255    pub fn reference(&mut self) -> Reference<'_> {
256        self.insert(Name(b"Ref")).start()
257    }
258
259    /// Write the `/Metadata` attribute to specify the XObject's metadata. PDF
260    /// 1.4+.
261    ///
262    /// The reference shall point to a [metadata stream](Metadata).
263    pub fn metadata(&mut self, id: Ref) -> &mut Self {
264        self.pair(Name(b"Metadata"), id);
265        self
266    }
267
268    /// Write the `/LastModified` attribute. PDF 1.3+.
269    pub fn last_modified(&mut self, last_modified: Date) -> &mut Self {
270        self.pair(Name(b"LastModified"), last_modified);
271        self
272    }
273
274    /// Start writing the `/AF` array to specify the associated files of the
275    /// Form XObject. PDF 2.0+ or PDF/A-3.
276    pub fn associated_files(&mut self) -> TypedArray<'_, FileSpec<'_>> {
277        self.insert(Name(b"AF")).array().typed()
278    }
279}
280
281deref!('a, FormXObject<'a> => Stream<'a>, stream);
282
283/// Writer for a _group XObject dictionary_. PDF 1.4+.
284///
285/// This struct is created by [`FormXObject::group`] and [`Page::group`].
286pub struct Group<'a> {
287    dict: Dict<'a>,
288}
289
290writer!(Group: |obj| {
291    let mut dict = obj.dict();
292    dict.pair(Name(b"Type"), Name(b"Group"));
293    Self { dict }
294});
295
296impl Group<'_> {
297    /// Set the `/S` attribute to `/Transparency`. Required to set the remaining
298    /// transparency parameters.
299    pub fn transparency(&mut self) -> &mut Self {
300        self.pair(Name(b"S"), Name(b"Transparency"));
301        self
302    }
303
304    /// Start writing the `/CS` attribute to set the color space.
305    ///
306    /// This is optional for isolated groups and required for groups where the
307    /// color space cannot be derived from the parent.
308    ///
309    /// Required in PDF/A-2 through PDF/A-4 if there is no OutputIntent.
310    pub fn color_space(&mut self) -> ColorSpace<'_> {
311        self.insert(Name(b"CS")).start()
312    }
313
314    /// Set the `/I` attribute to indicate whether the group is isolated.
315    ///
316    /// If it is true, the group will initially be composited against a clear
317    /// backdrop. If it is false, the group will be composited against the
318    /// backdrop of the parent group.
319    pub fn isolated(&mut self, isolated: bool) -> &mut Self {
320        self.pair(Name(b"I"), isolated);
321        self
322    }
323
324    /// Set the `/K` attribute to indicate whether the group is a knockout
325    /// group.
326    ///
327    /// Within a knockout group, the group children are all composited
328    /// separately against the backdrop instead of on top of each other.
329    pub fn knockout(&mut self, knockout: bool) -> &mut Self {
330        self.pair(Name(b"K"), knockout);
331        self
332    }
333}
334
335deref!('a, Group<'a> => Dict<'a>, dict);
336
337/// Writer for an _external XObject reference dictionary_. PDF 1.4+.
338///
339/// This struct is created by [`FormXObject::reference`].
340///
341/// Reference XObjects are forbidden in PDF/A.
342pub struct Reference<'a> {
343    dict: Dict<'a>,
344}
345
346writer!(Reference: |obj| Self { dict: obj.dict() });
347
348impl Reference<'_> {
349    /// Start writing the `/F` attribute to set a file specification dictionary.
350    /// Required.
351    pub fn file_spec(&mut self) -> FileSpec<'_> {
352        self.insert(Name(b"F")).start()
353    }
354
355    /// Write the `/Page` attribute to set the page number. Setting the
356    /// attribute through either this function or [`Self::page_label`] is
357    /// required. Page indices start at 0.
358    pub fn page_number(&mut self, page: i32) -> &mut Self {
359        self.pair(Name(b"Page"), page);
360        self
361    }
362
363    /// Write the `/Page` attribute to set the page label. Setting the attribute
364    /// through either this function or [`Self::page_number`] is required.
365    pub fn page_label(&mut self, label: impl TextStrLike) -> &mut Self {
366        self.pair(Name(b"Page"), label);
367        self
368    }
369
370    /// Write the `/ID` attribute to set the file identifier.
371    pub fn id(&mut self, id: [Str; 2]) -> &mut Self {
372        self.insert(Name(b"ID")).array().items(id);
373        self
374    }
375}
376
377deref!('a, Reference<'a> => Dict<'a>, dict);