Class: Pdfrb::Model::Cos::Dictionary

Inherits:
Object
  • Object
show all
Defined in:
lib/pdfrb/model/cos/dictionary.rb

Overview

Common Object Syntax dictionary (s7.3.7). Wraps a Hash of Symbol -> PDF value. Subclassed by every semantic Type::*.

Field metadata can come from two sources (mutually exclusive per field; last declaration wins):

* `define_field` — hand-coded (HexaPDF shape).
* `arlington_object "Name"` — auto-loaded from vendored TSVs
via Pdfrb::Arlington::Loader. Calls `define_field` under
the hood with types translated to Ruby classes.

Instance Attribute Summary

Attributes inherited from Object

#document, #gen, #oid, #value

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Object

#==, define_type, #deref, #indirect?, #must_be_indirect?, pdf_type

Constructor Details

#initialize(value = {}, oid: 0, gen: 0, document: nil) ⇒ Dictionary

Returns a new instance of Dictionary.



181
182
183
184
185
186
187
188
189
# File 'lib/pdfrb/model/cos/dictionary.rb', line 181

def initialize(value = {}, oid: 0, gen: 0, document: nil)
  value ||= {}
  unless value.is_a?(::Hash)
    raise ArgumentError, "Dictionary value must be a Hash, got #{value.class}"
  end

  super(value, oid: oid, gen: gen, document: document)
  apply_required_defaults
end

Class Method Details

.arlington_available?Boolean

Returns:

  • (Boolean)


108
109
110
111
112
113
# File 'lib/pdfrb/model/cos/dictionary.rb', line 108

def arlington_available?
  Pdfrb.const_defined?(:Arlington) &&
    Pdfrb::Arlington.const_defined?(:Loader)
rescue StandardError
  false
end

.arlington_default(field_def) ⇒ Object



168
169
170
171
172
# File 'lib/pdfrb/model/cos/dictionary.rb', line 168

def arlington_default(field_def)
  return nil if field_def.default_value.nil?

  field_def.default_value
end

.arlington_key_to_symbol(key) ⇒ Object



158
159
160
# File 'lib/pdfrb/model/cos/dictionary.rb', line 158

def arlington_key_to_symbol(key)
  key.to_sym
end

.arlington_loaded_for?(_name) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/pdfrb/model/cos/dictionary.rb', line 98

def arlington_loaded_for?(_name)
  @arlington_loaded_name == name
end

.arlington_mark_loaded(name) ⇒ Object



103
104
105
# File 'lib/pdfrb/model/cos/dictionary.rb', line 103

def arlington_mark_loaded(name)
  @arlington_loaded_name = name
end

.arlington_object(name, version: "latest") ⇒ Object

Pull field metadata from the named Arlington TSV. Idempotent. Silently no-ops when the Arlington layer or the TSV is unavailable, so hand-coded subclasses still work.

Also registers self in Pdfrb::Model::Type.arlington_registry under name so field-link resolution can find this class when other Types reference it.



79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/pdfrb/model/cos/dictionary.rb', line 79

def arlington_object(name, version: "latest")
  return if arlington_loaded_for?(name)
  return unless arlington_available?

  definition = Pdfrb::Arlington::Loader.object_definition(name, version: version)
  return unless definition

  arlington_mark_loaded(name)
  Pdfrb::Model::Type.register_arlington(name, self) if Pdfrb::Model.const_defined?(:Type)
  definition.each_field do |field_def|
    define_field_from_arlington(field_def)
  end
end

.arlington_one_type_to_ruby(t) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/pdfrb/model/cos/dictionary.rb', line 137

def arlington_one_type_to_ruby(t)
  case t
  when :boolean then [TrueClass, FalseClass]
  when :integer then Integer
  when :number then [Integer, Float]
  when :name then Symbol
  when :string, :"string-ascii", :"string-text" then String
  when :"string-byte" then Fields::PDFByteString
  when :array then Pdfrb::Model::PdfArray
  when :dictionary then Pdfrb::Model::Cos::Dictionary
  when :stream then Pdfrb::Model::Cos::Stream
  when :null then NilClass
  when :date then Fields::PDFDate
  when :rectangle then Pdfrb::Model::Rectangle
  when :matrix then Pdfrb::Model::Matrix
  when :bitmask then Integer
  when :"name-tree", :"number-tree" then Pdfrb::Model::Cos::Dictionary
  end
end

.arlington_required?(field_def) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
# File 'lib/pdfrb/model/cos/dictionary.rb', line 163

def arlington_required?(field_def)
  field_def.required_literal == true
end

.arlington_types_to_ruby(field_def) ⇒ Object



132
133
134
# File 'lib/pdfrb/model/cos/dictionary.rb', line 132

def arlington_types_to_ruby(field_def)
  field_def.types.map { |t| arlington_one_type_to_ruby(t) }.flatten.compact.uniq
end

.arlington_version(field_def) ⇒ Object



175
176
177
# File 'lib/pdfrb/model/cos/dictionary.rb', line 175

def arlington_version(field_def)
  field_def.since_version.to_s
end

.define_field(pdf_name, type:, required: false, default: nil, indirect: nil, allowed_values: nil, version: "1.0", arlington: nil) ⇒ Object

Declare a field on this class. Mirrors HexaPDF's define_field shape; we add an arlington: slot for the source FieldDefinition.



44
45
46
47
48
49
50
51
52
# File 'lib/pdfrb/model/cos/dictionary.rb', line 44

def define_field(pdf_name, type:, required: false, default: nil,
                 indirect: nil, allowed_values: nil, version: "1.0",
                 arlington: nil)
  own_fields[pdf_name] = Fields::Field.new(
    pdf_name, type: type, required: required, default: default,
    indirect: indirect, allowed_values: allowed_values,
    version: version, arlington: arlington
  )
end

.define_field_from_arlington(field_def) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/pdfrb/model/cos/dictionary.rb', line 116

def define_field_from_arlington(field_def)
  types = arlington_types_to_ruby(field_def)
  return if types.empty?

  key = arlington_key_to_symbol(field_def.key)
  define_field(
    key,
    type: types.length == 1 ? types.first : types,
    required: arlington_required?(field_def),
    default: arlington_default(field_def),
    version: arlington_version(field_def),
    arlington: field_def
  )
end

.each_fieldObject

Iterate (name, Field) pairs across the inheritance chain.



63
64
65
66
67
68
69
70
# File 'lib/pdfrb/model/cos/dictionary.rb', line 63

def each_field
  return enum_for(:each_field) unless block_given?

  if superclass <= Pdfrb::Model::Cos::Dictionary
    superclass.each_field { |n, f| yield n, f }
  end
  own_fields.each { |n, f| yield n, f }
end

.field(name) ⇒ Object

Look up a field by name; walks the inheritance chain.



55
56
57
58
59
60
# File 'lib/pdfrb/model/cos/dictionary.rb', line 55

def field(name)
  return own_fields[name] if own_fields.key?(name)
  return superclass.field(name) if superclass <= Pdfrb::Model::Cos::Dictionary

  nil
end

.lookup_type(symbol) ⇒ Object



37
38
39
# File 'lib/pdfrb/model/cos/dictionary.rb', line 37

def lookup_type(symbol)
  TYPE_MAP[symbol]
end

.own_fieldsObject



93
94
95
# File 'lib/pdfrb/model/cos/dictionary.rb', line 93

def own_fields
  @own_fields ||= {}
end

.register_type(symbol, klass = self) ⇒ Object



32
33
34
35
# File 'lib/pdfrb/model/cos/dictionary.rb', line 32

def register_type(symbol, klass = self)
  TYPE_MAP[symbol] = klass
  klass.define_type(symbol) unless klass.pdf_type
end

.type_mapObject

/Type-symbol -> Dictionary subclass. Populated by register_type. Used by Document#wrap to dispatch.



28
29
30
# File 'lib/pdfrb/model/cos/dictionary.rb', line 28

def type_map
  TYPE_MAP
end

Instance Method Details

#[](name) ⇒ Object

Typed access. Resolves References, wraps in field-specific subclass, applies default, runs converters.



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/pdfrb/model/cos/dictionary.rb', line 193

def [](name)
  name = name.to_sym unless name.is_a?(::Symbol)
  field = self.class.field(name)
  data = @value[name]
  data = field.default if data.nil? && field&.default?
  return nil if data.nil?

  data = dereference(data)
  data = data.value if unbox_object?(data)

  return data unless field

  converted = Fields.apply(field, data, document)
  if converted
    @value[name] = converted
    converted
  else
    data
  end
end

#[]=(name, value) ⇒ Object

Raises:

  • (ArgumentError)


214
215
216
217
218
# File 'lib/pdfrb/model/cos/dictionary.rb', line 214

def []=(name, value)
  raise ArgumentError, "Dictionary keys must be Symbols" unless name.is_a?(::Symbol)

  @value[name] = value
end

#delete(name) ⇒ Object



225
226
227
228
# File 'lib/pdfrb/model/cos/dictionary.rb', line 225

def delete(name)
  name = name.to_sym unless name.is_a?(::Symbol)
  @value.delete(name)
end

#eachObject

Iterate (Symbol, processed-value) pairs. Slower than #each_raw because it runs the full accessor pipeline; use that when you don't need wrapping.



233
234
235
236
237
238
# File 'lib/pdfrb/model/cos/dictionary.rb', line 233

def each
  return enum_for(:each) unless block_given?

  @value.each_key { |k| yield(k, self[k]) }
  self
end

#each_rawObject

Iterate (Symbol, raw-value) pairs without accessor pipeline.



241
242
243
244
245
246
# File 'lib/pdfrb/model/cos/dictionary.rb', line 241

def each_raw
  return enum_for(:each_raw) unless block_given?

  @value.each { |k, v| yield(k, v) }
  self
end

#empty?Boolean

Returns:

  • (Boolean)


249
# File 'lib/pdfrb/model/cos/dictionary.rb', line 249

def empty?; @value.empty?; end

#key?(name) ⇒ Boolean

Returns:

  • (Boolean)


220
221
222
223
# File 'lib/pdfrb/model/cos/dictionary.rb', line 220

def key?(name)
  name = name.to_sym unless name.is_a?(::Symbol)
  !@value[name].nil?
end

#keysObject



248
# File 'lib/pdfrb/model/cos/dictionary.rb', line 248

def keys; @value.keys; end

#pdf_typeObject



251
252
253
# File 'lib/pdfrb/model/cos/dictionary.rb', line 251

def pdf_type
  self.class.pdf_type || self[:Type]
end

#validateObject

Walk every declared field and yield (message, correctable) for each violation. Returns an Enumerator if no block given.



257
258
259
260
261
262
263
# File 'lib/pdfrb/model/cos/dictionary.rb', line 257

def validate
  return enum_for(:validate) unless block_given?

  self.class.each_field do |name, field|
    validate_one(name, field) { |*a| yield(*a) }
  end
end