Skip to main content

pdf_writer/
chunk.rs

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