class HexaPDF::Writer
Writes the contents of a PDF document to an IO stream.
Public Class Methods
Creates a new writer object for the given HexaPDF
document that gets written to the IO object.
# File lib/hexapdf/writer.rb, line 58 def initialize(document, io) @document = document @io = io @io.binmode @io.seek(0, IO::SEEK_SET) # TODO: incremental update! @serializer = Serializer.new @serializer.encrypter = @document.encrypted? ? @document.security_handler : nil @rev_size = 0 end
Writes the document to the IO object. If incremental
is true
and the document was created from an existing PDF file, the changes are appended to a full copy of the source document.
# File lib/hexapdf/writer.rb, line 48 def self.write(document, io, incremental: false) if incremental && document.revisions.parser new(document, io).write_incremental else new(document, io).write end end
Public Instance Methods
Writes the document to the IO object.
# File lib/hexapdf/writer.rb, line 71 def write write_file_header pos = nil @document.trailer.info[:Producer] = "HexaPDF version #{HexaPDF::VERSION}" @document.revisions.each do |rev| pos = write_revision(rev, pos) end end
Writes the complete source document and one revision containing all changes to the IO.
For this method to work the document must have been created from an existing file.
# File lib/hexapdf/writer.rb, line 84 def write_incremental @document.revisions.parser.io.seek(0, IO::SEEK_SET) IO.copy_stream(@document.revisions.parser.io, @io) @rev_size = @document.revisions.current.next_free_oid revision = Revision.new(@document.revisions.current.trailer) @document.revisions.each do |rev| rev.each_modified_object {|obj| revision.send(:add_without_check, obj) } end write_revision(revision, @document.revisions.parser.startxref_offset) end
Private Instance Methods
Writes the PDF file header.
See: PDF1.7 s7.5.2
# File lib/hexapdf/writer.rb, line 102 def write_file_header @io << "%PDF-#{@document.version}\n%\xCF\xEC\xFF\xE8\xD7\xCB\xCD\n" end
Writes the single indirect object which may be a stream object or another object.
# File lib/hexapdf/writer.rb, line 180 def write_indirect_object(obj) @io << "#{obj.oid} #{obj.gen} obj\n" @serializer.serialize_to_io(obj, @io) @io << "\nendobj\n" end
Writes the given revision.
The optional previous_xref_pos
argument needs to contain the byte position of the previous cross-reference section or stream if applicable.
# File lib/hexapdf/writer.rb, line 110 def write_revision(rev, previous_xref_pos = nil) xref_stream, object_streams = xref_and_object_streams(rev) obj_to_stm = object_streams.each_with_object({}) {|stm, m| m.update(stm.write_objects(rev)) } xref_section = XRefSection.new xref_section.add_free_entry(0, 65535) if previous_xref_pos.nil? rev.each do |obj| if obj.null? xref_section.add_free_entry(obj.oid, obj.gen) elsif (objstm = obj_to_stm[obj]) xref_section.add_compressed_entry(obj.oid, objstm.oid, objstm.object_index(obj)) elsif obj != xref_stream xref_section.add_in_use_entry(obj.oid, obj.gen, @io.pos) write_indirect_object(obj) end end trailer = rev.trailer.value.dup trailer.delete(:XRefStm) if previous_xref_pos trailer[:Prev] = previous_xref_pos else trailer.delete(:Prev) end @rev_size = rev.next_free_oid if rev.next_free_oid > @rev_size trailer[:Size] = @rev_size startxref = @io.pos if xref_stream xref_section.add_in_use_entry(xref_stream.oid, xref_stream.gen, startxref) xref_stream.update_with_xref_section_and_trailer(xref_section, trailer) write_indirect_object(xref_stream) else write_xref_section(xref_section) write_trailer(trailer) end write_startxref(startxref) startxref end
Writes the startxref line needed for cross-reference sections and cross-reference streams.
See: PDF1.7 s7.5.5, s7.5.8
# File lib/hexapdf/writer.rb, line 216 def write_startxref(startxref) @io << "startxref\n#{startxref}\n%%EOF\n" end
Writes the trailer dictionary.
See: PDF1.7 s7.5.5
# File lib/hexapdf/writer.rb, line 209 def write_trailer(trailer) @io << "trailer\n#{@serializer.serialize(trailer)}\n" end
Writes the cross-reference section.
See: PDF1.7 s7.5.4
# File lib/hexapdf/writer.rb, line 189 def write_xref_section(xref_section) @io << "xref\n" xref_section.each_subsection do |entries| @io << "#{entries.empty? ? 0 : entries.first.oid} #{entries.size}\n" entries.each do |entry| if entry.in_use? @io << sprintf("%010d %05d n \n", entry.pos, entry.gen).freeze elsif entry.free? @io << "0000000000 65535 f \n" else # Should never occur since we create the xref section! raise HexaPDF::Error, "Cannot use xref type #{entry.type} in cross-reference section" end end end end
Returns the cross-reference and object streams of the given revision.
An error is raised if the revision contains object streams and no cross-reference stream. If it contains multiple cross-reference streams only the first one is used, the rest are ignored.
# File lib/hexapdf/writer.rb, line 160 def xref_and_object_streams(rev) xref_stream = nil object_streams = [] rev.each do |obj| if obj.type == :ObjStm object_streams << obj elsif !xref_stream && obj.type == :XRef xref_stream = obj end end if !object_streams.empty? && xref_stream.nil? raise HexaPDF::Error, "Cannot use object streams when there is no xref stream" end [xref_stream, object_streams] end