1#![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
107pub 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
151pub 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
208pub struct Pdf {
217 chunk: Chunk,
218 trailer_data: TrailerData,
219}
220
221impl Pdf {
222 #[allow(clippy::new_without_default)]
225 pub fn new() -> Self {
226 Self::with_settings(Settings::default())
227 }
228
229 pub fn with_settings(settings: Settings) -> Self {
232 Self::with_settings_and_capacity(settings, 8 * 1024)
233 }
234
235 pub fn with_capacity(capacity: usize) -> Self {
238 Self::with_settings_and_capacity(Settings::default(), capacity)
239 }
240
241 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 pub fn set_binary_marker(&mut self, marker: &[u8; 4]) {
256 self.chunk.buf.inner[10..14].copy_from_slice(marker);
257 }
258
259 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 pub fn set_file_id(&mut self, id: (Vec<u8>, Vec<u8>)) {
281 self.trailer_data.file_id = Some(id);
282 }
283
284 pub fn catalog(&mut self, id: Ref) -> Catalog<'_> {
289 self.trailer_data.catalog_id = Some(id);
290 self.indirect(id).start()
291 }
292
293 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 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 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 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 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 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
418pub enum XRefFilter {
420 Single(Filter),
422 Multiple(Vec<Filter>),
424}
425
426fn finish_trailer(mut buf: Buf, xref_offset: usize, pad: &[u8]) -> Vec<u8> {
427 buf.extend(pad);
428 buf.extend(b"startxref\n");
430 write!(buf.inner, "{}", xref_offset).unwrap();
431
432 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 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 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 #[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 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 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 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 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}