| 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 |
| get_object | -> | [] |
| filename | [RW] | |
| header | [RW] | |
| revisions | [RW] |
| 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. |
# 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
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. |
# 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.
# File sources/parser/catalog.rb, line 33
33: def Catalog
34: get_doc_attr(:Root)
35: end
Sets the current Catalog Dictionary.
# 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. |
# 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.
# 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. |
# 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.
# 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
# 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
# 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. |
# 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.
# 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
# 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. |
# 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.
# 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
# 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.
# 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 |
# 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. |
# 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. |
# 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. |
# 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.
# 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.
# 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.
# 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
# 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.
# 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.
# 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.
# 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.
# File sources/parser/metadata.rb, line 42
42: def has_metadata?
43: self.Catalog.has_key? :Metadata
44: end
# 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
# 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 current document is linearized.
# 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.
# 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.
# 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
# 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.
# 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. |
# 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. |
# 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. |
# 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
Converts a logical PDF view into a physical view ready for writing.
# 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.
# 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. |
# 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. |
# 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.
# 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.
# 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. |
# 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. |
# 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
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. |
# 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. |
# 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