Class Origami::PDF
In: sources/parser/encryption.rb
sources/parser/pdf.rb
sources/parser/trailer.rb
sources/parser/file.rb
sources/parser/xreftable.rb
sources/parser/signature.rb
sources/parser/linearization.rb
sources/parser/export.rb
sources/parser/acroform.rb
sources/parser/catalog.rb
sources/parser/page.rb
sources/parser/obfuscation.rb
sources/parser/metadata.rb
sources/parser/header.rb
Parent: Object

Main class representing a PDF file and its inner contents. A PDF file contains a set of Revision.

Methods

Classes and Modules

Module Origami::PDF::Instruction
Class Origami::PDF::Header
Class Origami::PDF::Revision

Constants

V = version
R = revision
Length = params[:KeyLength]
P = params[:Permissions]
EncryptMetadata = params[:EncryptMetadata]
CF = Dictionary.new
AuthEvent = :DocOpen
CFM = :AESV2
Length = params[:KeyLength] >> 3
StmF = handler.StrF = :StdCF
W = [ 1, (xrefstm_offset.to_s(2).size + 7) >> 3, 2 ]
Prev = prev_xref_offset
Size = objset.size + 1
Prev = prev_xref_offset
XRefStm = xrefstm_offset if options[:use_xrefstm] == true
Size = size + 1
Root = root
Pages = PageTreeNode.new.set_indirect(true)
Root = catalog.reference
Size = size + 1
ID = [ id, id ]
Rect = Rectangle[:llx => 0.0, :lly => 0.0, :urx => 0.0, :ury => 0.0]
V = digsig ;
SigFlags = InteractiveForm::SigFlags::SIGNATURESEXIST | InteractiveForm::SigFlags::APPENDONLY
Location = HexaString.new(location) if location
ContactInfo = HexaString.new(contact) if contact
Reason = HexaString.new(reason) if reason
Data = self.Catalog
TransformParams = UsageRights::TransformParams.new
V = UsageRights::TransformParams::VERSION
Reference = [ sigref ]
UR3 = digsig
Root = self << cat
OpenAction = action
WC = action
WP = action
Names = Names.new
Count = treeroot.Kids.length
Parent = treeroot

External Aliases

get_object -> []

Attributes

filename  [RW] 
header  [RW] 
revisions  [RW] 

Public Class methods

Deserializes a PDF dump.

[Source]

     # File sources/parser/pdf.rb, line 151
151:       def deserialize(filename)
152:         
153:         Zlib::GzipReader.open(filename) { |gz|
154:           pdf = Marshal.load(gz.read)
155:         }
156:         
157:         pdf
158:       end

Creates a new PDF instance.

init_structure:If this flag is set, then some structures will be automatically generated while manipulating this PDF. Set it if you are creating a new PDF file, this must not be used when parsing an existing file.

[Source]

     # File sources/parser/pdf.rb, line 166
166:     def initialize(init_structure = true)
167: 
168:       @header = PDF::Header.new
169:       @revisions = []
170:       
171:       add_new_revision
172:       
173:       @revisions.first.trailer = Trailer.new
174: 
175:       init if init_structure
176:     end

Read and parse a PDF file from disk.

[Source]

     # File sources/parser/pdf.rb, line 144
144:       def read(filename, options = {:verbosity => Parser::VERBOSE_INSANE})
145:         Parser.new(options).parse(filename)
146:       end

Public Instance methods

Adds a new object to the PDF file. If this object has no version number, then a new one will be automatically computed and assignated to him. It returns a Reference to this Object.

object:The object to add.

[Source]

     # File sources/parser/pdf.rb, line 383
383:     def <<(object)
384:        
385:       add_to_revision(object, @revisions.last)
386:       
387:     end

Returns the current Catalog Dictionary.

[Source]

    # File sources/parser/catalog.rb, line 33
33:     def Catalog
34:       get_doc_attr(:Root)
35:     end

Sets the current Catalog Dictionary.

[Source]

    # File sources/parser/catalog.rb, line 40
40:     def Catalog=(cat)
41:       
42:       unless cat.is_a?(Catalog)
43:         raise TypeError, "Expected type Catalog, received #{cat.class}"
44:       end
45:       
46:       if @revisions.last.trailer.Root
47:         delete_object(@revisions.last.trailer.Root)
48:       end
49:       
50:       @revisions.last.trailer.Root = self << cat
51:     end

Add a field to the Acrobat form.

field:The Field to add.

[Source]

    # File sources/parser/acroform.rb, line 50
50:     def add_field(field)
51:       
52:       if field.is_a?(::Array)
53:         raise TypeError, "Expected array of Fields" unless field.all? { |f| f.is_a?(Field) }
54:       elsif not field.is_a?(Field)
55:         raise TypeError, "Expected Field, received #{field.class}"
56:       end
57:       
58:       fields = field.is_a?(Field) ? [field] : field
59:       
60:       self.Catalog.AcroForm ||= InteractiveForm.new
61:       self.Catalog.AcroForm.Fields ||= []
62:       
63:       self.Catalog.AcroForm.Fields.concat(fields)
64:       
65:       self
66:     end

Ends the current Revision, and starts a new one.

[Source]

     # File sources/parser/pdf.rb, line 653
653:     def add_new_revision
654:       
655:       root = @revisions.last.trailer[:Root] unless @revisions.empty?
656: 
657:       @revisions << Revision.new(self)
658:       @revisions.last.trailer = Trailer.new
659:       @revisions.last.trailer.Root = root
660: 
661:       self
662:     end

Adds a new object to a specific revision. If this object has no version number, then a new one will be automatically computed and assignated to him. It returns a Reference to this Object.

object:The object to add.
revision:The revision to add the object to.

[Source]

     # File sources/parser/pdf.rb, line 396
396:     def add_to_revision(object, revision)
397:      
398:       object.set_indirect(true)
399:       object.set_pdf(self)
400:       
401:       object.no, object.generation = alloc_new_object_number if object.no == 0
402:       
403:       revision.body[object.reference] = object
404:       
405:       object.reference
406:     end

Returns a new number/generation for future object.

[Source]

     # File sources/parser/pdf.rb, line 411
411:     def alloc_new_object_number
412:       
413:       no = 1
414:       no = no + 1 while get_object(no)
415: 
416:       objset = indirect_objects
417: 
418:       no = 
419:       if objset.size == 0 then 1
420:       else 
421:         indirect_objects.keys.max.refno + 1
422:       end
423: 
424:       [ no, 0 ]
425:     end

[Source]

    # File sources/parser/page.rb, line 26
26:     def append_page(page = Page.new, *more)
27:     
28:       pages = [ page ].concat(more)
29:       
30:       fail "Expecting Page type, instead of #{page.class}" unless pages.all?{|page| page.is_a?(Page)}
31:       
32:       treeroot = self.Catalog.Pages
33:       
34:       treeroot.Kids ||= [] #:nodoc:
35:       treeroot.Kids.concat(pages)
36:       treeroot.Count = treeroot.Kids.length
37:       
38:       pages.each do |page| 
39:         page.Parent = treeroot
40:       end
41:       
42:       self
43:     end

[Source]

     # File sources/parser/pdf.rb, line 330
330:       def append_subobj(root, objset, inc_objstm)
331:         
332:         if objset.find{ |o| root.equal?(o) }.nil?
333:           
334:           objset << root
335: 
336:           if root.is_a?(Dictionary)
337:             root.each_pair { |name, value|
338:               append_subobj(name, objset, inc_objstm)
339:               append_subobj(value, objset, inc_objstm)
340:             }
341:           elsif root.is_a?(Array) or (root.is_a?(ObjectStream) and inc_objstm == true)
342:             root.each { |subobj| append_subobj(subobj, objset, inc_objstm) }
343:           end
344:         
345:         end
346:         
347:       end

Attachs an embedded file to the PDF.

path:The path to the file to attach.
options:A set of options to configure the attachment.

[Source]

    # File sources/parser/file.rb, line 35
35:     def attach_file(path, options = {})
36: 
37:       #
38:       # Default options.
39:       #
40:       params = 
41:       {
42:         :Register => true,                      # Shall the file be registered in the name directory ?
43:         :EmbeddedName => File.basename(path),   # The inner filename of the attachment.
44:         :Filter => :FlateDecode                 # The stream filter used to store data.
45:       }
46: 
47:       params.update(options)
48:       
49:       fdata = File.open(path, "r").binmode.read
50:       
51:       fstream = EmbeddedFileStream.new
52:       fstream.data = fdata
53:       fstream.setFilter(params[:Filter])
54:       
55:       name = params[:EmbeddedName]
56:       fspec = FileSpec.new.setType(:Filespec).setF(name).setEF(FileSpec.new(:F => fstream))
57:       
58:       register(Names::Root::EMBEDDEDFILES, name, fspec) if params[:Register] == true
59: 
60:       fspec
61:     end

This method is meant to recompute, verify and correct main PDF structures, in order to output a proper file.

  • Allocates objects references.
  • Sets some objects missing required values.

[Source]

     # File sources/parser/pdf.rb, line 432
432:     def compile
433:       
434:       #
435:       # A valid document must have at least one page.
436:       #
437:       append_page if pages.empty?
438:      
439:       #
440:       # Allocates object numbers and creates references.
441:       # Invokes object finalization methods.
442:       #
443:       physicalize
444:             
445:       #
446:       # Sets the PDF version header.
447:       #
448:       pdf_version = version_required
449:       @header.majorversion = pdf_version.to_s[0,1].to_i
450:       @header.minorversion = pdf_version.to_s[2,1].to_i
451:       
452:       self
453:     end

[Source]

    # File sources/parser/acroform.rb, line 37
37:     def create_acroform(*fields)
38:       acroform = self.Catalog.AcroForm ||= InteractiveForm.new.set_indirect(true)
39: 
40:       acroform.Fields ||= []
41:       acroform.Fields.concat(fields)
42: 
43:       acroform
44:     end

Decrypts the current document (only RC4 40..128 bits). TODO: AESv2, AESv3, lazy decryption

passwd:The password to decrypt the document.

[Source]

     # File sources/parser/encryption.rb, line 54
 54:     def decrypt(passwd = "")
 55:     
 56:       unless self.is_encrypted?
 57:         raise EncryptionError, "PDF is not encrypted"
 58:       end
 59:      
 60:       encrypt_dict = get_doc_attr(:Encrypt)
 61:       handler = Encryption::Standard::Dictionary.new(encrypt_dict.copy)
 62: 
 63:       unless handler.Filter == :Standard
 64:         raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter.to_s}'"
 65:       end
 66: 
 67:       case handler.V.to_i
 68:         when 1,2 then str_algo = stm_algo = Encryption::ARC4
 69:         when 4
 70:           if handler[:CF].is_a?(Dictionary)
 71:             cfs = handler[:CF]
 72:             
 73:             if handler[:StrF].is_a?(Name) and cfs[handler[:StrF]].is_a?(Dictionary)
 74:               cfdict = cfs[handler[:StrF]]
 75:               
 76:               str_algo =
 77:               if cfdict[:CFM] == :V2 then Encryption::ARC4
 78:               elsif cfdict[:CFM] == :AESV2 then Encryption::AES
 79:               elsif cfdict[:CFM] == :None then Encryption::Identity
 80:               else
 81:                 Encryption::Identity
 82:               end
 83:             else
 84:               str_algo = Encryption::Identity
 85:             end
 86: 
 87:             if handler[:StmF].is_a?(Name) and cfs[handler[:StmF]].is_a?(Dictionary)
 88:               cfdict = cfs[handler[:StmF]]
 89: 
 90:               stm_algo =
 91:               if cfdict[:CFM] == :V2 then Encryption::ARC4
 92:               elsif cfdict[:CFM] == :AESV2 then Encryption::AES
 93:               elsif cfdict[:CFM] == :None then Encryption::Identity
 94:               else
 95:                 Encryption::Identity
 96:               end
 97:             else
 98:               stm_algo = Encryption::Identity
 99:             end
100: 
101:           else
102:             str_algo = stm_algo = Encryption::Identity
103:           end
104:       else
105:         raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
106:       end
107: 
108:       id = get_doc_attr(:ID)
109:       if id.nil? or not id.is_a?(Array)
110:         raise EncryptionError, "Document ID was not found or is invalid"
111:       else
112:         id = id.first
113:       end
114: 
115:       if not handler.is_owner_password?(passwd, id) and not handler.is_user_password?(passwd, id)
116:         raise EncryptionInvalidPasswordError
117:       end
118: 
119:       encryption_key = handler.compute_encryption_key(passwd, id)
120: 
121:       #self.extend(Encryption::EncryptedDocument)
122:       #self.encryption_dict = encrypt_dict
123:       #self.encryption_key = encryption_key
124:       #self.stm_algo = self.str_algo = algorithm
125: 
126:       encrypt_metadata = (handler.EncryptMetadata != false)
127:       #
128:       # Should be fixed to exclude only the active XRefStream
129:       #
130:       encrypted_objects = self.objects(false).find_all{ |obj|
131: 
132:         (obj.is_a?(String) and 
133:           not obj.indirect_parent.is_a?(XRefStream) and 
134:           not obj.equal?(encrypt_dict[:U]) and 
135:           not obj.equal?(encrypt_dict[:O])) or 
136:         
137:         (obj.is_a?(Stream) and 
138:           not obj.is_a?(XRefStream) and
139:           (not obj.equal?(self.Catalog.Metadata) or encrypt_metadata))
140:       }
141:      
142:       encrypted_objects.each { |obj|
143:         no = obj.indirect_parent.no
144:         gen = obj.indirect_parent.generation
145: 
146:         k = encryption_key + [no].pack("I")[0..2] + [gen].pack("I")[0..1]
147:         key_len = (k.length > 16) ? 16 : k.length
148: 
149:         case obj
150:           when String
151:             k << "sAlT" if str_algo == Encryption::AES
152:           when Stream
153:             k << "sAlT" if stm_algo == Encryption::AES
154:         end
155: 
156:         key = Digest::MD5.digest(k)[0, key_len]
157: 
158:         case obj
159:           when String then obj.replace(str_algo.decrypt(key, obj.value))
160:           when Stream then obj.rawdata = stm_algo.decrypt(key, obj.rawdata) 
161:         end
162:       }
163: 
164:       self
165:     end

Remove an object.

[Source]

     # File sources/parser/pdf.rb, line 691
691:     def delete_object(no, generation = 0)
692:       
693:       case no
694:         when Reference
695:           target = no
696:         when ::Integer
697:           target = Reference.new(no, generation)
698:       else
699:         raise TypeError, "Invalid parameter type : #{no.class}" 
700:       end
701:       
702:       @revisions.each do |rev|
703:         rev.body.delete(target)
704:       end
705: 
706:     end

[Source]

    # File sources/parser/xreftable.rb, line 30
30:       def delete_xrefstm(xrefstm)
31:         prev = xrefstm.Prev
32:         delete_object(xrefstm.reference)
33: 
34:         if prev.is_a?(Integer) and (prev_stm = get_object_by_offset(prev)).is_a?(XRefStream)
35:           delete_xrefstm(prev_stm)
36:         end
37:       end

Tries to delinearize the document if it has been linearized. This operation is xrefs destructive, should be fixed in the future to merge tables.

[Source]

    # File sources/parser/linearization.rb, line 43
43:     def delinearize!
44:       raise InvalidPDF, 'Not a linearized document' unless is_linearized?
45:       
46:       #
47:       # Saves the catalog location.
48:       #
49:       catalog_ref = self.Catalog.reference
50: 
51:       lin_dict = @revisions.first.body.values.first
52:       hints = lin_dict[:H]
53:   
54:       #
55:       # Removes hint streams used by linearization.
56:       #
57:       if hints.is_a?(::Array)
58:         if hints.length > 0 and hints[0].is_a?(Integer)
59:           hint_stream = get_object_by_offset(hints[0])
60:           delete_object(hint_stream.reference) if hint_stream.is_a?(Stream)
61:         end
62: 
63:         if hints.length > 2 and hints[2].is_a?(Integer)
64:           overflow_stream = get_object_by_offset(hints[2])
65:           delete_object(overflow_stream.reference) if overflow_stream.is_a?(Stream)
66:         end
67:       end
68: 
69:       #
70:       # Should be merged instead.
71:       #
72:       remove_xrefs
73: 
74:       #
75:       # Remove the linearization revision.
76:       #
77:       remove_revision(0)
78: 
79:       #
80:       # Restore the Catalog.
81:       #
82:       @revisions.last.trailer ||= Trailer.new
83:       @revisions.last.trailer.dictionary[:Root] = catalog_ref
84: 
85:       self
86:     end

Enable the document Usage Rights.

rights:list of rights defined in UsageRights::Rights

[Source]

     # File sources/parser/signature.rb, line 130
130:     def enable_usage_rights(*rights)
131:       
132:       def signfield_size(certificate, key, ca = []) #:nodoc:
133:         datatest = "abcdefghijklmnopqrstuvwxyz"
134:         OpenSSL::PKCS7.sign(certificate, key, datatest, ca, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der.size + 128
135:       end
136:       
137:       begin
138:         key = OpenSSL::PKey::RSA.new(File.open('adobe.key','r').binmode.read)
139:         certificate = OpenSSL::X509::Certificate.new(File.open('adobe.crt','r').binmode.read)
140:       rescue
141:         warn "The Adobe private key is necessary to enable usage rights.\nYou do not seem to be Adobe :)... Aborting."
142:         return nil
143:       end
144:       
145:       digsig = Signature::DigitalSignature.new.set_indirect(true)
146:       
147:       self.Catalog.AcroForm ||= InteractiveForm.new
148:       #self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPENDONLY
149:       
150:       digsig.Type = :Sig #:nodoc:
151:       digsig.Contents = HexaString.new("\x00" * signfield_size(certificate, key, [])) #:nodoc:
152:       digsig.Filter = Name.new("Adobe.PPKLite") #:nodoc:
153:       digsig.Name = "ARE Acrobat Product v8.0 P23 0002337" #:nodoc:
154:       digsig.SubFilter = Name.new("adbe.pkcs7.detached") #:nodoc:
155:       digsig.ByteRange = [0, 0, 0, 0] #:nodoc:
156:       
157:       sigref = Signature::Reference.new #:nodoc:
158:       sigref.Type = :SigRef #:nodoc:
159:       sigref.TransformMethod = :UR3 #:nodoc:
160:       sigref.Data = self.Catalog
161:       
162:       sigref.TransformParams = UsageRights::TransformParams.new
163:       sigref.TransformParams.P = true #:nodoc:
164:       sigref.TransformParams.Type = :TransformParams #:nodoc:
165:       sigref.TransformParams.V = UsageRights::TransformParams::VERSION
166:       
167:       rights.each { |right|
168:         
169:         sigref.TransformParams[right.first] ||= []
170:         sigref.TransformParams[right.first].concat(right[1..-1])
171:         
172:       }
173:       
174:       digsig.Reference = [ sigref ]
175:       
176:       self.Catalog.Perms ||= Perms.new
177:       self.Catalog.Perms.UR3 = digsig
178:       
179:       #
180:       #  Flattening the PDF to get file view.
181:       #
182:       self.compile
183:       
184:       #
185:       # Creating an empty Xref table to compute signature byte range.
186:       #
187:       rebuild_dummy_xrefs
188:       
189:       sigoffset = get_object_offset(digsig.no, digsig.generation) + digsig.sigOffset
190:       
191:       digsig.ByteRange[0] = 0 
192:       digsig.ByteRange[1] = sigoffset
193:       digsig.ByteRange[2] = sigoffset + digsig.Contents.size
194:       
195:       digsig.ByteRange[3] = filesize - digsig.ByteRange[2] until digsig.ByteRange[3] == filesize - digsig.ByteRange[2]
196:       
197:       # From that point the file size remains constant
198:       
199:       #
200:       # Correct Xrefs variations caused by ByteRange modifications.
201:       #
202:       rebuildxrefs
203:       
204:       filedata = self.to_bin
205:       signable_data = filedata[digsig.ByteRange[0],digsig.ByteRange[1]] + filedata[digsig.ByteRange[2],digsig.ByteRange[3]]
206:       
207:       signature = OpenSSL::PKCS7.sign(certificate, key, signable_data, [], OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
208:       digsig.Contents[0, signature.size] = signature
209:       
210:       #
211:       # No more modification are allowed after signing.
212:       #
213:       self.freeze
214:       
215:     end

Encrypts the current document with the provided passwords. The document will be encrypted at writing-on-disk time.

userpasswd:The user password.
ownerpasswd:The owner password.
options:A set of options to configure encryption.

[Source]

     # File sources/parser/encryption.rb, line 174
174:     def encrypt(userpasswd, ownerpasswd, options = {})
175:     
176:       if self.is_encrypted?
177:         raise EncryptionError, "PDF is already encrypted"
178:       end
179: 
180:       #
181:       # Default encryption options.
182:       #
183:       params = 
184:       {
185:         :Algorithm => :RC4,         # :RC4 or :AES
186:         :KeyLength => 128,          # Key size in bits
187:         :EncryptMetadata => true,    # Metadata shall be encrypted?
188:         :Permissions => Encryption::Standard::Permissions::ALL    # Document permissions
189:       }
190: 
191:       params.update(options)
192: 
193:       case params[:Algorithm]
194:       when :RC4
195:         algorithm = Encryption::ARC4
196:         if (40..128) === params[:KeyLength] and params[:KeyLength] % 8 == 0
197:           if params[:KeyLength] > 40
198:             version = 2
199:             revision = 3
200:           else
201:             version = 1
202:             revision = 2
203:           end
204:         else
205:           raise EncryptionError, "Invalid key length"
206:         end
207:       when :AES
208:         algorithm = Encryption::AES
209:         if params[:KeyLength] == 128 
210:           version = revision = 4
211:         else
212:           raise EncryptionError, "Invalid key length"
213:         end
214:       else
215:         raise EncryptionNotSupportedError, "Algorithm not supported : #{params[:Algorithm]}"
216:       end
217:      
218:       id = (get_doc_attr(:ID) || gen_id).first
219: 
220:       handler = Encryption::Standard::Dictionary.new
221:       handler.Filter = :Standard #:nodoc:
222:       handler.V = version
223:       handler.R = revision
224:       handler.Length = params[:KeyLength]
225:       handler.P = params[:Permissions] 
226:       
227:       if revision == 4
228:         handler.EncryptMetadata = params[:EncryptMetadata]
229:         handler.CF = Dictionary.new
230:         cryptfilter = Encryption::CryptFilterDictionary.new
231:         cryptfilter.AuthEvent = :DocOpen
232:         cryptfilter.CFM = :AESV2
233:         cryptfilter.Length = params[:KeyLength] >> 3
234: 
235:         handler.CF[:StdCF] = cryptfilter
236:         handler.StmF = handler.StrF = :StdCF
237:       end
238:       
239:       handler.set_owner_password(userpasswd, ownerpasswd)
240:       handler.set_user_password(userpasswd, id)
241:       
242:       encryption_key = handler.compute_encryption_key(userpasswd, id)
243: 
244:       fileInfo = get_trailer_info
245:       fileInfo[:Encrypt] = self << handler
246: 
247:       self.extend(Encryption::EncryptedDocument)
248:       self.encryption_dict = handler
249:       self.encryption_key = encryption_key
250:       self.stm_algo = self.str_algo = algorithm
251: 
252:       self
253:     end

Exports the document to a dot Graphiz file.

filename:The path where to save the file.

[Source]

     # File sources/parser/export.rb, line 34
 34:     def export_to_graph(filename)
 35:       
 36:       def appearance(object) #:nodoc:
 37:       
 38:         label = object.type.to_s
 39:         case object
 40:           when Catalog
 41:             fontcolor = "red"
 42:             color = "mistyrose"
 43:             shape = "doublecircle"
 44:           when Name, Number
 45:             label = object.value 
 46:             fontcolor = "orange"
 47:             color = "lightgoldenrodyellow"
 48:             shape = "polygon"
 49:            when String
 50:             label = object.value unless (object.is_binary_data? or object.length > 50)
 51:             fontcolor = "red"
 52:             color = "white"
 53:             shape = "polygon"
 54:           when Array
 55:             fontcolor = "green"
 56:             color = "lightcyan"
 57:             shape = "ellipse"
 58:         else
 59:           fontcolor = "blue"
 60:           color = "aliceblue"
 61:           shape = "ellipse"
 62:         end
 63:       
 64:         { :label => label, :fontcolor => fontcolor, :color => color, :shape => shape }
 65:       end
 66:       
 67:       def add_edges(pdf, fd, object) #:nodoc:
 68:         
 69:         if object.is_a?(Array) or object.is_a?(ObjectStream)
 70:           
 71:           object.each { |subobj|
 72:             
 73:             if subobj.is_a?(Reference) then subobj = pdf.indirect_objects[subobj] end
 74:             
 75:             unless subobj.nil?
 76:               fd << "\t#{object.object_id} -> #{subobj.object_id}\n"
 77:             end
 78:           }
 79:           
 80:         elsif object.is_a?(Dictionary)
 81:           
 82:           object.each_pair { |name, subobj|
 83:             
 84:             if subobj.is_a?(Reference) then subobj = pdf.indirect_objects[subobj] end
 85:             
 86:             unless subobj.nil?
 87:               fd << "\t#{object.object_id} -> #{subobj.object_id} [label=\"#{name.value}\",fontsize=7];\n"
 88:             end
 89:           }
 90:           
 91:         end
 92:         
 93:         if object.is_a?(Stream)
 94:           
 95:           object.dictionary.each_pair { |key, value|
 96:           
 97:             if value.is_a?(Reference) then value = pdf.indirect_objects[subobj] end
 98:             
 99:             unless value.nil?
100:               fd << "\t#{object.object_id} -> #{value.object_id} [label=\"#{key.value}\",fontsize=7];\n"
101:             end
102:           }
103:           
104:         end
105:         
106:       end
107:       
108:       graphname = "PDF" if graphname.nil? or graphname.empty?
109:       
110:       fd = File.open(filename, "w")
111:     
112:       begin
113:         
114:         fd << "digraph #{graphname} {\n\n"
115:         
116:         objects = self.objects(true).find_all{ |obj| not obj.is_a?(Reference) }
117:         
118:         objects.each { |object|
119:           
120:           attr = appearance(object)
121:           
122:           fd << "\t#{object.object_id} [label=\"#{attr[:label]}\",shape=#{attr[:shape]},color=#{attr[:color]},style=filled,fontcolor=#{attr[:fontcolor]}];\n"
123:           
124:           if object.is_a?(Stream)
125:             
126:             object.dictionary.each { |value|
127:             
128:               unless value.is_a?(Reference)
129:                 attr = appearance(value)
130:                 fd << "\t#{value.object_id} [label=\"#{attr[:label]}\",shape=#{attr[:shape]},color=#{attr[:color]},style=filled,fontcolor=#{attr[:fontcolor]}];\n"
131:               end
132:             
133:             }
134:             
135:           end
136:           
137:           add_edges(self, fd, object)
138:         
139:         }
140:         
141:         fd << "\n}"
142:         
143:       ensure
144:         fd.close
145:       end
146:       
147:     end

Exports the document to a GraphML file.

filename:The path where to save the file.

[Source]

     # File sources/parser/export.rb, line 153
153:     def export_to_graphml(filename)
154:       
155:       def declare_node(id, attr) #:nodoc:
156:         " <node id=\"#{id}\">\n" <<
157:         "  <data key=\"d0\">\n" <<
158:         "    <y:ShapeNode>\n" <<
159:         "     <y:NodeLabel>#{attr[:label]}</y:NodeLabel>\n" <<
160:         #~ "     <y:Shape type=\"#{attr[:shape]}\"/>\n" <<
161:         "    </y:ShapeNode>\n" <<
162:         "  </data>\n" <<
163:         " </node>\n"
164:       end
165:       
166:       def declare_edge(id, src, dest, label = nil) #:nodoc:
167:         " <edge id=\"#{id}\" source=\"#{src}\" target=\"#{dest}\">\n" << 
168:         "  <data key=\"d1\">\n" <<
169:         "   <y:PolyLineEdge>\n" <<
170:         "    <y:LineStyle type=\"line\" width=\"1.0\" color=\"#000000\"/>\n" <<
171:         "    <y:Arrows source=\"none\" target=\"standard\"/>\n" << 
172:         "    <y:EdgeLabel>#{label.to_s}</y:EdgeLabel>\n" <<
173:         "   </y:PolyLineEdge>\n" <<
174:         "  </data>\n" <<
175:         " </edge>\n"
176:       end
177:       
178:       def appearance(object) #:nodoc:
179:       
180:         label = object.type.to_s
181:         case object
182:           when Catalog
183:             fontcolor = "red"
184:             color = "mistyrose"
185:             shape = "doublecircle"
186:           when Name, Number
187:             label = object.value 
188:             fontcolor = "orange"
189:             color = "lightgoldenrodyellow"
190:             shape = "polygon"
191:           when String
192:             label = object.value unless (object.is_binary_data? or object.length > 50)
193:             fontcolor = "red"
194:             color = "white"
195:             shape = "polygon"
196:           when Array
197:             fontcolor = "green"
198:             color = "lightcyan"
199:             shape = "ellipse"
200:         else
201:           fontcolor = "blue"
202:           color = "aliceblue"
203:           shape = "ellipse"
204:         end
205:       
206:         { :label => label, :fontcolor => fontcolor, :color => color, :shape => shape }
207:       end
208:       
209:      def add_edges(pdf, fd, object, id) #:nodoc:
210:         
211:         if object.is_a?(Array) or object.is_a?(ObjectStream)
212:           
213:           object.each { |subobj|
214:             
215:             if subobj.is_a?(Reference) then subobj = pdf.indirect_objects[subobj] end
216:             
217:             unless subobj.nil?
218:               fd << declare_edge("e#{id}", "n#{object.object_id}", "n#{subobj.object_id}")
219:               id = id + 1
220:             end
221:           }
222:           
223:         elsif object.is_a?(Dictionary)
224:           
225:           object.each_pair { |name, subobj|
226:             
227:             if subobj.is_a?(Reference) then subobj = pdf.indirect_objects[subobj] end
228:             
229:             unless subobj.nil?
230:               fd << declare_edge("e#{id}", "n#{object.object_id}", "n#{subobj.object_id}", name.value)
231:               id = id + 1
232:             end
233:           }
234:           
235:         end
236:         
237:         if object.is_a?(Stream)
238:           
239:           object.dictionary.each_pair { |key, value|
240:           
241:             if value.is_a?(Reference) then value = pdf.indirect_objects[subobj] end
242:             
243:             unless value.nil?
244:               fd << declare_edge("e#{id}", "n#{object.object_id}", "n#{value.object_id}", key.value)
245:               id = id + 1
246:             end
247:           }
248:           
249:         end
250:         
251:         id
252:       end
253:       
254:       @@edge_nb = 1
255:       
256:       graphname = "PDF" if graphname.nil? or graphname.empty?
257:       
258:       fd = File.open(filename, "w")
259:       
260:       edge_nb = 1
261:       begin
262:         
263:         fd << '<?xml version="1.0" encoding="UTF-8"?>' << "\n"
264:         fd << '<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml"' << "\n"
265:         fd << ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' << "\n"
266:         fd << ' xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml ' << "\n"
267:         fd << ' http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd"' << "\n"
268:         fd << ' xmlns:y="http://www.yworks.com/xml/graphml">' << "\n"
269:         fd << '<key id="d0" for="node" yfiles.type="nodegraphics"/>' << "\n"
270:         fd << '<key id="d1" for="edge" yfiles.type="edgegraphics"/>' << "\n"
271:         fd << "<graph id=\"#{graphname}\" edgedefault=\"directed\">\n"
272:         
273:         objects = self.objects(true).find_all{ |obj| not obj.is_a?(Reference) }
274:         
275:         objects.each { |object|
276:           
277:           fd << declare_node("n#{object.object_id}", appearance(object))
278:           
279:           if object.is_a?(Stream)
280:             
281:             object.dictionary.each { |value|
282:             
283:               unless value.is_a?(Reference)
284:                 fd << declare_node(value.object_id, appearance(value))
285:               end
286:             }
287:           end
288:           
289:           edge_nb = add_edges(self, fd, object, edge_nb)
290:         }
291:         
292:         fd << '</graph>' << "\n"
293:         fd << '</graphml>'
294:         
295:       ensure
296:         fd.close
297:       end
298:       
299:     end

Returns the virtual file size as it would be taking on disk.

[Source]

     # File sources/parser/pdf.rb, line 193
193:     def filesize
194:       self.to_bin(:rebuildxrefs => false).size
195:     end

Returns an array of objects matching specified block.

[Source]

     # File sources/parser/pdf.rb, line 309
309:     def find(params = {}, &b)
310:       
311:       options =
312:       {
313:         :only_indirect => false
314:       }
315:       options.update(params)
316:       
317:       objset = (options[:only_indirect] == true) ? 
318:         self.indirect_objects.values : self.objects
319: 
320:       objset.find_all(&b)
321:     end

Returns the document information dictionary if present.

[Source]

    # File sources/parser/metadata.rb, line 49
49:     def get_document_info
50:       get_doc_attr :Info
51:     end

Returns a Hash of the information found in the metadata stream

[Source]

    # File sources/parser/metadata.rb, line 56
56:     def get_metadata
57:       metadata_stm = self.Catalog.Metadata
58: 
59:       if metadata_stm.is_a?(Stream)
60:         doc = REXML::Document.new(metadata_stm.data)
61: 
62:         info = {}
63: 
64:         doc.elements.each('*/*/rdf:Description') do |description|
65:           
66:           description.attributes.each_attribute do |attr|
67:             case attr.prefix
68:               when 'pdf','xap','pdf'
69:                 info[attr.name] = attr.value
70:             end
71:           end
72: 
73:           description.elements.each('*') do |element|
74:             value = (element.elements['.//rdf:li'] || element).text
75:             info[element.name] = value.to_s
76:           end
77: 
78:         end
79: 
80:         return info
81:       end
82:     end

Returns an array of Objects whose content is matching pattern.

[Source]

     # File sources/parser/pdf.rb, line 255
255:     def grep(*patterns)
256: 
257:       patterns.map! do |pattern|
258:         pattern.is_a?(::String) ? Regexp.new(Regexp.escape(pattern)) : pattern
259:       end
260: 
261:       unless patterns.all? { |pattern| pattern.is_a?(Regexp) }
262:         raise TypeError, "Expected a String or Regexp"
263:       end
264: 
265:       result = []
266:       objects.each do |obj|
267:         case obj
268:           when String, Name
269:             result << obj if patterns.any?{|pattern| obj.value.to_s.match(pattern)}
270:           when Stream
271:             result << obj if patterns.any?{|pattern| obj.data.match(pattern)}
272:         end
273:       end
274: 
275:       result
276:     end

Returns true if the document has a document information dictionary.

[Source]

    # File sources/parser/metadata.rb, line 35
35:     def has_document_info?
36:       has_attr? :Info 
37:     end

Returns true if the document contains an acrobat form.

[Source]

    # File sources/parser/acroform.rb, line 33
33:     def has_form?
34:       not self.Catalog.nil? and not self.Catalog.has_field? :AcroForm
35:     end

Returns true if the document has a catalog metadata stream.

[Source]

    # File sources/parser/metadata.rb, line 42
42:     def has_metadata?
43:       self.Catalog.has_key? :Metadata 
44:     end

[Source]

     # File sources/parser/signature.rb, line 217
217:     def has_usage_rights?
218:       
219:       #~ not self.Catalog.Perms.nil? and (not self.Catalog.Perms.UR3.nil? or not self.Catalog.Perms.UR.nil?)
220:       "todo"
221:       
222:     end

Return an hash of indirect objects. Updated objects appear only once.

[Source]

     # File sources/parser/pdf.rb, line 363
363:     def indirect_objects
364:       @revisions.inject({}) do |set, rev| set.merge(rev.body) end
365:     end

[Source]

    # File sources/parser/page.rb, line 45
45:     def insert_page(index, page)
46:       
47:       treeroot = self.Catalog.Pages
48:       raise InvalidPDF, "No page tree" if treeroot.nil?
49: 
50:       treeroot.insert_page(index, page)
51: 
52:       self
53:     end

Returns whether the PDF file is encrypted.

[Source]

    # File sources/parser/encryption.rb, line 45
45:     def is_encrypted?
46:       has_attr? :Encrypt
47:     end

Returns whether the current document is linearized.

[Source]

    # File sources/parser/linearization.rb, line 33
33:     def is_linearized?
34:       obj = @revisions.first.body.values.first
35:       
36:       obj.is_a?(Dictionary) and obj.has_key? :Linearized
37:     end

Returns whether the document contains a digital signature.

[Source]

     # File sources/parser/signature.rb, line 119
119:     def is_signed?
120:       
121:       #~ not self.Catalog.AcroForm.nil? and (self.Catalog.AcroForm[:SigFlags] & InteractiveForm::SigFlags::SIGNATUREEXISTS) != 0
122:       "todo"
123:       
124:     end

Returns an array of Objects whose name (in a Dictionary) is matching pattern.

[Source]

     # File sources/parser/pdf.rb, line 281
281:     def ls(*patterns)
282:     
283:       if patterns.empty?
284:         return objects
285:       end
286: 
287:       result = []
288: 
289:       patterns.map! do |pattern|
290:         pattern.is_a?(::String) ? Regexp.new(Regexp.escape(pattern)) : pattern
291:       end
292: 
293:       objects.each do |obj|
294:         if obj.is_a?(Dictionary)
295:           obj.each_pair do |name, obj|
296:             if patterns.any?{ |pattern| name.value.to_s.match(pattern) }
297:               result << ( obj.is_a?(Reference) ? obj.solve : obj )
298:             end
299:           end
300:         end
301:       end
302: 
303:       result
304:     end

[Source]

     # File sources/parser/obfuscation.rb, line 216
216:     def obfuscate_and_saveas(filename, options = {})
217:       options[:obfuscate] = true
218:       saveas(filename, options)
219:     end

Returns an array of objects embedded in the PDF body.

include_objstm:Whether it shall return objects embedded in object streams.

Note : Shall return to an iterator for Ruby 1.9 comp.

[Source]

     # File sources/parser/pdf.rb, line 328
328:     def objects(include_objstm = true)
329:       
330:       def append_subobj(root, objset, inc_objstm)
331:         
332:         if objset.find{ |o| root.equal?(o) }.nil?
333:           
334:           objset << root
335: 
336:           if root.is_a?(Dictionary)
337:             root.each_pair { |name, value|
338:               append_subobj(name, objset, inc_objstm)
339:               append_subobj(value, objset, inc_objstm)
340:             }
341:           elsif root.is_a?(Array) or (root.is_a?(ObjectStream) and inc_objstm == true)
342:             root.each { |subobj| append_subobj(subobj, objset, inc_objstm) }
343:           end
344:         
345:         end
346:         
347:       end
348:       
349:       objset = []
350:       @revisions.each { |revision|
351:         revision.body.each_value { |object|
352:             append_subobj(object, objset, include_objstm)
353:         }
354:       }
355:       
356:       objset
357:     end

Sets an action to run on document closing.

action:A JavaScript Action Object.

[Source]

    # File sources/parser/catalog.rb, line 76
76:     def onDocumentClose(action)
77:       
78:       unless action.is_a?(Action::JavaScript)
79:         raise TypeError, "An Action::JavaScript object must be passed."
80:       end
81:       
82:       unless self.Catalog
83:         raise InvalidPDF, "A catalog object must exist to add this action."
84:       end
85:       
86:       self.Catalog.AA ||= CatalogAdditionalActions.new
87:       self.Catalog.AA.WC = action
88:       
89:       self
90:     end

Sets an action to run on document opening.

action:An Action Object.

[Source]

    # File sources/parser/catalog.rb, line 57
57:     def onDocumentOpen(action)   
58:       
59:       unless action.is_a?(Action::Action)
60:         raise TypeError, "An Action object must be passed."
61:       end
62:       
63:       unless self.Catalog
64:         raise InvalidPDF, "A catalog object must exist to add this action."
65:       end
66:       
67:       self.Catalog.OpenAction = action
68:       
69:       self
70:     end

Sets an action to run on document printing.

action:A JavaScript Action Object.

[Source]

     # File sources/parser/catalog.rb, line 96
 96:     def onDocumentPrint(action)
 97:       
 98:       unless action.is_a?(Action::JavaScript)
 99:         raise TypeError, "An Action::JavaScript object must be passed."
100:       end
101:       
102:       unless self.Catalog
103:         raise InvalidPDF, "A catalog object must exist to add this action."
104:       end
105:       
106:       self.Catalog.AA ||= CatalogAdditionalActions.new
107:       self.Catalog.AA.WP = action
108:       
109:     end

Returns an array of Page

[Source]

    # File sources/parser/page.rb, line 58
58:     def pages
59:       
60:       return [] if not self.Catalog or not self.Catalog.Pages
61:       
62:       root = self.Catalog.Pages
63:       return [] if root.nil?
64: 
65:       root.solve if root.is_a?(Reference) 
66: 
67:       root.children
68:     end

Converts a logical PDF view into a physical view ready for writing.

[Source]

     # File sources/parser/pdf.rb, line 780
780:     def physicalize
781:      
782:       #
783:       # Indirect objects are added to the revision and assigned numbers.
784:       #
785:       def build(obj, revision) #:nodoc:
786: 
787:         #
788:         # Finalize any subobjects before building the stream.
789:         #
790:         if obj.is_a?(ObjectStream)
791:           obj.each { |subobj|
792:             build(subobj, revision)
793:           }
794:         end
795:   
796:         obj.pre_build
797: 
798:         if obj.is_a?(Dictionary) or obj.is_a?(Array)
799:             
800:             obj.map! { |subobj|
801:               if subobj.is_indirect?
802:                 if get_object(subobj.reference)
803:                   subobj.reference
804:                 else
805:                   ref = add_to_revision(subobj, revision)
806:                   build(subobj, revision)
807:                   ref
808:                 end
809:               else
810:                 subobj
811:               end
812:             }
813:             
814:             obj.each { |subobj|
815:               build(subobj, revision)
816:             }
817:             
818:         elsif obj.is_a?(Stream)
819:           build(obj.dictionary, revision)
820:         end
821: 
822:         obj.post_build
823:         
824:       end
825:       
826:       all_indirect_objects.each { |obj, revision|
827:           build(obj, revision)          
828:       }
829:       
830:       self
831:     end

Compute and update XRef::Section for each Revision.

[Source]

     # File sources/parser/pdf.rb, line 626
626:     def rebuildxrefs
627:       
628:       size = 0
629:       startxref = @header.to_s.size
630:       
631:       @revisions.each { |revision|
632:       
633:         revision.body.each_value { |object|
634:           startxref += object.to_s.size
635:         }
636:         
637:         size += revision.body.size
638:         revision.xreftable = buildxrefs(revision.body.values)
639:         
640:         revision.trailer ||= Trailer.new
641:         revision.trailer.Size = size + 1
642:         revision.trailer.startxref = startxref
643:         
644:         startxref += revision.xreftable.to_s.size + revision.trailer.to_s.size
645:       }
646:       
647:       self
648:     end

Registers an object into a specific Names root dictionary.

root:The root dictionary (see Names::Root)
name:The value name.
value:The value to associate with this name.

[Source]

     # File sources/parser/catalog.rb, line 117
117:     def register(root, name, value)
118:       
119:       if self.Catalog.Names.nil?
120:         self.Catalog.Names = Names.new
121:       end
122:       
123:       value.set_indirect(true)
124:       
125:       namesroot = self.Catalog.Names.send(root)
126:       if namesroot.nil?
127:         names = NameTreeNode.new({:Names => [] })
128:         self.Catalog.Names.send((root.id2name + "=").to_sym, (self << names))
129:         names.Names << name << value
130:       else
131:         namesroot.Names << name << value
132:       end
133:       
134:     end

Removes a whole document revision.

index:Revision index, first is 0.

[Source]

     # File sources/parser/pdf.rb, line 668
668:     def remove_revision(index)
669:       if index < 0 or index > @revisions.size
670:         raise IndexError, "Not a valid revision index"
671:       end
672: 
673:       if @revisions.size == 1
674:         raise InvalidPDF, "Cannot remove last revision"
675:       end
676: 
677:       @revisions.delete_at(index)
678:       self
679:     end

Tries to strip any xrefs information off the document.

[Source]

    # File sources/parser/xreftable.rb, line 29
29:     def remove_xrefs
30:       def delete_xrefstm(xrefstm)
31:         prev = xrefstm.Prev
32:         delete_object(xrefstm.reference)
33: 
34:         if prev.is_a?(Integer) and (prev_stm = get_object_by_offset(prev)).is_a?(XRefStream)
35:           delete_xrefstm(prev_stm)
36:         end
37:       end
38: 
39:       @revisions.reverse_each do |rev|
40:         if rev.has_xrefstm?
41:           delete_xrefstm(rev.xrefstm)
42:         end
43:         
44:         if rev.trailer.has_dictionary? and rev.trailer.XRefStm.is_a?(Integer)
45:           xrefstm = get_object_by_offset(rev.trailer.XRefStm)
46: 
47:           delete_xrefstm(xrefstm) if xrefstm.is_a?(XRefStream)
48:         end
49: 
50:         rev.xrefstm = rev.xreftable = nil
51:       end
52:     end

Saves the current file as its current filename.

[Source]

     # File sources/parser/pdf.rb, line 200
200:     def save(file, params = {})
201:       
202:       options = 
203:       {
204:         :delinearize => false,
205:         :recompile => true,
206:       }
207:       options.update(params)
208: 
209:       if file.respond_to?(:write)
210:         fd = file
211:       else
212:         fd = File.open(file, 'w').binmode
213:       end
214:       
215:       self.delinearize! if options[:delinearize] == true and is_linearized?
216:       self.compile if options[:recompile] == true
217: 
218:       fd.write self.to_bin(options)
219:         
220:       fd.close unless file.respond_to?(:write)
221:       
222:       self
223:     end

Saves the file up to given revision number. This can be useful to visualize the modifications over different incremental updates.

revision:The revision number to save.
filename:The path where to save this PDF.

[Source]

     # File sources/parser/pdf.rb, line 248
248:     def save_upto(revision, filename)
249:       saveas(filename, :up_to_revision => revision)  
250:     end

Sets the current filename to the argument given, then save it.

filename:The path where to save this PDF.

[Source]

     # File sources/parser/pdf.rb, line 229
229:     def saveas(filename, params = {})
230:       
231:       if self.frozen?
232:         params[:recompile] = params[:rebuildxrefs] = false
233:         save(filename, params)
234:       else 
235:         @filename = filename
236:         save(filename, params)
237:       end
238:       
239:       self
240:     end

Serializes the current PDF

[Source]

     # File sources/parser/pdf.rb, line 182
182:     def serialize(filename)
183:         Zlib::GzipWriter.open(filename) { |gz|
184:           gz.write Marshal.dump(self)
185:         }
186:         
187:         self
188:     end

Sign the document with the given key and x509 certificate.

certificate:The X509 certificate containing the public key.
key:The private key associated with the certificate.
ca:Optional CA certificates used to sign the user certificate.

[Source]

     # File sources/parser/signature.rb, line 34
 34:     def sign(certificate, key, ca = [], annotation = nil, location = nil, contact = nil, reason = nil)
 35:       
 36:       unless certificate.is_a?(OpenSSL::X509::Certificate)
 37:         raise TypeError, "A OpenSSL::X509::Certificate object must be passed."
 38:       end
 39:       
 40:       unless key.is_a?(OpenSSL::PKey::RSA)
 41:         raise TypeError, "A OpenSSL::PKey::RSA object must be passed."
 42:       end
 43:       
 44:       unless ca.is_a?(::Array)
 45:         raise TypeError, "Expected an Array of CA certificate."
 46:       end
 47:       
 48:       unless annotation.nil? or annotation.is_a?(Annotation::Widget::Signature)
 49:         raise TypeError, "Expected a Annotation::Widget::Signature object."
 50:       end
 51:       
 52:       def signfield_size(certificate, key, ca = []) #;nodoc:
 53:         datatest = "abcdefghijklmnopqrstuvwxyz"
 54:         OpenSSL::PKCS7.sign(certificate, key, datatest, ca, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der.size + 128
 55:       end
 56:       
 57:       digsig = Signature::DigitalSignature.new.set_indirect(true)
 58:       
 59:       if annotation.nil?
 60:         annotation = Annotation::Widget::Signature.new
 61:         annotation.Rect = Rectangle[:llx => 0.0, :lly => 0.0, :urx => 0.0, :ury => 0.0]        
 62:       end
 63:       
 64:       annotation.V = digsig ;
 65:       add_field(annotation)
 66:       self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::SIGNATURESEXIST | InteractiveForm::SigFlags::APPENDONLY
 67:       
 68:       digsig.Type = :Sig #:nodoc:
 69:       digsig.Contents = HexaString.new("\x00" * signfield_size(certificate, key, ca)) #:nodoc:
 70:       digsig.Filter = Name.new("Adobe.PPKMS") #:nodoc:
 71:       digsig.SubFilter = Name.new("adbe.pkcs7.detached") #:nodoc:
 72:       digsig.ByteRange = [0, 0, 0, 0] #:nodoc:
 73:       
 74:       digsig.Location = HexaString.new(location) if location
 75:       digsig.ContactInfo = HexaString.new(contact) if contact
 76:       digsig.Reason = HexaString.new(reason) if reason
 77:       
 78:       #
 79:       #  Flattening the PDF to get file view.
 80:       #
 81:       self.compile
 82:       
 83:       #
 84:       # Creating an empty Xref table to compute signature byte range.
 85:       #
 86:       rebuild_dummy_xrefs
 87:       
 88:       sigoffset = get_object_offset(digsig.no, digsig.generation) + digsig.sigOffset
 89:       
 90:       digsig.ByteRange[0] = 0 
 91:       digsig.ByteRange[1] = sigoffset
 92:       digsig.ByteRange[2] = sigoffset + digsig.Contents.size
 93:       
 94:       digsig.ByteRange[3] = filesize - digsig.ByteRange[2] until digsig.ByteRange[3] == filesize - digsig.ByteRange[2]
 95:       
 96:       # From that point the file size remains constant
 97:       
 98:       #
 99:       # Correct Xrefs variations caused by ByteRange modifications.
100:       #
101:       rebuildxrefs
102:       
103:       filedata = self.to_bin
104:       signable_data = filedata[digsig.ByteRange[0],digsig.ByteRange[1]] + filedata[digsig.ByteRange[2],digsig.ByteRange[3]]
105:       
106:       signature = OpenSSL::PKCS7.sign(certificate, key, signable_data, ca, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
107:       digsig.Contents[0, signature.size] = signature
108:       
109:       #
110:       # No more modification are allowed after signing.
111:       #
112:       self.freeze
113:       
114:     end

Returns the final binary representation of the current document.

rebuildxrefs:Computes xrefs while writing objects (default true).
obfuscate:Do some basic syntactic object obfuscation.

[Source]

     # File sources/parser/pdf.rb, line 460
460:     def to_bin(params = {})
461:    
462:       has_objstm = self.indirect_objects.values.any?{|obj| obj.is_a?(ObjectStream)}
463: 
464:       options =
465:       {
466:         :rebuildxrefs => true,
467:         :obfuscate => false,
468:         :use_xrefstm => has_objstm,
469:         :use_xreftable => (not has_objstm),
470:         :up_to_revision => @revisions.size
471:         #todo linearize
472:       }
473:       options.update(params)
474: 
475:       options[:up_to_revision] = @revisions.size if options[:up_to_revision] > @revisions.size
476: 
477:       # Reset to default params if no xrefs are chosen (hybrid files not supported yet)
478:       if options[:use_xrefstm] == options[:use_xreftable]
479:         options[:use_xrefstm] = has_objstm
480:         options[:use_xreftable] = (not has_objstm)
481:       end
482: 
483:       # Get trailer dictionary
484:       trailer_info = get_trailer_info
485:       if trailer_info.nil?
486:         raise InvalidPDF, "No trailer information found"
487:       end
488:       trailer_dict = trailer_info.dictionary
489:  
490:       prev_xref_offset = nil
491:       xrefstm_offset = nil
492:       xreftable_offset = nil
493:     
494:       # Header
495:       bin = ""
496:       bin << @header.to_s
497:       
498:       # For each revision
499:       @revisions[0, options[:up_to_revision]].each do |rev|
500:         
501:         if options[:rebuildxrefs] == true
502:           lastno_table, lastno_stm = 0, 0
503:           brange_table, brange_stm = 0, 0
504:           
505:           xrefs_stm = [ XRef.new(0, XRef::LASTFREE, XRef::FREE) ]
506:           xrefs_table = [ XRef.new(0, XRef::LASTFREE, XRef::FREE) ]
507: 
508:           if options[:use_xreftable] == true
509:             xrefsection = XRef::Section.new
510:           end
511: 
512:           if options[:use_xrefstm] == true
513:             xrefstm = rev.xrefstm || XRefStream.new
514:             add_to_revision(xrefstm, rev) unless xrefstm == rev.xrefstm
515:           end
516:         end
517:        
518:         objset = rev.body.values
519:         
520:         objset.find_all{|obj| obj.is_a?(ObjectStream)}.each do |objstm|
521:           objset |= objstm.objects
522:         end if options[:rebuildxrefs] == true and options[:use_xrefstm] == true
523: 
524:         objset.sort # process objects in number order
525:         
526:         # For each object
527:         objset.sort.each { |obj|
528:          
529:           if options[:rebuildxrefs] == true
530:            
531:             # Adding subsections if needed
532:             if options[:use_xreftable] and (obj.no - lastno_table).abs > 1
533:               xrefsection << XRef::Subsection.new(brange_table, xrefs_table)
534: 
535:               xrefs_table.clear
536:               brange_table = obj.no
537:             end
538:             if options[:use_xrefstm] and (obj.no - lastno_stm).abs > 1
539:               xrefs_stm.each do |xref| xrefstm << xref end
540:               xrefstm.Index ||= []
541:               xrefstm.Index << brange_stm << xrefs_stm.length
542: 
543:               xrefs_stm.clear
544:               brange_stm = obj.no
545:             end
546: 
547:             # Process embedded objects
548:             if options[:use_xrefstm] and obj.parent != obj and obj.parent.is_a?(ObjectStream)
549:               index = obj.parent.index(obj.no)
550:              
551:               xrefs_stm << XRefToCompressedObj.new(obj.parent.no, index)
552:               
553:               lastno_stm = obj.no
554:             else
555:               xrefs_stm << XRef.new(bin.size, obj.generation, XRef::USED)
556:               xrefs_table << XRef.new(bin.size, obj.generation, XRef::USED)
557: 
558:               lastno_table = lastno_stm = obj.no
559:             end
560: 
561:           end
562:          
563:           if obj.parent == obj or not obj.parent.is_a?(ObjectStream)
564:            
565:             # Finalize XRefStm
566:             if options[:rebuildxrefs] == true and options[:use_xrefstm] == true and obj == xrefstm
567:               xrefstm_offset = bin.size
568:    
569:               xrefs_stm.each do |xref| xrefstm << xref end
570: 
571:               xrefstm.W = [ 1, (xrefstm_offset.to_s(2).size + 7) >> 3, 2 ]
572:               xrefstm.Index ||= []
573:               xrefstm.Index << brange_stm << xrefs_stm.size
574:    
575:               xrefstm.dictionary = xrefstm.dictionary.merge(trailer_dict) 
576:               xrefstm.Prev = prev_xref_offset
577: 
578:               rev.trailer.dictionary = nil
579: 
580:               add_to_revision(xrefstm, rev)
581: 
582:               xrefstm.pre_build
583:               xrefstm.post_build
584:             end
585: 
586:             bin << (options[:obfuscate] == true ? obj.to_obfuscated_str : obj.to_s)
587:           end
588:         }
589:       
590:         rev.trailer ||= Trailer.new
591:         
592:         # XRef table
593:         if options[:rebuildxrefs] == true
594:  
595:           if options[:use_xreftable] == true
596:             table_offset = bin.size
597:             
598:             xrefsection << XRef::Subsection.new(brange_table, xrefs_table)
599:             rev.xreftable = xrefsection
600:  
601:             rev.trailer.dictionary = trailer_dict
602:             rev.trailer.Size = objset.size + 1
603:             rev.trailer.Prev = prev_xref_offset
604: 
605:             rev.trailer.XRefStm = xrefstm_offset if options[:use_xrefstm] == true
606:           end
607: 
608:           startxref = options[:use_xreftable] == true ? table_offset : xrefstm_offset
609:           rev.trailer.startxref = prev_xref_offset = startxref
610: 
611:         end # end each rev
612:         
613:         # Trailer
614: 
615:         bin << rev.xreftable.to_s if options[:use_xreftable] == true
616:         bin << (options[:obfuscate] == true ? rev.trailer.to_obfuscated_str : rev.trailer.to_s)
617:         
618:       end
619:       
620:       bin
621:     end

[Validate]