pdf_writer/
chunk.rs

1use super::*;
2
3/// A builder for a collection of indirect PDF objects.
4///
5/// This type holds written top-level indirect PDF objects. Typically, you won't
6/// create a colllection yourself, but use the primary chunk of the top-level
7/// [`Pdf`] through its [`Deref`] implementation.
8///
9/// However, sometimes it's useful to be able to create a separate chunk to be
10/// able to write two things at the same time (which isn't possible with a
11/// single chunk because of the streaming nature --- only one writer can borrow
12/// it at a time).
13#[derive(Clone)]
14pub struct Chunk {
15    pub(crate) buf: Buf,
16    pub(crate) offsets: Vec<(Ref, usize)>,
17}
18
19impl Chunk {
20    /// Create a new chunk with the default capacity (currently 1 KB).
21    #[allow(clippy::new_without_default)]
22    pub fn new() -> Self {
23        Self::with_capacity(1024)
24    }
25
26    /// Create a new chunk with the specified initial capacity.
27    pub fn with_capacity(capacity: usize) -> Self {
28        Self { buf: Buf::with_capacity(capacity), offsets: vec![] }
29    }
30
31    /// The number of bytes that were written so far.
32    #[inline]
33    #[allow(clippy::len_without_is_empty)]
34    pub fn len(&self) -> usize {
35        self.buf.len()
36    }
37
38    /// The bytes already written so far.
39    pub fn as_bytes(&self) -> &[u8] {
40        self.buf.as_slice()
41    }
42
43    /// Add all objects from another chunk to this one.
44    pub fn extend(&mut self, other: &Chunk) {
45        let base = self.len();
46        self.buf.extend_buf(&other.buf);
47        self.offsets
48            .extend(other.offsets.iter().map(|&(id, offset)| (id, base + offset)));
49    }
50
51    /// An iterator over the references of the top-level objects
52    /// of the chunk, in the order they appear in the chunk.
53    pub fn refs(&self) -> impl ExactSizeIterator<Item = Ref> + '_ {
54        self.offsets.iter().map(|&(id, _)| id)
55    }
56
57    /// Returns the limits of data written into the chunk.
58    pub fn limits(&self) -> &Limits {
59        self.buf.limits()
60    }
61
62    /// Merges other limits into this chunk, taking the maximum of each field
63    /// from the chunk's current [`limits()`](Self::limits) and `other`.
64    ///
65    /// This is, for instance, useful when adding a content stream (with limits
66    /// of its own) to the chunk.
67    ///
68    /// ```
69    /// use pdf_writer::{Chunk, Content, Ref};
70    ///
71    /// let mut content = Content::new();
72    /// content.set_dash_pattern([1.0, 3.0, 2.0, 4.0, 5.0], 1.0);
73    /// let buf = content.finish();
74    ///
75    /// let mut chunk = Chunk::new();
76    /// chunk.stream(Ref::new(1), &buf);
77    /// chunk.merge_limits(buf.limits());
78    ///
79    /// // Dash pattern had an array with 5 entries.
80    /// assert_eq!(chunk.limits().array_len(), 5);
81    /// ```
82    pub fn merge_limits(&mut self, other: &Limits) {
83        self.buf.limits.merge(other);
84    }
85
86    /// Renumbers the IDs of indirect objects and all indirect references in the
87    /// chunk and returns the resulting chunk.
88    ///
89    /// The given closure is called for each object and indirect reference in
90    /// the chunk. When an ID appears multiple times in the chunk (for object
91    /// and/or reference), it will be called multiple times. When assigning new
92    /// IDs, it is up to you to provide a well-defined mapping (it should most
93    /// probably be a pure function so that a specific old ID is always mapped
94    /// to the same new ID).
95    ///
96    /// A simple way to renumber a chunk is to map all old IDs to new
97    /// consecutive IDs. This can be achieved by allocating a new ID for each
98    /// unique ID we have seen and memoizing this mapping in a hash map:
99    ///
100    /// ```
101    /// # use std::collections::HashMap;
102    /// # use pdf_writer::{Chunk, Ref, TextStr, Name};
103    /// let mut chunk = Chunk::new();
104    /// chunk.indirect(Ref::new(10)).primitive(true);
105    /// chunk.indirect(Ref::new(17))
106    ///     .dict()
107    ///     .pair(Name(b"Self"), Ref::new(17))
108    ///     .pair(Name(b"Ref"), Ref::new(10))
109    ///     .pair(Name(b"NoRef"), TextStr("Text with 10 0 R"));
110    ///
111    /// // Gives the objects consecutive IDs.
112    /// // - The `true` object will get ID 1.
113    /// // - The dictionary object will get ID 2.
114    /// let mut alloc = Ref::new(1);
115    /// let mut map = HashMap::new();
116    /// let renumbered = chunk.renumber(|old| {
117    ///     *map.entry(old).or_insert_with(|| alloc.bump())
118    /// });
119    /// ```
120    ///
121    /// If a chunk references indirect objects that are not defined within it,
122    /// the closure is still called with those references. Allocating new IDs
123    /// for them will probably not make sense, so it's up to you to either not
124    /// have dangling references or handle them in a way that makes sense for
125    /// your use case.
126    pub fn renumber<F>(&self, mapping: F) -> Chunk
127    where
128        F: FnMut(Ref) -> Ref,
129    {
130        let mut chunk = Chunk::with_capacity(self.len());
131        self.renumber_into(&mut chunk, mapping);
132        chunk
133    }
134
135    /// Same as [`renumber`](Self::renumber), but writes the results into an
136    /// existing `target` chunk instead of creating a new chunk.
137    pub fn renumber_into<F>(&self, target: &mut Chunk, mut mapping: F)
138    where
139        F: FnMut(Ref) -> Ref,
140    {
141        target.buf.reserve(self.len());
142        crate::renumber::renumber(self, target, &mut mapping);
143    }
144}
145
146/// Indirect objects and streams.
147impl Chunk {
148    /// Start writing an indirectly referenceable object.
149    pub fn indirect(&mut self, id: Ref) -> Obj<'_> {
150        self.offsets.push((id, self.buf.len()));
151        Obj::indirect(&mut self.buf, id)
152    }
153
154    /// Start writing an indirectly referenceable stream.
155    ///
156    /// The stream data and the `/Length` field are written automatically. You
157    /// can add additional key-value pairs to the stream dictionary with the
158    /// returned stream writer.
159    ///
160    /// You can use this function together with a [`Content`] stream builder to
161    /// provide a [page's contents](Page::contents).
162    /// ```
163    /// use pdf_writer::{Pdf, Content, Ref};
164    ///
165    /// // Create a simple content stream.
166    /// let mut content = Content::new();
167    /// content.rect(50.0, 50.0, 50.0, 50.0);
168    /// content.stroke();
169    ///
170    /// // Create a writer and write the stream.
171    /// let mut pdf = Pdf::new();
172    /// pdf.stream(Ref::new(1), &content.finish());
173    /// ```
174    ///
175    /// This crate does not do any compression for you. If you want to compress
176    /// a stream, you have to pass already compressed data into this function
177    /// and specify the appropriate filter in the stream dictionary.
178    ///
179    /// For example, if you want to compress your content stream with DEFLATE,
180    /// you could do something like this:
181    /// ```
182    /// use pdf_writer::{Pdf, Content, Ref, Filter};
183    /// use miniz_oxide::deflate::{compress_to_vec_zlib, CompressionLevel};
184    ///
185    /// // Create a simple content stream.
186    /// let mut content = Content::new();
187    /// content.rect(50.0, 50.0, 50.0, 50.0);
188    /// content.stroke();
189    ///
190    /// // Compress the stream.
191    /// let level = CompressionLevel::DefaultLevel as u8;
192    /// let compressed = compress_to_vec_zlib(&content.finish(), level);
193    ///
194    /// // Create a writer, write the compressed stream and specify that it
195    /// // needs to be decoded with a FLATE filter.
196    /// let mut pdf = Pdf::new();
197    /// pdf.stream(Ref::new(1), &compressed).filter(Filter::FlateDecode);
198    /// ```
199    /// For all the specialized stream functions below, it works the same way:
200    /// You can pass compressed data and specify a filter.
201    ///
202    /// Panics if the stream length exceeds `i32::MAX`.
203    pub fn stream<'a>(&'a mut self, id: Ref, data: &'a [u8]) -> Stream<'a> {
204        Stream::start(self.indirect(id), data)
205    }
206}
207
208/// Document structure.
209impl Chunk {
210    /// Start writing a page tree.
211    pub fn pages(&mut self, id: Ref) -> Pages<'_> {
212        self.indirect(id).start()
213    }
214
215    /// Start writing a page.
216    pub fn page(&mut self, id: Ref) -> Page<'_> {
217        self.indirect(id).start()
218    }
219
220    /// Start writing an outline.
221    pub fn outline(&mut self, id: Ref) -> Outline<'_> {
222        self.indirect(id).start()
223    }
224
225    /// Start writing an outline item.
226    pub fn outline_item(&mut self, id: Ref) -> OutlineItem<'_> {
227        self.indirect(id).start()
228    }
229
230    /// Start writing a destination for use in a name tree.
231    pub fn destination(&mut self, id: Ref) -> Destination<'_> {
232        self.indirect(id).start()
233    }
234
235    /// Start writing a named destination dictionary.
236    pub fn destinations(&mut self, id: Ref) -> TypedDict<'_, Destination<'_>> {
237        self.indirect(id).dict().typed()
238    }
239
240    /// Start writing a file specification dictionary.
241    pub fn file_spec(&mut self, id: Ref) -> FileSpec<'_> {
242        self.indirect(id).start()
243    }
244
245    /// Start writing an embedded file stream.
246    pub fn embedded_file<'a>(&'a mut self, id: Ref, bytes: &'a [u8]) -> EmbeddedFile<'a> {
247        EmbeddedFile::start(self.stream(id, bytes))
248    }
249
250    /// Start writing a structure tree element.
251    pub fn struct_element(&mut self, id: Ref) -> StructElement<'_> {
252        self.indirect(id).start()
253    }
254
255    /// Start writing a namespace dictionary. PDF 2.0+
256    pub fn namespace(&mut self, id: Ref) -> Namespace<'_> {
257        self.indirect(id).start()
258    }
259
260    /// Start writing a metadata stream.
261    pub fn metadata<'a>(&'a mut self, id: Ref, bytes: &'a [u8]) -> Metadata<'a> {
262        Metadata::start(self.stream(id, bytes))
263    }
264}
265
266/// Graphics and content.
267impl Chunk {
268    /// Start writing an image XObject stream.
269    ///
270    /// The samples should be encoded according to the stream's filter, color
271    /// space and bits per component.
272    pub fn image_xobject<'a>(
273        &'a mut self,
274        id: Ref,
275        samples: &'a [u8],
276    ) -> ImageXObject<'a> {
277        ImageXObject::start(self.stream(id, samples))
278    }
279
280    /// Start writing a form XObject stream.
281    ///
282    /// These can be used as transparency groups.
283    ///
284    /// Note that these have nothing to do with forms that have fields to fill
285    /// out. Rather, they are a way to encapsulate and reuse content across the
286    /// file.
287    ///
288    /// You can create the content bytes using a [`Content`] builder.
289    pub fn form_xobject<'a>(&'a mut self, id: Ref, content: &'a [u8]) -> FormXObject<'a> {
290        FormXObject::start(self.stream(id, content))
291    }
292
293    /// Start writing an external graphics state dictionary.
294    pub fn ext_graphics(&mut self, id: Ref) -> ExtGraphicsState<'_> {
295        self.indirect(id).start()
296    }
297}
298
299/// Fonts.
300impl Chunk {
301    /// Start writing a Type-1 font.
302    pub fn type1_font(&mut self, id: Ref) -> Type1Font<'_> {
303        self.indirect(id).start()
304    }
305
306    /// Start writing a Type-3 font.
307    pub fn type3_font(&mut self, id: Ref) -> Type3Font<'_> {
308        self.indirect(id).start()
309    }
310
311    /// Start writing a Type-0 font.
312    pub fn type0_font(&mut self, id: Ref) -> Type0Font<'_> {
313        self.indirect(id).start()
314    }
315
316    /// Start writing a CID font.
317    pub fn cid_font(&mut self, id: Ref) -> CidFont<'_> {
318        self.indirect(id).start()
319    }
320
321    /// Start writing a font descriptor.
322    pub fn font_descriptor(&mut self, id: Ref) -> FontDescriptor<'_> {
323        self.indirect(id).start()
324    }
325
326    /// Start writing a character map stream.
327    ///
328    /// If you want to use this for a `/ToUnicode` CMap, you can create the
329    /// bytes using a [`UnicodeCmap`](types::UnicodeCmap) builder.
330    pub fn cmap<'a>(&'a mut self, id: Ref, cmap: &'a [u8]) -> Cmap<'a> {
331        Cmap::start(self.stream(id, cmap))
332    }
333}
334
335/// Color spaces, shadings and patterns.
336impl Chunk {
337    /// Start writing a color space.
338    pub fn color_space(&mut self, id: Ref) -> ColorSpace<'_> {
339        self.indirect(id).start()
340    }
341
342    /// Start writing a function-based shading (type 1-3).
343    pub fn function_shading(&mut self, id: Ref) -> FunctionShading<'_> {
344        self.indirect(id).start()
345    }
346
347    /// Start writing a stream-based shading (type 4-7).
348    pub fn stream_shading<'a>(
349        &'a mut self,
350        id: Ref,
351        content: &'a [u8],
352    ) -> StreamShading<'a> {
353        StreamShading::start(self.stream(id, content))
354    }
355
356    /// Start writing a tiling pattern stream.
357    ///
358    /// You can create the content bytes using a [`Content`] builder.
359    pub fn tiling_pattern<'a>(
360        &'a mut self,
361        id: Ref,
362        content: &'a [u8],
363    ) -> TilingPattern<'a> {
364        TilingPattern::start_with_stream(self.stream(id, content))
365    }
366
367    /// Start writing a shading pattern.
368    pub fn shading_pattern(&mut self, id: Ref) -> ShadingPattern<'_> {
369        self.indirect(id).start()
370    }
371
372    /// Start writing an ICC profile stream.
373    ///
374    /// The `profile` argument shall contain the ICC profile data conforming to
375    /// ICC.1:2004-10 (PDF 1.7), ICC.1:2003-09 (PDF 1.6), ICC.1:2001-12 (PDF 1.5),
376    /// ICC.1:1999-04 (PDF 1.4), or ICC 3.3 (PDF 1.3). Profile data is commonly
377    /// compressed using the `FlateDecode` filter.
378    pub fn icc_profile<'a>(&'a mut self, id: Ref, profile: &'a [u8]) -> IccProfile<'a> {
379        IccProfile::start(self.stream(id, profile))
380    }
381}
382
383/// Functions.
384impl Chunk {
385    /// Start writing a sampled function stream.
386    pub fn sampled_function<'a>(
387        &'a mut self,
388        id: Ref,
389        samples: &'a [u8],
390    ) -> SampledFunction<'a> {
391        SampledFunction::start(self.stream(id, samples))
392    }
393
394    /// Start writing an exponential function.
395    pub fn exponential_function(&mut self, id: Ref) -> ExponentialFunction<'_> {
396        self.indirect(id).start()
397    }
398
399    /// Start writing a stitching function.
400    pub fn stitching_function(&mut self, id: Ref) -> StitchingFunction<'_> {
401        self.indirect(id).start()
402    }
403
404    /// Start writing a PostScript function stream.
405    ///
406    /// You can create the code bytes using [`PostScriptOp::encode`](types::PostScriptOp::encode).
407    pub fn post_script_function<'a>(
408        &'a mut self,
409        id: Ref,
410        code: &'a [u8],
411    ) -> PostScriptFunction<'a> {
412        PostScriptFunction::start(self.stream(id, code))
413    }
414}
415
416/// Tree data structures.
417impl Chunk {
418    /// Start writing a name tree node.
419    pub fn name_tree<T: Primitive>(&mut self, id: Ref) -> NameTree<'_, T> {
420        self.indirect(id).start()
421    }
422
423    /// Start writing a number tree node.
424    pub fn number_tree<T: Primitive>(&mut self, id: Ref) -> NumberTree<'_, T> {
425        self.indirect(id).start()
426    }
427}
428
429/// Interactive features.
430impl Chunk {
431    /// Start writing an annotation dictionary.
432    pub fn annotation(&mut self, id: Ref) -> Annotation<'_> {
433        self.indirect(id).start()
434    }
435
436    /// Start writing a form field dictionary.
437    pub fn form_field(&mut self, id: Ref) -> Field<'_> {
438        self.indirect(id).start()
439    }
440}
441
442impl Debug for Chunk {
443    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
444        f.pad("Chunk(..)")
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use super::*;
451
452    #[test]
453    fn test_chunk() {
454        let mut w = Pdf::new();
455        let mut font = w.type3_font(Ref::new(1));
456        let mut c = Chunk::new();
457        c.font_descriptor(Ref::new(2)).name(Name(b"MyFont"));
458        font.font_descriptor(Ref::new(2));
459        font.finish();
460        w.extend(&c);
461        test!(
462            w.finish(),
463            b"%PDF-1.7\n%\x80\x80\x80\x80\n",
464            b"1 0 obj",
465            b"<<\n  /Type /Font\n  /Subtype /Type3\n  /FontDescriptor 2 0 R\n>>",
466            b"endobj\n",
467            b"2 0 obj",
468            b"<<\n  /Type /FontDescriptor\n  /FontName /MyFont\n>>",
469            b"endobj\n",
470            b"xref",
471            b"0 3",
472            b"0000000000 65535 f\r",
473            b"0000000016 00000 n\r",
474            b"0000000094 00000 n\r",
475            b"trailer",
476            b"<<\n  /Size 3\n>>",
477            b"startxref\n160\n%%EOF",
478        );
479    }
480}