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}