Class: Pdfrb::Writer

Inherits:
Object
  • Object
show all
Defined in:
lib/pdfrb/writer.rb

Overview

Writes a Document to an IO as a complete PDF file: header, indirect objects, xref section, trailer.

Two modes:

* +write+ (default) — full rewrite; assign oids to new objects,
emit every object the document references.
* +write_incremental+ — append a new revision to an existing
byte stream. Only valid when the document was opened from an
IO; the original bytes are preserved verbatim, and a new
xref + trailer are appended with /Prev pointing back.

Constant Summary collapse

DEFAULT_VERSION =
"1.4"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document, io) ⇒ Writer

Returns a new instance of Writer.



21
22
23
24
25
26
27
28
29
30
# File 'lib/pdfrb/writer.rb', line 21

def initialize(document, io)
  @document = document
  @io = io
  @serializer = Serializer.new(
    compress_streams: document.config["writer.compress_streams"],
    compress_min_size: document.config["writer.compress_min_size"],
    **serializer_encrypter_opts
  )
  @xref_offsets = {}
end

Instance Attribute Details

#documentObject (readonly)

Returns the value of attribute document.



19
20
21
# File 'lib/pdfrb/writer.rb', line 19

def document
  @document
end

#ioObject (readonly)

Returns the value of attribute io.



19
20
21
# File 'lib/pdfrb/writer.rb', line 19

def io
  @io
end

#serializerObject (readonly)

Returns the value of attribute serializer.



19
20
21
# File 'lib/pdfrb/writer.rb', line 19

def serializer
  @serializer
end

Class Method Details

.write(document, io) ⇒ Object



64
65
66
# File 'lib/pdfrb/writer.rb', line 64

def self.write(document, io)
  new(document, io).write
end

.write_incremental(document, io) ⇒ Object



68
69
70
# File 'lib/pdfrb/writer.rb', line 68

def self.write_incremental(document, io)
  new(document, io).write_incremental
end

Instance Method Details

#serializer_encrypter_optsObject

Build the encrypter option for the Serializer from the document's /Encrypt dict. If the document isn't encrypted, returns empty hash. The SecurityHandler's encrypt_data method becomes the Serializer's encrypter, which encrypts string/stream payloads per-object.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/pdfrb/writer.rb', line 36

def serializer_encrypter_opts
  # Use a pre-configured handler if the document provides one
  # (e.g., set by the encryption CLI).
  handler = document.config["encryption.handler"]
  return { encrypter: handler } if handler

  trailer = document.trailer
  return {} unless trailer

  encrypt_ref = trailer[:Encrypt]
  return {} unless encrypt_ref

  encrypt_dict = encrypt_ref.is_a?(Pdfrb::Model::Reference) ?
                    document.object(encrypt_ref) : encrypt_ref
  return {} unless encrypt_dict

  handler = Pdfrb::Encryption::StandardSecurityHandler.new(
    Encrypt: encrypt_dict.value,
    ID: trailer[:ID]
  )
  password = document.config["encryption.password"] || ""
  handler.verify_user_password(password)

  { encrypter: handler }
rescue StandardError
  {}
end

#writeObject



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/pdfrb/writer.rb', line 72

def write
  @io.binmode
  @io.truncate(0) if @io.is_a?(StringIO)
  write_header
  dispatch_before_write

  use_stream = document.config["writer.use_xref_stream"]
  pack_objstm = document.config["writer.pack_object_streams"]

  packed = pack_objstm ? pack_object_streams : {}

  each_indirect_object do |obj|
    next if packed.key?(obj.oid)

    @xref_offsets[obj.oid] = @io.pos
    @io << @serializer.serialize_indirect(obj)
  end

  xref_pos = if use_stream
               write_xref_stream(packed)
             else
               write_xref
             end
  if use_stream
    write_xref_stream_trailer(xref_pos)
  else
    write_trailer(xref_pos)
  end
  @io.flush
  self
end

#write_incrementalObject

Append a new revision to the original byte stream. Requires the document to have been opened from an IO (so we can copy its original bytes verbatim) and the IO the writer is given is the target output.

Raises:



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/pdfrb/writer.rb', line 108

def write_incremental
  raise Pdfrb::Error, "incremental write requires a source IO" unless document.io

  @io.binmode
  # Copy the original bytes verbatim.
  document.io.seek(0, IO::SEEK_SET)
  @io << document.io.read
  dispatch_before_write
  # Emit only the modified objects.
  document.each_indirect_object do |obj|
    next unless obj.indirect?

    @xref_offsets[obj.oid] = @io.pos
    @io << @serializer.serialize_indirect(obj)
  end
  xref_pos = write_xref(prev: previous_xref_offset)
  write_trailer(xref_pos, prev: previous_xref_offset)
  @io.flush
  self
end