Class: Origami::Dictionary

Inherits:
Hash
  • Object
show all
Includes:
Object
Defined in:
lib/origami/dictionary.rb,
lib/origami/obfuscation.rb

Overview

Class representing a Dictionary Object. Dictionaries are containers associating a Name to an embedded Object.

Direct Known Subclasses

Action, Action::GoToE::EmbeddedTarget, Action::Launch::WindowsLaunchParams, Action::RichMediaExecute::Command, Adobe::PPKLite::AddressList, Adobe::PPKLite::Catalog, Adobe::PPKLite::Certificate, Adobe::PPKLite::PPK, Adobe::PPKLite::User, Adobe::PPKLite::UserList, AnimationStyle3D, Annotation, Annotation::AdditionalActions, Annotation::AppearanceCharacteristics, Annotation::AppearanceDictionary, Annotation::Artwork3D::Activation, Annotation::BorderStyle, Annotation::RichMedia::Activation, Annotation::RichMedia::Animation, Annotation::RichMedia::Configuration, Annotation::RichMedia::Content, Annotation::RichMedia::CuePoint, Annotation::RichMedia::Deactivation, Annotation::RichMedia::Instance, Annotation::RichMedia::Parameters, Annotation::RichMedia::Position, Annotation::RichMedia::Presentation, Annotation::RichMedia::Settings, Annotation::RichMedia::Window, Background3D, Catalog, CatalogAdditionalActions, CrossSection3D, DeveloperExtension, EmbeddedFileParameters, Encoding, Encryption::CryptFilterDictionary, Encryption::EncryptionDictionary, Extensions, Field::AdditionalActions, Field::CertificateSeedValue, Field::SignatureLock, Field::SignatureSeedValue, Field::Subform, FileSpec, Filter::CCITTFax::DecodeParms, Filter::Crypt::DecodeParms, Filter::DCT::DecodeParms, Filter::Flate::DecodeParms, Filter::JBIG2::DecodeParms, Filter::LZW::DecodeParms, Font, FontDescriptor, Function::Exponential, Function::Stitching, Graphics::ExtGState, Graphics::Pattern::Shading, Graphics::Pattern::Shading::Axial, Graphics::Pattern::Shading::FunctionBased, Graphics::Pattern::Shading::Radial, Graphics::ReferenceDictionary, InteractiveForm, LightingScheme3D, Linearization, Metadata, NameTreeNode, Names, NavigationNode, Node3D, Outline, OutlineItem, OutputIntent, Page, PageAdditionalActions, PageTreeNode, Perms, Projection3D, Reference3D, RenderMode3D, Requirement, Requirement::Handler, Resources, Signature::BuildData, Signature::BuildProperties, Signature::DigitalSignature, Signature::Reference, UsageRights::TransformParams, View3D, ViewerPreferences, Webcapture::Command, Webcapture::CommandSettings, Webcapture::SourceInformation, Webcapture::SpiderInfo

Constant Summary collapse

TOKENS =

:nodoc:

%w{ << >> }
@@regexp_open =
Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first) + WHITESPACES)
@@regexp_close =
Regexp.new(WHITESPACES + Regexp.escape(TOKENS.last) + WHITESPACES)
@@cast_fingerprints =
{}

Instance Attribute Summary collapse

Attributes included from Object

#file_offset, #generation, #no, #objstm_offset, #parent

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Object

#<=>, #export, #indirect_parent, #is_indirect?, #logicalize, #logicalize!, #native_type, #pdf, #pdf_version_required, #post_build, #pre_build, #reference, #resolve_all_references, #set_indirect, #set_pdf, #size, skip_until_next_obj, #solve, #to_o, #type, typeof, #xrefs

Methods inherited from Hash

#to_o

Constructor Details

#initialize(hash = {}) ⇒ Dictionary

Creates a new Dictionary.

hash

The hash representing the new Dictionary.

Raises:

  • (TypeError)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/origami/dictionary.rb', line 48

def initialize(hash = {})
  raise TypeError, "Expected type Hash, received #{hash.class}." unless hash.is_a?(Hash)
  super()
  
  @strings_cache = []
  @names_cache = []
  @xref_cache = {}

  hash.each_pair do |k,v|
    @names_cache.push(k.to_o)
    case val = v.to_o
      when String then @strings_cache.push(val)
      when Name then @names_cache.push(val)
      when Reference then
        (@xref_cache[val] ||= []).push(self)
      when Dictionary,Array then 
        @strings_cache.concat(val.strings_cache)
        @names_cache.concat(val.names_cache)
        @xref_cache.update(val.xref_cache) do |ref, cache1, cache2|
          cache1.concat(cache2)  
        end

        val.strings_cache.clear
        val.names_cache.clear
        val.xref_cache.clear
    end

    self[k.to_o] = val unless k.nil?
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(field, *args) ⇒ Object

:nodoc:

Raises:

  • (NoMethodError)


219
220
221
222
223
224
225
226
227
228
# File 'lib/origami/dictionary.rb', line 219

def method_missing(field, *args) #:nodoc:
  raise NoMethodError, "No method `#{field}' for #{self.class}" unless field.to_s[0,1] =~ /[A-Z]/

  if field.to_s[-1,1] == '='
    self[field.to_s[0..-2].to_sym] = args.first
  else
    obj = self[field]; 
    obj.is_a?(Reference) ? obj.solve : obj
  end
end

Instance Attribute Details

#names_cacheObject (readonly)

Returns the value of attribute names_cache.



42
43
44
# File 'lib/origami/dictionary.rb', line 42

def names_cache
  @names_cache
end

#strings_cacheObject (readonly)

Returns the value of attribute strings_cache.



42
43
44
# File 'lib/origami/dictionary.rb', line 42

def strings_cache
  @strings_cache
end

#xref_cacheObject (readonly)

Returns the value of attribute xref_cache.



42
43
44
# File 'lib/origami/dictionary.rb', line 42

def xref_cache
  @xref_cache
end

Class Method Details

.add_type_info(typeclass, key, value) ⇒ Object

:nodoc:



245
246
247
248
249
250
251
252
253
# File 'lib/origami/dictionary.rb', line 245

def self.add_type_info(typeclass, key, value) #:nodoc:
  if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Dictionary and
     @@cast_fingerprints.has_key?(typeclass.superclass)
    @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
  end

  @@cast_fingerprints[typeclass] ||= {}
  @@cast_fingerprints[typeclass][key.to_o] = value.to_o
end

.guess_type(hash) ⇒ Object

:nodoc:



255
256
257
258
259
260
261
262
263
264
265
# File 'lib/origami/dictionary.rb', line 255

def self.guess_type(hash) #:nodoc:
  best_type = self

  @@cast_fingerprints.each_pair do |typeclass, keys|
    best_type = typeclass if keys.all? { |k,v| 
      hash.has_key?(k) and hash[k] == v
    } and typeclass < best_type
  end

  best_type
end

.hint_type(name) ⇒ Object

:nodoc:



267
# File 'lib/origami/dictionary.rb', line 267

def self.hint_type(name); nil end

.native_typeObject



243
# File 'lib/origami/dictionary.rb', line 243

def self.native_type; Dictionary end

.parse(stream, parser = nil) ⇒ Object

:nodoc:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/origami/dictionary.rb', line 79

def self.parse(stream, parser = nil) #:nodoc:
  
  offset = stream.pos

  if stream.skip(@@regexp_open).nil?
    raise InvalidDictionaryObjectError, "No token '#{TOKENS.first}' found"
  end
    
  pairs = {}
  while stream.skip(@@regexp_close).nil? do
    key = Name.parse(stream, parser)
    
    type = Object.typeof(stream)
    if type.nil?
      raise InvalidDictionaryObjectError, "Invalid object for field #{key.to_s}"
    end
    
    value = type.parse(stream, parser)
    pairs[key] = value
  end
 
  dict = 
    if Origami::OPTIONS[:enable_type_guessing]
      guessed_type = self.guess_type(pairs)

      if Origami::OPTIONS[:enable_type_propagation]
        guessed_type.new(
          Hash[
            pairs.map {|key, value|
              hint_type = guessed_type.hint_type(key.value)
              if hint_type.is_a?(::Array) and not value.is_a?(Reference) # Choose best match
                hint_type.find {|type| type.native_type == value.native_type}
              end

              if hint_type.is_a?(Class) and hint_type.native_type == value.native_type
                [key, value.cast_to(hint_type)]
              elsif hint_type and value.is_a?(Reference) and parser
                parser.defer_type_cast(value, hint_type)
                [key, value]
              else
                [key, value]
              end
            }])
      else
        guessed_type.new(pairs)
      end
    else
      self.new(pairs)
    end

  dict.file_offset = offset

  dict
end

Instance Method Details

#[](key) ⇒ Object



184
185
186
# File 'lib/origami/dictionary.rb', line 184

def [](key)
  super(key.to_o)
end

#[]=(key, val) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/origami/dictionary.rb', line 165

def []=(key,val)
  unless key.is_a?(Symbol) or key.is_a?(Name)
    fail "Expecting a Name for a Dictionary entry, found #{key.class} instead."
  end
  
  key = key.to_o
  if not val.nil?
    val = val.to_o
    super(key,val)
    
    key.parent = self
    val.parent = self unless val.is_indirect? or val.parent.equal?(self)

    val
  else
    delete(key)
  end
end

#cast_to(type) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/origami/dictionary.rb', line 196

def cast_to(type)
  super(type)

  cast = type.new(self)
  cast.parent = self.parent
  cast.no, cast.generation = self.no, self.generation
  if self.is_indirect?
    cast.set_indirect(true) 
    cast.set_pdf(self.pdf) 
    cast.file_offset = self.file_offset # cast can replace self
  end

  cast.xref_cache.update(self.xref_cache)
  cast.names_cache.concat(self.names_cache)
  cast.strings_cache.concat(self.strings_cache)

  cast
end

#copyObject



230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/origami/dictionary.rb', line 230

def copy
  copy = self.class.new
  self.each_pair do |k,v|
    copy[k] = v.copy 
  end 

  copy.parent = @parent
  copy.no, copy.generation = @no, @generation
  copy.set_indirect(true) if is_indirect?
  copy.set_pdf(@pdf) if is_indirect?
  copy
end

#delete(key) ⇒ Object



192
193
194
# File 'lib/origami/dictionary.rb', line 192

def delete(key)
  super(key.to_o)
end

#has_key?(key) ⇒ Boolean

Returns:



188
189
190
# File 'lib/origami/dictionary.rb', line 188

def has_key?(key)
  super(key.to_o)
end

#map!(&b) ⇒ Object



155
156
157
158
159
# File 'lib/origami/dictionary.rb', line 155

def map!(&b)
  self.each_pair do |k,v|
    self[k] = b.call(v)
  end
end

#merge(dict) ⇒ Object



161
162
163
# File 'lib/origami/dictionary.rb', line 161

def merge(dict)
  Dictionary.new(super(dict))
end

#to_obfuscated_strObject



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/origami/obfuscation.rb', line 117

def to_obfuscated_str
  content = TOKENS.first + Obfuscator.junk_spaces
  self.each_pair { |key, value|
    content << Obfuscator.junk_spaces + 
      key.to_obfuscated_str + Obfuscator.junk_spaces + 
      value.to_obfuscated_str + Obfuscator.junk_spaces
  }

  content << TOKENS.last
  super(content)
end

#to_s(indent = 1) ⇒ Object

:nodoc:



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/origami/dictionary.rb', line 136

def to_s(indent = 1) #:nodoc:
  if indent > 0
    content = TOKENS.first + EOL
    self.each_pair do |key,value|
      content << "\t" * indent + key.to_s + " " + (value.is_a?(Dictionary) ? value.to_s(indent + 1) : value.to_s) + EOL
    end
    
    content << "\t" * (indent - 1) + TOKENS.last
  else
    content = TOKENS.first.dup
    self.each_pair do |key,value|
      content << "#{key.to_s} #{value.is_a?(Dictionary) ? value.to_s(0) : value.to_s}"
    end
    content << TOKENS.last
  end

  super(content)
end