Class: Pdfrb::Writer
- Inherits:
-
Object
- Object
- Pdfrb::Writer
- 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"- TRAILER_OVERRIDDEN_KEYS =
Trailer keys each writer recomputes itself; existing values for these are never carried through from the parsed trailer.
%i[Size Root Prev XRefStm].freeze
Instance Attribute Summary collapse
-
#document ⇒ Object
readonly
Returns the value of attribute document.
-
#io ⇒ Object
readonly
Returns the value of attribute io.
-
#serializer ⇒ Object
readonly
Returns the value of attribute serializer.
Class Method Summary collapse
-
.serializer_encrypter_opts_for(document) ⇒ Object
Build the encrypter option for the Serializer from the document's /Encrypt dict.
-
.serializer_for(document) ⇒ Object
Build a Serializer configured for
document: stream compression from config, and an encrypter derived from the trailer /Encrypt. - .write(document, io) ⇒ Object
- .write_incremental(document, io) ⇒ Object
Instance Method Summary collapse
-
#encryption_dict?(obj) ⇒ Boolean
The /Encrypt dictionary's own strings (U, O, UE, OE, Perms) must stay in cleartext — they carry the password hashes.
-
#initialize(document, io) ⇒ Writer
constructor
A new instance of Writer.
- #write ⇒ Object
-
#write_incremental ⇒ Object
Append a new revision to the original byte stream.
Constructor Details
#initialize(document, io) ⇒ Writer
Returns a new instance of Writer.
26 27 28 29 30 31 |
# File 'lib/pdfrb/writer.rb', line 26 def initialize(document, io) @document = document @io = io @serializer = Writer.serializer_for(document) @xref_offsets = {} end |
Instance Attribute Details
#document ⇒ Object (readonly)
Returns the value of attribute document.
24 25 26 |
# File 'lib/pdfrb/writer.rb', line 24 def document @document end |
#io ⇒ Object (readonly)
Returns the value of attribute io.
24 25 26 |
# File 'lib/pdfrb/writer.rb', line 24 def io @io end |
#serializer ⇒ Object (readonly)
Returns the value of attribute serializer.
24 25 26 |
# File 'lib/pdfrb/writer.rb', line 24 def serializer @serializer end |
Class Method Details
.serializer_encrypter_opts_for(document) ⇒ Object
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.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/pdfrb/writer.rb', line 50 def self.serializer_encrypter_opts_for(document) # 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 = document.resolve(encrypt_ref) return {} unless encrypt_dict handler = Pdfrb::Encryption::StandardSecurityHandler.new( { Encrypt: encrypt_dict.value, ID: trailer[:ID] } ) password = document.config["encryption.password"] || "" unless handler.verify_user_password?(password) raise Pdfrb::EncryptionError, "cannot write encrypted document: password does not verify" end { encrypter: handler } end |
.serializer_for(document) ⇒ Object
Build a Serializer configured for document: stream compression
from config, and an encrypter derived from the trailer /Encrypt.
Raises EncryptionError when the document is encrypted but the
configured password does not open it — writing must never
silently fall back to plaintext.
38 39 40 41 42 43 44 |
# File 'lib/pdfrb/writer.rb', line 38 def self.serializer_for(document) Serializer.new( compress_streams: document.config["writer.compress_streams"], compress_min_size: document.config["writer.compress_min_size"], **serializer_encrypter_opts_for(document) ) end |
.write(document, io) ⇒ Object
83 84 85 |
# File 'lib/pdfrb/writer.rb', line 83 def self.write(document, io) new(document, io).write end |
.write_incremental(document, io) ⇒ Object
87 88 89 |
# File 'lib/pdfrb/writer.rb', line 87 def self.write_incremental(document, io) new(document, io).write_incremental end |
Instance Method Details
#encryption_dict?(obj) ⇒ Boolean
The /Encrypt dictionary's own strings (U, O, UE, OE, Perms) must stay in cleartext — they carry the password hashes.
79 80 81 |
# File 'lib/pdfrb/writer.rb', line 79 def encryption_dict?(obj) (document.trailer || {})[:Encrypt]&.oid == obj.oid end |
#write ⇒ Object
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/pdfrb/writer.rb', line 91 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, skip_string_encryption: encryption_dict?(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_incremental ⇒ Object
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.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/pdfrb/writer.rb', line 128 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, skip_string_encryption: encryption_dict?(obj)) end xref_pos = write_xref(prev: previous_xref_offset) write_trailer(xref_pos, prev: previous_xref_offset) @io.flush self end |