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}