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