Skip to main content

pdf_writer/
lib.rs

1/*!
2A step-by-step PDF writer.
3
4The entry point into the API is the [`Pdf`] struct, which constructs the
5document into one big internal buffer. The top-level writer has many methods to
6create specialized writers for specific PDF objects. These all follow the same
7general pattern: They borrow the main buffer mutably, expose a builder pattern
8for writing individual fields in a strongly typed fashion and finish up the
9object when dropped.
10
11There are a few more top-level structs with internal buffers, like the
12[`Content`] stream builder and the [`Chunk`], but wherever possible buffers
13are borrowed from parent writers to minimize allocations.
14
15# Writers
16The writers contained is this crate fall roughly into two categories.
17
18**Core writers** enable you to write arbitrary PDF objects.
19
20- The [`Obj`] writer allows to write most fundamental PDF objects (numbers,
21  strings, arrays, dictionaries, ...). It is exposed through
22  [`Chunk::indirect`] to write top-level indirect objects and through
23  [`Array::push`] and [`Dict::insert`] to compose objects.
24- Streams are exposed through a separate [`Chunk::stream`] method since
25  they _must_ be indirect objects.
26
27**Specialized writers** for things like a _[page]_ or an _[image]_ expose the
28core writer's capabilities in a strongly typed fashion.
29
30- A [`Page`] writer, for example, is just a thin wrapper around a [`Dict`] and
31  it even derefs to a dictionary in case you need to write a field that is not
32  yet exposed by the typed API.
33- Similarly, the [`ImageXObject`] derefs to a [`Stream`], so that the [`filter()`]
34  function can be shared by all kinds of streams. The [`Stream`] in turn derefs
35  to a [`Dict`] so that you can add arbitrary fields to the stream dictionary.
36
37When you bind a writer to a variable instead of just writing a chained builder
38pattern, you may need to manually drop it before starting a new object using
39[`finish()`](Finish::finish) or [`drop()`].
40
41# Minimal example
42The following example creates a PDF with a single, empty A4 page.
43
44```
45use pdf_writer::{Pdf, Rect, Ref};
46
47# fn main() -> std::io::Result<()> {
48// Define some indirect reference ids we'll use.
49let catalog_id = Ref::new(1);
50let page_tree_id = Ref::new(2);
51let page_id = Ref::new(3);
52
53// Write a document catalog and a page tree with one A4 page that uses no resources.
54let mut pdf = Pdf::new();
55pdf.catalog(catalog_id).pages(page_tree_id);
56pdf.pages(page_tree_id).kids([page_id]).count(1);
57pdf.page(page_id)
58    .parent(page_tree_id)
59    .media_box(Rect::new(0.0, 0.0, 595.0, 842.0))
60    .resources();
61
62// Finish with cross-reference table and trailer and write to file.
63std::fs::write("target/empty.pdf", pdf.finish())?;
64# Ok(())
65# }
66```
67
68For more examples, check out the [examples folder] in the repository.
69
70# Note
71This crate is rather low-level. It does not allocate or validate indirect
72reference IDs for you and it does not check whether you write all required
73fields for an object. Refer to the [PDF specification] to make sure you create
74valid PDFs.
75
76[page]: writers::Page
77[image]: writers::ImageXObject
78[`filter()`]: Stream::filter
79[examples folder]: https://github.com/typst/pdf-writer/tree/main/examples
80[PDF specification]: https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf
81*/
82
83#![forbid(unsafe_code)]
84#![deny(missing_docs)]
85#![allow(clippy::wrong_self_convention)]
86
87#[macro_use]
88mod macros;
89mod actions;
90mod annotations;
91mod attributes;
92mod buf;
93mod chunk;
94mod color;
95mod content;
96mod files;
97mod font;
98mod forms;
99mod functions;
100mod object;
101mod renditions;
102mod renumber;
103mod structure;
104mod transitions;
105mod xobject;
106
107/// Strongly typed writers for specific PDF structures.
108pub mod writers {
109    use super::*;
110    pub use actions::{Action, AdditionalActions, Fields};
111    pub use annotations::{
112        Annotation, Appearance, AppearanceCharacteristics, AppearanceEntry, BorderStyle,
113        IconFit,
114    };
115    pub use attributes::{
116        ArtifactAttributes, Attributes, FENoteAttributes, FieldAttributes,
117        LayoutAttributes, ListAttributes, TableAttributes, TrackSizes, UserProperty,
118    };
119    pub use color::{
120        ColorSpace, DeviceN, DeviceNAttrs, DeviceNMixingHints, DeviceNProcess,
121        FunctionShading, IccProfile, OutputIntent, Separation, SeparationInfo,
122        ShadingPattern, StreamShading, StreamShadingType, TilingPattern,
123    };
124    pub use content::{
125        Artifact, ExtGraphicsState, MarkContent, Operation, PositionedItems,
126        PropertyList, Resources, ShowPositioned, SoftMask,
127    };
128    pub use files::{EmbeddedFile, EmbeddingParams, FileSpec};
129    pub use font::{
130        CidFont, Cmap, Differences, Encoding, FontDescriptor, FontDescriptorOverride,
131        Type0Font, Type1Font, Type3Font, WMode, Widths,
132    };
133    pub use forms::{Field, Form};
134    pub use functions::{
135        ExponentialFunction, PostScriptFunction, SampledFunction, StitchingFunction,
136    };
137    pub use object::{
138        DecodeParms, NameTree, NameTreeEntries, NumberTree, NumberTreeEntries,
139    };
140    pub use renditions::{MediaClip, MediaPermissions, MediaPlayParams, Rendition};
141    pub use structure::{
142        Catalog, ClassMap, Destination, DeveloperExtension, DocumentInfo, MarkInfo,
143        MarkedRef, Metadata, Names, Namespace, NamespaceRoleMap, ObjectRef, Outline,
144        OutlineItem, Page, PageLabel, Pages, RoleMap, StructChildren, StructElement,
145        StructTreeRoot, ViewerPreferences,
146    };
147    pub use transitions::Transition;
148    pub use xobject::{FormXObject, Group, ImageXObject, Reference};
149}
150
151/// Types used by specific PDF structures.
152pub mod types {
153    use super::*;
154    pub use actions::{ActionType, FormActionFlags, RenditionOperation};
155    pub use annotations::{
156        AnnotationFlags, AnnotationIcon, AnnotationType, BorderType, HighlightEffect,
157        IconScale, IconScaleType, TextPosition,
158    };
159    pub use attributes::{
160        AttributeOwner, BlockAlign, FieldRole, FieldState, GlyphOrientationVertical,
161        InlineAlign, LayoutBorderStyle, LayoutTextPosition, LineHeight, ListNumbering,
162        NoteType, Placement, RubyAlign, RubyPosition, Sides, TableHeaderScope, TextAlign,
163        TextDecorationType, WritingMode,
164    };
165    pub use color::{
166        DeviceNSubtype, FunctionShadingType, OutputIntentSubtype, PaintType, TilingType,
167    };
168    pub use content::{
169        ArtifactAttachment, ArtifactSubtype, ArtifactType, BlendMode, ColorSpaceOperand,
170        LineCapStyle, LineJoinStyle, MaskType, OverprintMode, ProcSet, RenderingIntent,
171        TextRenderingMode,
172    };
173    pub use files::AssociationKind;
174    pub use font::{
175        CidFontType, CjkClass, FontFlags, FontStretch, GlyphId, SystemInfo, UnicodeCmap,
176    };
177    pub use forms::{
178        CheckBoxState, ChoiceOptions, FieldFlags, FieldType, Quadding, SigFlags,
179    };
180    pub use functions::{InterpolationOrder, PostScriptOp};
181    pub use object::Predictor;
182    pub use renditions::{MediaClipType, RenditionType, TempFileType};
183    pub use structure::{
184        BlockLevelRoleSubtype, Direction, InlineLevelRoleSubtype,
185        InlineLevelRoleSubtype2, NumberingStyle, OutlineItemFlags, PageLayout, PageMode,
186        PhoneticAlphabet, RoleMapOpts, StructRole, StructRole2, StructRole2Compat,
187        StructRoleType, StructRoleType2, TabOrder, TrappingStatus,
188    };
189    pub use transitions::{TransitionAngle, TransitionStyle};
190    pub use xobject::SMaskInData;
191}
192
193pub use self::buf::{Buf, Limits};
194pub use self::chunk::{Chunk, Settings};
195pub use self::content::Content;
196pub use self::object::{
197    Array, Date, Dict, Filter, Finish, LanguageIdentifier, Name, Null, Obj, Primitive,
198    Rect, Ref, Rewrite, Str, Stream, TextStr, TextStrLike, TextStrWithLang, TypedArray,
199    TypedDict, Writer,
200};
201
202use std::fmt::{self, Debug, Formatter};
203use std::io::Write;
204use std::ops::{Deref, DerefMut};
205
206use self::writers::*;
207
208/// A builder for a PDF file.
209///
210/// This type constructs a PDF file in-memory. Aside from a few specific
211/// structures, a PDF file mostly consists of indirect objects. For more
212/// flexibility, you can write these objects either directly into a [`Pdf`] or
213/// into a [`Chunk`], which you can add to the [`Pdf`] (or another chunk) later.
214/// Therefore, most writing methods are exposed on the chunk type, which this
215/// type dereferences to.
216pub struct Pdf {
217    chunk: Chunk,
218    trailer_data: TrailerData,
219}
220
221impl Pdf {
222    /// Create a new PDF with the default settings and buffer capacity
223    /// (currently 8 KB).
224    #[allow(clippy::new_without_default)]
225    pub fn new() -> Self {
226        Self::with_settings(Settings::default())
227    }
228
229    /// Create a new PDF with the given settings and the default buffer capacity
230    /// (currently 8 KB).
231    pub fn with_settings(settings: Settings) -> Self {
232        Self::with_settings_and_capacity(settings, 8 * 1024)
233    }
234
235    /// Create a new PDF with the default settings and the specified initial
236    /// buffer capacity.
237    pub fn with_capacity(capacity: usize) -> Self {
238        Self::with_settings_and_capacity(Settings::default(), capacity)
239    }
240
241    /// Create a new PDF with the given settings and the specified initial
242    /// buffer capacity.
243    pub fn with_settings_and_capacity(settings: Settings, capacity: usize) -> Self {
244        let mut chunk = Chunk::with_settings_and_capacity(settings, capacity);
245        chunk.buf.extend(b"%PDF-1.7\n%\x80\x80\x80\x80\n\n");
246        Self { chunk, trailer_data: TrailerData::default() }
247    }
248
249    /// Set the binary marker in the header of the PDF.
250    ///
251    /// This can be useful if you want to ensure that your PDF consists of only
252    /// ASCII characters, as this is not the case by default.
253    ///
254    /// _Default value_: \x80\x80\x80\x80
255    pub fn set_binary_marker(&mut self, marker: &[u8; 4]) {
256        self.chunk.buf.inner[10..14].copy_from_slice(marker);
257    }
258
259    /// Set the PDF version.
260    ///
261    /// The version is not semantically important to the crate, but must be
262    /// present in the output document.
263    ///
264    /// _Default value_: 1.7.
265    pub fn set_version(&mut self, major: u8, minor: u8) {
266        if major < 10 {
267            self.chunk.buf.inner[5] = b'0' + major;
268        }
269        if minor < 10 {
270            self.chunk.buf.inner[7] = b'0' + minor;
271        }
272    }
273
274    /// Set the file identifier for the document.
275    ///
276    /// The file identifier is a pair of two byte strings that shall be used to
277    /// uniquely identify a particular file. The first string should always stay
278    /// the same for a document, the second should change for each revision. It
279    /// is optional, but recommended. In PDF/A, this is required. PDF 1.1+.
280    pub fn set_file_id(&mut self, id: (Vec<u8>, Vec<u8>)) {
281        self.trailer_data.file_id = Some(id);
282    }
283
284    /// Start writing the document catalog. Required.
285    ///
286    /// This will also register the document catalog with the file trailer,
287    /// meaning that you don't need to provide the given `id` anywhere else.
288    pub fn catalog(&mut self, id: Ref) -> Catalog<'_> {
289        self.trailer_data.catalog_id = Some(id);
290        self.indirect(id).start()
291    }
292
293    /// Start writing the document information.
294    ///
295    /// This will also register the document information dictionary with the
296    /// file trailer, meaning that you don't need to provide the given `id`
297    /// anywhere else.
298    pub fn document_info(&mut self, id: Ref) -> DocumentInfo<'_> {
299        self.trailer_data.info_id = Some(id);
300        self.indirect(id).start()
301    }
302
303    /// Write the cross-reference table and file trailer and return the
304    /// underlying buffer.
305    ///
306    /// Panics if any indirect reference id was used twice.
307    pub fn finish(self) -> Vec<u8> {
308        let Chunk { mut buf, offsets, settings } = self.chunk;
309        let trailer_data = self.trailer_data;
310        let xref_offset = buf.len();
311
312        let mut writer = PlainXRefWriter::new(&mut buf);
313        let xref_len = write_offsets(offsets, &mut writer);
314
315        // Write the trailer dictionary.
316        buf.extend(b"trailer\n");
317        let mut trailer = Obj::direct(&mut buf, 0, settings, false).dict();
318        trailer_data.write_into_dict(&mut trailer, xref_len);
319        trailer.finish();
320
321        finish_trailer(buf, xref_offset, b"\n")
322    }
323
324    /// Write the cross-reference stream and file trailer and return the
325    /// underlying buffer. This method is functionally the same as
326    /// [`Pdf::finish`], the difference being that the cross-reference
327    /// information is written as a cross-reference stream instead of a
328    /// cross-reference table. Cross-reference streams usually allow for
329    /// smaller file sizes since they can also be compressed (see
330    /// [`Pdf::finish_with_xref_stream_and_filter`]),
331    /// but are only available from PDF 1.5 onwards. It is also necessary
332    /// to call this method instead of [`Pdf::finish`] in case object stream
333    /// are used anywhere in the document since normal xref tables do not
334    /// support object streams.
335    ///
336    /// `xref_id` will be the object identifier used for the cross-reference
337    /// stream. As in other cases, the identifier needs to be unique throughout
338    /// the whole document.
339    ///
340    /// Panics if any indirect reference id was used twice.
341    pub fn finish_with_xref_stream(self, xref_id: Ref) -> Vec<u8> {
342        self.finish_with_xref_stream_inner(xref_id, |buf| (buf, None))
343    }
344
345    /// Write the cross-reference stream and file trailer and return the
346    /// underlying buffer.
347    ///
348    /// This method is equivalent to [`Pdf::finish_with_xref_stream`], except
349    /// that it allows you to apply one or multiple filters to the xref stream
350    /// via the `filter` closure. The input of the closure will be the raw
351    /// content of the xref stream, and the output should be the filtered data
352    /// as well as a single filter or a list of filters that need to be
353    /// applied to unfilter the data in the correct order.
354    pub fn finish_with_xref_stream_and_filter(
355        self,
356        xref_id: Ref,
357        filter: impl FnOnce(&[u8]) -> (Vec<u8>, XRefFilter),
358    ) -> Vec<u8> {
359        self.finish_with_xref_stream_inner(xref_id, |buf| {
360            let (xref_data, filter) = filter(&buf);
361            (xref_data, Some(filter))
362        })
363    }
364
365    fn finish_with_xref_stream_inner(
366        self,
367        xref_id: Ref,
368        filter: impl FnOnce(Vec<u8>) -> (Vec<u8>, Option<XRefFilter>),
369    ) -> Vec<u8> {
370        let Chunk { mut buf, mut offsets, settings } = self.chunk;
371        let trailer_data = self.trailer_data;
372
373        // Include the reference of the xref stream in the offsets as well!
374        let xref_offset = buf.len();
375        offsets.push((xref_id, xref_offset));
376        let field_width = determine_field_width(xref_offset);
377
378        let mut writer = XRefStreamWriter::new(field_width);
379        let xref_len = write_offsets(offsets, &mut writer);
380
381        let (xref_data, filter) = filter(writer.buf);
382
383        let mut stream =
384            Stream::start(Obj::indirect(&mut buf, xref_id, settings), &xref_data);
385
386        stream.pair(Name(b"Type"), Name(b"XRef"));
387
388        if let Some(filter) = filter {
389            match filter {
390                XRefFilter::Single(filter) => {
391                    stream.filter(filter);
392                }
393                XRefFilter::Multiple(filters) => {
394                    let mut arr = stream.insert(Name(b"Filter")).array();
395
396                    for filter in filters {
397                        arr.item(filter.to_name());
398                    }
399                }
400            }
401        }
402
403        trailer_data.write_into_dict(stream.deref_mut(), xref_len);
404
405        stream
406            .insert(Name(b"W"))
407            .array()
408            .item(1)
409            .item(field_width as i32)
410            .item(2);
411
412        stream.finish();
413
414        finish_trailer(buf, xref_offset, &[])
415    }
416}
417
418/// The filters used for the xref stream.
419pub enum XRefFilter {
420    /// A single filter.
421    Single(Filter),
422    /// An array of filters.
423    Multiple(Vec<Filter>),
424}
425
426fn finish_trailer(mut buf: Buf, xref_offset: usize, pad: &[u8]) -> Vec<u8> {
427    buf.extend(pad);
428    // Write startxref pointing to the xref stream
429    buf.extend(b"startxref\n");
430    write!(buf.inner, "{}", xref_offset).unwrap();
431
432    // Write EOF marker
433    buf.extend(b"\n%%EOF");
434    buf.into_vec()
435}
436
437fn write_offsets(mut offsets: Vec<(Ref, usize)>, writer: &mut impl XRefWriter) -> i32 {
438    offsets.sort();
439
440    let xref_len = 1 + offsets.last().map_or(0, |p| p.0.get());
441    writer.prologue(xref_len);
442
443    if offsets.is_empty() {
444        writer.write_free_entry(0, 65535);
445    }
446
447    let mut written = 0;
448    for (i, (object_id, offset)) in offsets.iter().enumerate() {
449        if written > object_id.get() {
450            panic!("duplicate indirect reference id: {}", object_id.get());
451        }
452
453        // Fill in free list.
454        let start = written;
455        for free_id in start..object_id.get() {
456            let mut next = free_id + 1;
457            if next == object_id.get() {
458                // Find next free id.
459                for (used_id, _) in &offsets[i..] {
460                    if next < used_id.get() {
461                        break;
462                    } else {
463                        next = used_id.get() + 1;
464                    }
465                }
466            }
467
468            let gen = if free_id == 0 { 65535 } else { 0 };
469            writer.write_free_entry((next % xref_len) as usize, gen);
470            written += 1;
471        }
472
473        writer.write_occupied_entry(*offset, 0);
474        written += 1;
475    }
476
477    xref_len
478}
479
480impl Debug for Pdf {
481    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
482        f.pad("Pdf(..)")
483    }
484}
485
486impl Deref for Pdf {
487    type Target = Chunk;
488
489    fn deref(&self) -> &Self::Target {
490        &self.chunk
491    }
492}
493
494impl DerefMut for Pdf {
495    fn deref_mut(&mut self) -> &mut Self::Target {
496        &mut self.chunk
497    }
498}
499
500#[derive(Default)]
501struct TrailerData {
502    catalog_id: Option<Ref>,
503    info_id: Option<Ref>,
504    file_id: Option<(Vec<u8>, Vec<u8>)>,
505}
506
507impl TrailerData {
508    fn write_into_dict(&self, dict: &mut Dict, xref_len: i32) {
509        dict.pair(Name(b"Size"), xref_len);
510
511        if let Some(catalog_id) = self.catalog_id {
512            dict.pair(Name(b"Root"), catalog_id);
513        }
514
515        if let Some(info_id) = self.info_id {
516            dict.pair(Name(b"Info"), info_id);
517        }
518
519        if let Some(file_id) = &self.file_id {
520            let mut ids = dict.insert(Name(b"ID")).array();
521            ids.item(Str(&file_id.0));
522            ids.item(Str(&file_id.1));
523        }
524    }
525}
526
527trait XRefWriter {
528    fn prologue(&mut self, xref_len: i32);
529    fn write_free_entry(&mut self, offset: usize, gen_number: u16);
530    fn write_occupied_entry(&mut self, offset: usize, gen_number: u16);
531}
532
533struct XRefStreamWriter {
534    buf: Vec<u8>,
535    field_width: u32,
536}
537
538impl XRefStreamWriter {
539    fn new(field_width: u32) -> Self {
540        Self { buf: Vec::new(), field_width }
541    }
542}
543
544impl XRefStreamWriter {
545    fn write(&mut self, entry_type: u8, offset: usize, gen_number: u16) {
546        let offset_bytes = (offset as u64).to_be_bytes();
547
548        self.buf.push(entry_type);
549        self.buf.extend(
550            offset_bytes
551                .iter()
552                .skip(offset_bytes.len() - self.field_width as usize),
553        );
554        self.buf.extend_from_slice(&gen_number.to_be_bytes());
555    }
556}
557
558impl XRefWriter for XRefStreamWriter {
559    fn prologue(&mut self, _: i32) {}
560
561    fn write_free_entry(&mut self, offset: usize, gen_number: u16) {
562        self.write(0, offset, gen_number);
563    }
564
565    fn write_occupied_entry(&mut self, offset: usize, gen_number: u16) {
566        self.write(1, offset, gen_number);
567    }
568}
569
570struct PlainXRefWriter<'a> {
571    buf: &'a mut Buf,
572}
573
574impl<'a> PlainXRefWriter<'a> {
575    fn new(buf: &'a mut Buf) -> Self {
576        Self { buf }
577    }
578}
579
580impl<'a> XRefWriter for PlainXRefWriter<'a> {
581    fn prologue(&mut self, xref_len: i32) {
582        self.buf.extend(b"xref\n0 ");
583        self.buf.push_int(xref_len);
584        self.buf.push(b'\n');
585    }
586
587    fn write_free_entry(&mut self, offset: usize, gen_number: u16) {
588        write!(self.buf.inner, "{offset:010} {gen_number:05} f\r\n").unwrap();
589    }
590
591    fn write_occupied_entry(&mut self, offset: usize, gen_number: u16) {
592        write!(self.buf.inner, "{offset:010} {gen_number:05} n\r\n").unwrap();
593    }
594}
595
596fn determine_field_width(offset: usize) -> u32 {
597    (usize::BITS - offset.leading_zeros()).div_ceil(8)
598}
599
600#[cfg(test)]
601mod tests {
602    use super::*;
603
604    /// Print a chunk.
605    #[allow(unused)]
606    pub fn print_chunk(chunk: &Chunk) {
607        println!("========== Chunk ==========");
608        for &(id, offset) in &chunk.offsets {
609            println!("[{}]: {}", id.get(), offset);
610        }
611        println!("---------------------------");
612        print!("{}", String::from_utf8_lossy(&chunk.buf));
613        println!("===========================");
614    }
615
616    /// Return the slice of bytes written during the execution of `f`.
617    pub fn slice<F>(f: F, settings: Settings) -> Vec<u8>
618    where
619        F: FnOnce(&mut Pdf),
620    {
621        let mut w = Pdf::with_settings(settings);
622        let start = w.len();
623        f(&mut w);
624        let end = w.len();
625        let buf = w.finish();
626        buf[start..end].to_vec()
627    }
628
629    /// Return the slice of bytes written for an object.
630    pub fn slice_obj<F>(f: F, settings: Settings) -> Vec<u8>
631    where
632        F: FnOnce(Obj<'_>),
633    {
634        let buf = slice(|w| f(w.indirect(Ref::new(1))), settings);
635        if settings.pretty {
636            buf[8..buf.len() - 9].to_vec()
637        } else {
638            buf[8..buf.len() - 8].to_vec()
639        }
640    }
641
642    #[test]
643    fn test_minimal() {
644        let w = Pdf::new();
645        test!(
646            w.finish(),
647            b"%PDF-1.7\n%\x80\x80\x80\x80\n",
648            b"xref\n0 1\n0000000000 65535 f\r",
649            b"trailer\n<<\n  /Size 1\n>>",
650            b"startxref\n16\n%%EOF",
651        );
652    }
653
654    #[test]
655    fn test_xref_free_list_short() {
656        let mut w = Pdf::new();
657        w.indirect(Ref::new(1)).primitive(1);
658        w.indirect(Ref::new(2)).primitive(2);
659        test!(
660            w.finish(),
661            b"%PDF-1.7\n%\x80\x80\x80\x80\n",
662            b"1 0 obj\n1\nendobj\n",
663            b"2 0 obj\n2\nendobj\n",
664            b"xref",
665            b"0 3",
666            b"0000000000 65535 f\r",
667            b"0000000016 00000 n\r",
668            b"0000000034 00000 n\r",
669            b"trailer",
670            b"<<\n  /Size 3\n>>",
671            b"startxref\n52\n%%EOF",
672        )
673    }
674
675    #[test]
676    fn test_xref_free_list_long() {
677        let mut w = Pdf::new();
678        w.set_version(1, 4);
679        w.indirect(Ref::new(1)).primitive(1);
680        w.indirect(Ref::new(2)).primitive(2);
681        w.indirect(Ref::new(5)).primitive(5);
682        test!(
683            w.finish(),
684            b"%PDF-1.4\n%\x80\x80\x80\x80\n",
685            b"1 0 obj\n1\nendobj\n",
686            b"2 0 obj\n2\nendobj\n",
687            b"5 0 obj\n5\nendobj\n",
688            b"xref",
689            b"0 6",
690            b"0000000003 65535 f\r",
691            b"0000000016 00000 n\r",
692            b"0000000034 00000 n\r",
693            b"0000000004 00000 f\r",
694            b"0000000000 00000 f\r",
695            b"0000000052 00000 n\r",
696            b"trailer",
697            b"<<\n  /Size 6\n>>",
698            b"startxref\n70\n%%EOF",
699        )
700    }
701
702    #[test]
703    #[should_panic(expected = "duplicate indirect reference id: 3")]
704    fn test_xref_free_list_duplicate() {
705        let mut w = Pdf::new();
706        w.indirect(Ref::new(3)).primitive(1);
707        w.indirect(Ref::new(5)).primitive(2);
708        w.indirect(Ref::new(13)).primitive(1);
709        w.indirect(Ref::new(3)).primitive(1);
710        w.indirect(Ref::new(6)).primitive(2);
711        w.finish();
712    }
713
714    #[test]
715    fn test_binary_marker() {
716        let mut w = Pdf::new();
717        w.set_binary_marker(b"ABCD");
718        test!(
719            w.finish(),
720            b"%PDF-1.7\n%ABCD\n",
721            b"xref\n0 1\n0000000000 65535 f\r",
722            b"trailer\n<<\n  /Size 1\n>>",
723            b"startxref\n16\n%%EOF",
724        );
725    }
726
727    #[test]
728    fn field_width() {
729        assert_eq!(determine_field_width(128), 1);
730        assert_eq!(determine_field_width(255), 1);
731        assert_eq!(determine_field_width(256), 2);
732        assert_eq!(determine_field_width(u16::MAX as usize), 2);
733        assert_eq!(determine_field_width(u16::MAX as usize + 1), 3);
734        assert_eq!(determine_field_width(u32::MAX as usize), 4);
735    }
736
737    #[test]
738    fn test_xref_stream() {
739        let mut w = Pdf::new();
740        w.indirect(Ref::new(1)).primitive(1);
741        w.indirect(Ref::new(2)).primitive(2);
742        w.indirect(Ref::new(5)).primitive(5);
743        test!(
744            w.finish_with_xref_stream(Ref::new(6)),
745            b"%PDF-1.7\n%\x80\x80\x80\x80\n",
746            b"1 0 obj\n1\nendobj\n",
747            b"2 0 obj\n2\nendobj\n",
748            b"5 0 obj\n5\nendobj\n",
749            b"6 0 obj\n<<\n  /Length 28\n  /Type /XRef\n  /Size 7\n  /W [1 1 2]\n>>\nstream",
750            // [0, 3, 255, 255], [1, 16, 0, 0], [1, 34, 0, 0], [0, 4, 0, 0], [0, 0, 0, 0], [1, 52, 0, 0], [1, 70, 0, 0]
751            b"\x00\x03\xFF\xFF\x01\x10\x00\x00\x01\x22\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x01\x34\x00\x00\x01\x46\x00\x00",
752            b"endstream\nendobj\n",
753            b"startxref\n70\n%%EOF",
754        )
755    }
756
757    #[test]
758    fn test_xref_stream_single_filter() {
759        let mut w = Pdf::new();
760        w.indirect(Ref::new(1)).primitive(1);
761        test!(
762            w.finish_with_xref_stream_and_filter(Ref::new(2), |_| (b"ABCDEFGH".to_vec(), XRefFilter::Single(Filter::FlateDecode))),
763            b"%PDF-1.7\n%\x80\x80\x80\x80\n",
764            b"1 0 obj\n1\nendobj\n",
765            b"2 0 obj\n<<\n  /Length 8\n  /Type /XRef\n  /Filter /FlateDecode\n  /Size 3\n  /W [1 1 2]\n>>\nstream",
766            b"ABCDEFGH",
767            b"endstream\nendobj\n",
768            b"startxref\n34\n%%EOF",
769        )
770    }
771
772    #[test]
773    fn test_xref_stream_multiple_filters() {
774        let mut w = Pdf::new();
775        w.indirect(Ref::new(1)).primitive(1);
776        test!(
777            w.finish_with_xref_stream_and_filter(Ref::new(2), |_| (b"ABCDEFGH".to_vec(), XRefFilter::Multiple(vec![Filter::AsciiHexDecode, Filter::FlateDecode]))),
778            b"%PDF-1.7\n%\x80\x80\x80\x80\n",
779            b"1 0 obj\n1\nendobj\n",
780            b"2 0 obj\n<<\n  /Length 8\n  /Type /XRef\n  /Filter [/ASCIIHexDecode /FlateDecode]\n  /Size 3\n  /W [1 1 2]\n>>\nstream",
781            b"ABCDEFGH",
782            b"endstream\nendobj\n",
783            b"startxref\n34\n%%EOF",
784        )
785    }
786
787    #[test]
788    fn test_xref_width2() {
789        let mut w = Pdf::new();
790        w.stream(Ref::new(1), &[b'0'; 256]);
791        w.indirect(Ref::new(2)).primitive(1);
792        test!(
793            w.finish_with_xref_stream(Ref::new(3)),
794            b"%PDF-1.7\n%\x80\x80\x80\x80\n",
795            b"1 0 obj\n<<\n  /Length 256\n>>\nstream",
796            b"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
797            000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
798            000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
799            b"endstream\nendobj\n",
800            b"2 0 obj\n1\nendobj\n",
801            b"3 0 obj\n<<\n  /Length 20\n  /Type /XRef\n  /Size 4\n  /W [1 2 2]\n>>\nstream",
802            // [0, 0, 0, 255, 255], [1, 0, 16, 0, 0], [1, 0, 34, 0, 0], [1, 1, 32, 0, 0]
803            b"\x00\x00\x00\xFF\xFF\x01\x00\x10\x00\x00\x01\x01\x46\x00\x00\x01\x01\x58\x00\x00",
804            b"endstream\nendobj\n",
805            b"startxref\n344\n%%EOF",
806        )
807    }
808}